@steadybrew/postgresjs 4.5.0-rc1 → 4.5.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/README.md +47 -12
- package/cf/src/connection.js +21 -5
- package/cf/src/errors.js +2 -2
- package/cf/src/index.js +6 -2
- package/cf/src/query.js +1 -0
- package/cf/src/types.js +2 -2
- package/cjs/src/connection.js +21 -5
- package/cjs/src/errors.js +2 -2
- package/cjs/src/index.js +6 -2
- package/cjs/src/query.js +1 -0
- package/cjs/src/types.js +2 -2
- package/package.json +3 -3
- package/src/connection.js +21 -5
- package/src/errors.js +2 -2
- package/src/index.js +6 -2
- package/src/query.js +1 -0
- package/src/types.js +2 -2
- package/types/index.d.ts +7 -1
package/README.md
CHANGED
|
@@ -71,22 +71,25 @@ See the [changelog](./CHANGELOG.md#breaking-changes-and-migration) for the full
|
|
|
71
71
|
|
|
72
72
|
## Fixed Postgres.js issues
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Bugs and feature requests from [porsager/postgres](https://github.com/porsager/postgres) that this fork fixes, whether reported in an issue or in a pull request suggesting a fix, and the version that fixed them. Each fix has a test that reproduces the report, except [#1100](https://github.com/porsager/postgres/issues/1100), which rests on a production measurement, and [#1154](https://github.com/porsager/postgres/issues/1154), whose test reproduces the reported stack without a known trigger. The [changelog](./CHANGELOG.md) has the details.
|
|
75
75
|
|
|
76
76
|
<details>
|
|
77
|
-
<summary>
|
|
77
|
+
<summary>54 bugs and 7 feature requests</summary>
|
|
78
78
|
|
|
79
79
|
**Wrong results and data safety**
|
|
80
80
|
|
|
81
81
|
| Issue | What happens now | Since |
|
|
82
82
|
| --- | --- | --- |
|
|
83
83
|
| [#1082](https://github.com/porsager/postgres/issues/1082), [#729](https://github.com/porsager/postgres/issues/729) | A query whose values fail to serialize rejects on its own; the query running alongside no longer gets its error, and the next query no longer gets another query's rows | 4.3.0 |
|
|
84
|
-
| [#1216](https://github.com/porsager/postgres/issues/1216) | A transaction handle whose connection closed
|
|
84
|
+
| [#1216](https://github.com/porsager/postgres/issues/1216) | A transaction handle whose connection closed rejects with `CONNECTION_CLOSED` instead of running inside the next user's transaction, so it can't read their rows through row-level security. | 4.0.0 |
|
|
85
85
|
| [#1248](https://github.com/porsager/postgres/issues/1248) | Statements in a `sql.begin()` callback after its connection closes reject instead of running outside the transaction | 4.0.0 |
|
|
86
|
-
| [#1204](https://github.com/porsager/postgres/issues/1204) | A transaction's connection is
|
|
86
|
+
| [#1204](https://github.com/porsager/postgres/issues/1204) | A transaction's connection is not handed to another `sql.begin()` when its socket drains late, so one transaction's `COMMIT` can't commit another's unfinished writes. | 4.0.0 |
|
|
87
87
|
| [#1217](https://github.com/porsager/postgres/issues/1217) | `sql.begin()` rejects with `TRANSACTION_ROLLED_BACK` when the server rolled back the COMMIT | 4.4.0 |
|
|
88
88
|
| [#1249](https://github.com/porsager/postgres/issues/1249), [#1226](https://github.com/porsager/postgres/issues/1226), [#1223](https://github.com/porsager/postgres/issues/1223) | The first query after a reconnect no longer rejects with an error from the closed session | 4.0.0 |
|
|
89
89
|
| [#789](https://github.com/porsager/postgres/issues/789) | `sql.array()` infers the right array type on first use | 4.0.0 |
|
|
90
|
+
| [#1049](https://github.com/porsager/postgres/issues/1049), [#1124](https://github.com/porsager/postgres/issues/1124) | `NULL` elements in arrays parse as `null`, not `NaN` or the string `"NULL"` | 4.0.0 |
|
|
91
|
+
| [#1252](https://github.com/porsager/postgres/issues/1252) | Cancelling a pipelined query that hasn't started no longer gives the next query its result | 4.1.0 |
|
|
92
|
+
| [#1253](https://github.com/porsager/postgres/issues/1253), [#1181](https://github.com/porsager/postgres/issues/1181), [#1119](https://github.com/porsager/postgres/issues/1119) | A query no longer gets empty slots at the start of its result after the previous query on the connection failed or was cancelled partway through its rows | 4.5.0 |
|
|
90
93
|
|
|
91
94
|
**Hangs**
|
|
92
95
|
|
|
@@ -96,40 +99,57 @@ Upstream issues on [porsager/postgres](https://github.com/porsager/postgres/issu
|
|
|
96
99
|
| [#1195](https://github.com/porsager/postgres/issues/1195), [#1199](https://github.com/porsager/postgres/issues/1199) | A reservation survives its connection being terminated by the server, and the pool recovers after it is released | 4.0.0 |
|
|
97
100
|
| [#1186](https://github.com/porsager/postgres/issues/1186) | Queries queued on a reserved or transaction handle settle when its connection closes | 4.0.0 |
|
|
98
101
|
| [#1097](https://github.com/porsager/postgres/issues/1097), [#1130](https://github.com/porsager/postgres/issues/1130), [#1242](https://github.com/porsager/postgres/issues/1242), [#861](https://github.com/porsager/postgres/issues/861) | `sql.end()` resolves after a disconnect, after a failed query and after a connection closes inside `sql.begin()`, and waits for queries queued in the pool | 4.0.0 |
|
|
99
|
-
| [#1246](https://github.com/porsager/postgres/issues/1246) | A query sent between a socket error and its close settles | 4.1.0 |
|
|
102
|
+
| [#1246](https://github.com/porsager/postgres/issues/1246) | A query sent between a socket error and its close settles, and a transaction reset mid-statement no longer loses a pool connection | 4.1.0 |
|
|
100
103
|
| [#1193](https://github.com/porsager/postgres/issues/1193) | A connection closed during startup settles its first query instead of reconnecting in a tight loop | 4.0.0 |
|
|
101
104
|
| [#1234](https://github.com/porsager/postgres/issues/1234) | Concurrent queries in `sql.begin()` after a column type change roll the transaction back and return the connection to the pool | 4.4.0 |
|
|
105
|
+
| [#1255](https://github.com/porsager/postgres/issues/1255), [#1261](https://github.com/porsager/postgres/issues/1261) | A reserved connection whose `max_lifetime` passed is closed when released, and `sql.end()` called before the release resolves | 4.0.0 |
|
|
106
|
+
| [#1016](https://github.com/porsager/postgres/pull/1016) | Queries no longer hang after a `COPY ... TO STDOUT` whose reader fell behind | 4.5.0 |
|
|
102
107
|
|
|
103
108
|
**Crashes**
|
|
104
109
|
|
|
105
110
|
| Issue | What happens now | Since |
|
|
106
111
|
| --- | --- | --- |
|
|
107
|
-
| [#
|
|
112
|
+
| [#1154](https://github.com/porsager/postgres/issues/1154), [#1208](https://github.com/porsager/postgres/issues/1208), [#1133](https://github.com/porsager/postgres/issues/1133) | A reserved or transaction connection that the server or network closes rejects its later statements, and `sql.begin()`'s own `ROLLBACK` or `COMMIT`, with `CONNECTION_CLOSED` instead of crashing the process with `Cannot read properties of null (reading 'write')`. | 4.0.0 |
|
|
108
113
|
| [#1192](https://github.com/porsager/postgres/issues/1192), [#1205](https://github.com/porsager/postgres/issues/1205), [#1086](https://github.com/porsager/postgres/issues/1086) | A failed or cancelled type query at startup rejects the query instead of crashing the process | 4.0.0 |
|
|
109
114
|
| [#1088](https://github.com/porsager/postgres/issues/1088) | Timers no longer fail on Cloudflare Workers | 4.0.0 |
|
|
115
|
+
| [#1227](https://github.com/porsager/postgres/issues/1227) | Assigning `err.stack` on a query error no longer throws on Node 22 and newer | 4.5.0 |
|
|
116
|
+
| [#1009](https://github.com/porsager/postgres/pull/1009) | The `camel` and `pascal` transforms no longer throw on a column name ending with an underscore | 4.5.0 |
|
|
110
117
|
|
|
111
118
|
**Reconnects and failover**
|
|
112
119
|
|
|
113
120
|
| Issue | What happens now | Since |
|
|
114
121
|
| --- | --- | --- |
|
|
115
122
|
| [#1179](https://github.com/porsager/postgres/issues/1179) | Failed connection attempts back off instead of the whole pool retrying at once | 4.0.0 |
|
|
123
|
+
| [#1250](https://github.com/porsager/postgres/issues/1250) | A connection closed with a statement still buffered reconnects, instead of failing every later statement with `CONNECT_TIMEOUT` | 4.0.0 |
|
|
116
124
|
| [#1174](https://github.com/porsager/postgres/issues/1174), [#815](https://github.com/porsager/postgres/issues/815), [#988](https://github.com/porsager/postgres/issues/988) | With several hosts, retries move to the next host, a connect timeout fails over, and `prefer-standby` falls back to a primary | 4.0.0 |
|
|
117
125
|
|
|
118
126
|
**Transactions, notices and subscriptions**
|
|
119
127
|
|
|
120
128
|
| Issue | What happens now | Since |
|
|
121
129
|
| --- | --- | --- |
|
|
122
|
-
| [#823](https://github.com/porsager/postgres/issues/823), [#1189](https://github.com/porsager/postgres/issues/1189), [#1210](https://github.com/porsager/postgres/issues/1210) | `sql.begin()` no longer fails with `UNSAFE_TRANSACTION` under write backpressure or with `max_pipeline: 0` | 4.0.0 |
|
|
130
|
+
| [#823](https://github.com/porsager/postgres/issues/823), [#1189](https://github.com/porsager/postgres/issues/1189), [#1210](https://github.com/porsager/postgres/issues/1210) | `sql.begin()` no longer fails with `UNSAFE_TRANSACTION` or sends a second `BEGIN` on a busy connection when transactions and queries run concurrently, at the pipeline limit, under write backpressure or with `max_pipeline: 0` | 4.0.0 |
|
|
123
131
|
| [#1063](https://github.com/porsager/postgres/issues/1063) | `onnotice: false` silences notices | 4.4.0 |
|
|
124
132
|
| [#1087](https://github.com/porsager/postgres/issues/1087) | `sql.subscribe()` no longer adds a listener on each subscribe or loses changes after a transform throws | 4.4.0 |
|
|
125
133
|
|
|
134
|
+
**TypeScript**
|
|
135
|
+
|
|
136
|
+
| Issue | What happens now | Since |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| [#1158](https://github.com/porsager/postgres/issues/1158), [#1177](https://github.com/porsager/postgres/pull/1177) | `sslnegotiation` is in the option types | 4.5.0 |
|
|
139
|
+
| [#968](https://github.com/porsager/postgres/pull/968) | `socket` is in the option types | 4.5.0 |
|
|
140
|
+
| [#703](https://github.com/porsager/postgres/issues/703), [#1194](https://github.com/porsager/postgres/pull/1194) | `new PostgresError('message')` sets the message, as with `Error`, and the types declare both the message and the fields forms | 4.5.0 |
|
|
141
|
+
|
|
126
142
|
**Feature requests**
|
|
127
143
|
|
|
128
144
|
| Issue | What you get | Since |
|
|
129
145
|
| --- | --- | --- |
|
|
130
146
|
| [#443](https://github.com/porsager/postgres/issues/443), [#908](https://github.com/porsager/postgres/issues/908) | `sql.stats()` reports connections in use and idle, and queries waiting | 4.3.0 |
|
|
131
147
|
| [#1100](https://github.com/porsager/postgres/issues/1100) | An `onidle` callback and the `@steadybrew/postgresjs/vercel` adapter | 4.2.0 |
|
|
132
|
-
| [#1164](https://github.com/porsager/postgres/issues/1164) | Built-in array types are decoded with `fetch_types: false` | 4.0.0 |
|
|
148
|
+
| [#1164](https://github.com/porsager/postgres/issues/1164) | Built-in array types are decoded, and plain JavaScript arrays are sent, with `fetch_types: false` | 4.0.0 |
|
|
149
|
+
| [#886](https://github.com/porsager/postgres/issues/886), [#1094](https://github.com/porsager/postgres/issues/1094) | `await using` ends a pool and releases a reserved connection when it goes out of scope | 4.5.0 |
|
|
150
|
+
| [#1225](https://github.com/porsager/postgres/issues/1225) | Queries with values in one round trip instead of two, opt-in with `parameter_types: 'client'` | 4.5.0 |
|
|
151
|
+
|
|
152
|
+
Bun isn't tested here, so Bun-only reports such as [#1066](https://github.com/porsager/postgres/issues/1066) aren't listed; the same crash on Node is [#1208](https://github.com/porsager/postgres/issues/1208).
|
|
133
153
|
|
|
134
154
|
</details>
|
|
135
155
|
|
|
@@ -693,11 +713,13 @@ A `COPY` holds its connection until it finishes. Postgres.js cannot tell that a
|
|
|
693
713
|
Postgres.js supports, [canceling queries in progress](https://www.postgresql.org/docs/7.1/protocol-protocol.html#AEN39000). It works by opening a new connection with a protocol level startup message to cancel the current query running on a specific connection. That means there is no guarantee that the query will be canceled, and due to the possible race conditions it might even result in canceling another query. This is fine for long running queries, but in the case of high load and fast queries it might be better to simply ignore results instead of canceling.
|
|
694
714
|
|
|
695
715
|
```js
|
|
696
|
-
const query = sql`select pg_sleep
|
|
716
|
+
const query = sql`select pg_sleep(100)`.execute()
|
|
697
717
|
setTimeout(() => query.cancel(), 100)
|
|
698
718
|
const result = await query
|
|
699
719
|
```
|
|
700
720
|
|
|
721
|
+
A cancelled query rejects with code `57014`. A cancel can arrive too late, in which case the query may still complete, or fail with its own error. `query.cancel()` resolves once the cancel request has been sent. A cancelled query you don't await does not cause an unhandled rejection.
|
|
722
|
+
|
|
701
723
|
### Execute
|
|
702
724
|
|
|
703
725
|
#### ```await sql``.execute()```
|
|
@@ -1244,8 +1266,8 @@ const sql = postgres('postgres://username:password@host:port/database', {
|
|
|
1244
1266
|
application_name : 'postgres.js', // Default application_name
|
|
1245
1267
|
... // Other connection parameters, see https://www.postgresql.org/docs/current/runtime-config-client.html
|
|
1246
1268
|
},
|
|
1247
|
-
target_session_attrs : null, //
|
|
1248
|
-
//
|
|
1269
|
+
target_session_attrs : null, // Which kind of server to connect to, see
|
|
1270
|
+
// Multi-host connections below
|
|
1249
1271
|
fetch_types : true, // Automatically fetches types on connect
|
|
1250
1272
|
// on initial connection.
|
|
1251
1273
|
parameter_types : 'server', // 'client' or 'warn', see Parameter types (PGPARAMETER_TYPES)
|
|
@@ -1292,7 +1314,20 @@ Multiple connection strings can be passed to `postgres()` in the form of `postgr
|
|
|
1292
1314
|
|
|
1293
1315
|
Connections will be attempted in order of the specified hosts/ports. On a successful connection, all retries will be reset. This ensures that hosts can come up and down seamlessly.
|
|
1294
1316
|
|
|
1295
|
-
|
|
1317
|
+
`target_session_attrs` (or `PGTARGETSESSIONATTRS`) picks which kind of server to connect to. The values and their meaning are libpq's:
|
|
1318
|
+
|
|
1319
|
+
| Value | Connects to |
|
|
1320
|
+
|---|---|
|
|
1321
|
+
| `null` (default) | any server |
|
|
1322
|
+
| `'read-write'` | a server that accepts writes: not a hot standby, and `default_transaction_read_only` off |
|
|
1323
|
+
| `'primary'` | a server that is not a hot standby, even if `default_transaction_read_only` is on |
|
|
1324
|
+
| `'read-only'` | a hot standby, or a server with `default_transaction_read_only` on |
|
|
1325
|
+
| `'standby'` | a hot standby |
|
|
1326
|
+
| `'prefer-standby'` | a hot standby if any host is one, otherwise any server |
|
|
1327
|
+
|
|
1328
|
+
During a failover or an upgrade, `'read-write'` and `'primary'` wait for the promoted server instead of connecting to a standby whose writes would fail. Read traffic that should keep using standbys during the switch belongs on `'prefer-standby'` or no `target_session_attrs` at all.
|
|
1329
|
+
|
|
1330
|
+
With several hosts, a host that answers but is the wrong kind is skipped and the next one is tried at once; if every host answers and none matches, the connection fails immediately with `TARGET_SESSION_ATTRS`. With a single host, or when some host fails to connect, it keeps retrying, bounded by `connect_timeout` per host, because a failover may be in progress.
|
|
1296
1331
|
|
|
1297
1332
|
### The Connection Pool
|
|
1298
1333
|
|
package/cf/src/connection.js
CHANGED
|
@@ -106,6 +106,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
106
106
|
, hostIndex = 0
|
|
107
107
|
, length = 0
|
|
108
108
|
, rows = 0
|
|
109
|
+
, copyPaused = false
|
|
109
110
|
, serverSignature = null
|
|
110
111
|
, nextWriteTimer = null
|
|
111
112
|
, incomings = null
|
|
@@ -464,6 +465,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
464
465
|
query = results = errorResponse = chunk = nextWriteTimer = barrier = null
|
|
465
466
|
result = new Result()
|
|
466
467
|
rows = 0
|
|
468
|
+
copyPaused = false
|
|
467
469
|
nonce = serverSignature = null
|
|
468
470
|
}
|
|
469
471
|
|
|
@@ -686,6 +688,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
686
688
|
|
|
687
689
|
function errored(err) {
|
|
688
690
|
stream && (stream.destroy(err), stream = null)
|
|
691
|
+
resumeCopy()
|
|
689
692
|
query && queryError(query, err)
|
|
690
693
|
}
|
|
691
694
|
|
|
@@ -802,7 +805,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
802
805
|
query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
|
|
803
806
|
query.retried
|
|
804
807
|
? errored(query.retried)
|
|
805
|
-
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
|
|
808
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier && !query.cancelled
|
|
806
809
|
&& (x[5] !== 69 || endsAbortedState.test(query.string))
|
|
807
810
|
? retry(query, errorResponse)
|
|
808
811
|
: errored(errorResponse)
|
|
@@ -816,6 +819,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
816
819
|
|
|
817
820
|
query = results = errorResponse = null
|
|
818
821
|
result = new Result()
|
|
822
|
+
rows = 0
|
|
819
823
|
|
|
820
824
|
dispatch('ready')
|
|
821
825
|
}
|
|
@@ -1048,8 +1052,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1048
1052
|
|
|
1049
1053
|
function mismatchReason(x, xs) {
|
|
1050
1054
|
return (
|
|
1055
|
+
(x === 'read-write' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1051
1056
|
(x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
|
|
1052
|
-
(x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1057
|
+
(x === 'read-only' && xs.in_hot_standby === 'off' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1053
1058
|
(x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1054
1059
|
(x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
|
|
1055
1060
|
(x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
|
|
@@ -1148,7 +1153,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1148
1153
|
|
|
1149
1154
|
function CopyOutResponse() {
|
|
1150
1155
|
stream = new Stream.Readable({
|
|
1151
|
-
read() { socket.resume() }
|
|
1156
|
+
read() { copyPaused = false; socket.resume() }
|
|
1152
1157
|
})
|
|
1153
1158
|
query.resolve(stream)
|
|
1154
1159
|
connection.owner && connection.owner.next()
|
|
@@ -1158,7 +1163,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1158
1163
|
function CopyBothResponse() {
|
|
1159
1164
|
stream = new Stream.Duplex({
|
|
1160
1165
|
autoDestroy: true,
|
|
1161
|
-
read() { socket.resume() },
|
|
1166
|
+
read() { copyPaused = false; socket.resume() },
|
|
1162
1167
|
/* c8 ignore next 11 */
|
|
1163
1168
|
write(chunk, encoding, callback) {
|
|
1164
1169
|
socket.write(b().d().raw(chunk).end(), callback)
|
|
@@ -1178,12 +1183,23 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1178
1183
|
}
|
|
1179
1184
|
|
|
1180
1185
|
function CopyData(x) {
|
|
1181
|
-
stream &&
|
|
1186
|
+
if (stream && !stream.push(x.subarray(5))) {
|
|
1187
|
+
copyPaused = true
|
|
1188
|
+
socket.pause()
|
|
1189
|
+
}
|
|
1182
1190
|
}
|
|
1183
1191
|
|
|
1184
1192
|
function CopyDone() {
|
|
1185
1193
|
stream && stream.push(null)
|
|
1186
1194
|
stream = null
|
|
1195
|
+
resumeCopy()
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function resumeCopy() {
|
|
1199
|
+
if (copyPaused) {
|
|
1200
|
+
copyPaused = false
|
|
1201
|
+
socket.resume()
|
|
1202
|
+
}
|
|
1187
1203
|
}
|
|
1188
1204
|
|
|
1189
1205
|
function NoticeResponse(x) {
|
package/cf/src/errors.js
CHANGED
package/cf/src/index.js
CHANGED
|
@@ -703,8 +703,8 @@ function parseOptions(a, b) {
|
|
|
703
703
|
...Object.entries(defaults).reduce(
|
|
704
704
|
(acc, [k, d]) => {
|
|
705
705
|
const value = k in o ? o[k] : k in query
|
|
706
|
-
? (query[k]
|
|
707
|
-
: env['PG' + k.toUpperCase()]
|
|
706
|
+
? off(query[k])
|
|
707
|
+
: env['PG' + k.toUpperCase()] ? off(env['PG' + k.toUpperCase()]) : d
|
|
708
708
|
acc[k] = typeof value === 'string' && ints.includes(k)
|
|
709
709
|
? +value
|
|
710
710
|
: value
|
|
@@ -737,6 +737,10 @@ function parseOptions(a, b) {
|
|
|
737
737
|
return options
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
+
function off(x) {
|
|
741
|
+
return x === 'disable' || x === 'false' ? false : x
|
|
742
|
+
}
|
|
743
|
+
|
|
740
744
|
function tsa(o, url, env) {
|
|
741
745
|
const x = o.target_session_attrs || url.searchParams.get('target_session_attrs') || env.PGTARGETSESSIONATTRS
|
|
742
746
|
if (!x || ['read-write', 'read-only', 'primary', 'standby', 'prefer-standby'].includes(x))
|
package/cf/src/query.js
CHANGED
|
@@ -55,6 +55,7 @@ export class Query extends Promise {
|
|
|
55
55
|
if (!this.canceller || this.settled)
|
|
56
56
|
return Promise.resolve()
|
|
57
57
|
|
|
58
|
+
Promise.prototype.then.call(this, undefined, () => undefined)
|
|
58
59
|
const cancelled = this.canceller(this)
|
|
59
60
|
this.canceller = null
|
|
60
61
|
cancelled.catch(() => undefined)
|
package/cf/src/types.js
CHANGED
|
@@ -347,14 +347,14 @@ function arrayParserLoop(s, x, parser, typarray) {
|
|
|
347
347
|
export const toCamel = x => {
|
|
348
348
|
let str = x[0]
|
|
349
349
|
for (let i = 1; i < x.length; i++)
|
|
350
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
350
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
351
351
|
return str
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
export const toPascal = x => {
|
|
355
355
|
let str = x[0].toUpperCase()
|
|
356
356
|
for (let i = 1; i < x.length; i++)
|
|
357
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
357
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
358
358
|
return str
|
|
359
359
|
}
|
|
360
360
|
|
package/cjs/src/connection.js
CHANGED
|
@@ -104,6 +104,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
104
104
|
, hostIndex = 0
|
|
105
105
|
, length = 0
|
|
106
106
|
, rows = 0
|
|
107
|
+
, copyPaused = false
|
|
107
108
|
, serverSignature = null
|
|
108
109
|
, nextWriteTimer = null
|
|
109
110
|
, incomings = null
|
|
@@ -462,6 +463,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
462
463
|
query = results = errorResponse = chunk = nextWriteTimer = barrier = null
|
|
463
464
|
result = new Result()
|
|
464
465
|
rows = 0
|
|
466
|
+
copyPaused = false
|
|
465
467
|
nonce = serverSignature = null
|
|
466
468
|
}
|
|
467
469
|
|
|
@@ -684,6 +686,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
684
686
|
|
|
685
687
|
function errored(err) {
|
|
686
688
|
stream && (stream.destroy(err), stream = null)
|
|
689
|
+
resumeCopy()
|
|
687
690
|
query && queryError(query, err)
|
|
688
691
|
}
|
|
689
692
|
|
|
@@ -800,7 +803,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
800
803
|
query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
|
|
801
804
|
query.retried
|
|
802
805
|
? errored(query.retried)
|
|
803
|
-
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
|
|
806
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier && !query.cancelled
|
|
804
807
|
&& (x[5] !== 69 || endsAbortedState.test(query.string))
|
|
805
808
|
? retry(query, errorResponse)
|
|
806
809
|
: errored(errorResponse)
|
|
@@ -814,6 +817,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
814
817
|
|
|
815
818
|
query = results = errorResponse = null
|
|
816
819
|
result = new Result()
|
|
820
|
+
rows = 0
|
|
817
821
|
|
|
818
822
|
dispatch('ready')
|
|
819
823
|
}
|
|
@@ -1046,8 +1050,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1046
1050
|
|
|
1047
1051
|
function mismatchReason(x, xs) {
|
|
1048
1052
|
return (
|
|
1053
|
+
(x === 'read-write' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1049
1054
|
(x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
|
|
1050
|
-
(x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1055
|
+
(x === 'read-only' && xs.in_hot_standby === 'off' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1051
1056
|
(x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1052
1057
|
(x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
|
|
1053
1058
|
(x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
|
|
@@ -1146,7 +1151,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1146
1151
|
|
|
1147
1152
|
function CopyOutResponse() {
|
|
1148
1153
|
stream = new Stream.Readable({
|
|
1149
|
-
read() { socket.resume() }
|
|
1154
|
+
read() { copyPaused = false; socket.resume() }
|
|
1150
1155
|
})
|
|
1151
1156
|
query.resolve(stream)
|
|
1152
1157
|
connection.owner && connection.owner.next()
|
|
@@ -1156,7 +1161,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1156
1161
|
function CopyBothResponse() {
|
|
1157
1162
|
stream = new Stream.Duplex({
|
|
1158
1163
|
autoDestroy: true,
|
|
1159
|
-
read() { socket.resume() },
|
|
1164
|
+
read() { copyPaused = false; socket.resume() },
|
|
1160
1165
|
/* c8 ignore next 11 */
|
|
1161
1166
|
write(chunk, encoding, callback) {
|
|
1162
1167
|
socket.write(b().d().raw(chunk).end(), callback)
|
|
@@ -1176,12 +1181,23 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1176
1181
|
}
|
|
1177
1182
|
|
|
1178
1183
|
function CopyData(x) {
|
|
1179
|
-
stream &&
|
|
1184
|
+
if (stream && !stream.push(x.subarray(5))) {
|
|
1185
|
+
copyPaused = true
|
|
1186
|
+
socket.pause()
|
|
1187
|
+
}
|
|
1180
1188
|
}
|
|
1181
1189
|
|
|
1182
1190
|
function CopyDone() {
|
|
1183
1191
|
stream && stream.push(null)
|
|
1184
1192
|
stream = null
|
|
1193
|
+
resumeCopy()
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function resumeCopy() {
|
|
1197
|
+
if (copyPaused) {
|
|
1198
|
+
copyPaused = false
|
|
1199
|
+
socket.resume()
|
|
1200
|
+
}
|
|
1185
1201
|
}
|
|
1186
1202
|
|
|
1187
1203
|
function NoticeResponse(x) {
|
package/cjs/src/errors.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const PostgresError = module.exports.PostgresError = class PostgresError extends Error {
|
|
2
2
|
constructor(x) {
|
|
3
|
-
super(x.message)
|
|
3
|
+
super(typeof x === 'string' ? x : x.message)
|
|
4
4
|
this.name = this.constructor.name
|
|
5
|
-
Object.assign(this, x)
|
|
5
|
+
typeof x === 'string' || Object.assign(this, x)
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|
package/cjs/src/index.js
CHANGED
|
@@ -702,8 +702,8 @@ function parseOptions(a, b) {
|
|
|
702
702
|
...Object.entries(defaults).reduce(
|
|
703
703
|
(acc, [k, d]) => {
|
|
704
704
|
const value = k in o ? o[k] : k in query
|
|
705
|
-
? (query[k]
|
|
706
|
-
: env['PG' + k.toUpperCase()]
|
|
705
|
+
? off(query[k])
|
|
706
|
+
: env['PG' + k.toUpperCase()] ? off(env['PG' + k.toUpperCase()]) : d
|
|
707
707
|
acc[k] = typeof value === 'string' && ints.includes(k)
|
|
708
708
|
? +value
|
|
709
709
|
: value
|
|
@@ -736,6 +736,10 @@ function parseOptions(a, b) {
|
|
|
736
736
|
return options
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
+
function off(x) {
|
|
740
|
+
return x === 'disable' || x === 'false' ? false : x
|
|
741
|
+
}
|
|
742
|
+
|
|
739
743
|
function tsa(o, url, env) {
|
|
740
744
|
const x = o.target_session_attrs || url.searchParams.get('target_session_attrs') || env.PGTARGETSESSIONATTRS
|
|
741
745
|
if (!x || ['read-write', 'read-only', 'primary', 'standby', 'prefer-standby'].includes(x))
|
package/cjs/src/query.js
CHANGED
|
@@ -55,6 +55,7 @@ const Query = module.exports.Query = class Query extends Promise {
|
|
|
55
55
|
if (!this.canceller || this.settled)
|
|
56
56
|
return Promise.resolve()
|
|
57
57
|
|
|
58
|
+
Promise.prototype.then.call(this, undefined, () => undefined)
|
|
58
59
|
const cancelled = this.canceller(this)
|
|
59
60
|
this.canceller = null
|
|
60
61
|
cancelled.catch(() => undefined)
|
package/cjs/src/types.js
CHANGED
|
@@ -346,14 +346,14 @@ function arrayParserLoop(s, x, parser, typarray) {
|
|
|
346
346
|
const toCamel = module.exports.toCamel = x => {
|
|
347
347
|
let str = x[0]
|
|
348
348
|
for (let i = 1; i < x.length; i++)
|
|
349
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
349
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
350
350
|
return str
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
const toPascal = module.exports.toPascal = x => {
|
|
354
354
|
let str = x[0].toUpperCase()
|
|
355
355
|
for (let i = 1; i < x.length; i++)
|
|
356
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
356
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
357
357
|
return str
|
|
358
358
|
}
|
|
359
359
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steadybrew/postgresjs",
|
|
3
|
-
"version": "4.5.0
|
|
3
|
+
"version": "4.5.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",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"build:deno": "node transpile.deno.js",
|
|
33
33
|
"build:cf": "node transpile.cf.js",
|
|
34
34
|
"test": "npm run test:startup && npm run test:esm && npm run test:cjs && npm run test:deno",
|
|
35
|
-
"test:esm": "node tests-startup/run.js --integration && node tests/index.js",
|
|
35
|
+
"test:esm": "node tests-startup/run.js --integration && node tests/index.js && node tests-startup/run.js --reports",
|
|
36
36
|
"test:startup": "node tests-startup/run.js",
|
|
37
37
|
"test:tracing": "node tests-tracing/run.js",
|
|
38
|
-
"test:cjs": "npm run build:cjs && node tests-startup/run.js --integration --cjs && cd cjs/tests && node index.js && cd ../../",
|
|
38
|
+
"test:cjs": "npm run build:cjs && node tests-startup/run.js --integration --cjs && cd cjs/tests && node index.js && cd ../../ && node tests-startup/run.js --reports --cjs",
|
|
39
39
|
"test:deno": "npm run test:deno-socket && npm run build:deno && cd deno/tests && deno run --no-lock --allow-all --unsafely-ignore-certificate-errors index.js && cd ../../",
|
|
40
40
|
"lint": "eslint src && eslint tests && eslint tests-startup --ext .js,.mjs",
|
|
41
41
|
"prepare": "npm run build",
|
package/src/connection.js
CHANGED
|
@@ -104,6 +104,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
104
104
|
, hostIndex = 0
|
|
105
105
|
, length = 0
|
|
106
106
|
, rows = 0
|
|
107
|
+
, copyPaused = false
|
|
107
108
|
, serverSignature = null
|
|
108
109
|
, nextWriteTimer = null
|
|
109
110
|
, incomings = null
|
|
@@ -462,6 +463,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
462
463
|
query = results = errorResponse = chunk = nextWriteTimer = barrier = null
|
|
463
464
|
result = new Result()
|
|
464
465
|
rows = 0
|
|
466
|
+
copyPaused = false
|
|
465
467
|
nonce = serverSignature = null
|
|
466
468
|
}
|
|
467
469
|
|
|
@@ -684,6 +686,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
684
686
|
|
|
685
687
|
function errored(err) {
|
|
686
688
|
stream && (stream.destroy(err), stream = null)
|
|
689
|
+
resumeCopy()
|
|
687
690
|
query && queryError(query, err)
|
|
688
691
|
}
|
|
689
692
|
|
|
@@ -800,7 +803,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
800
803
|
query.prepared && retryRoutines.has(errorResponse.routine) && delete statements[query.signature]
|
|
801
804
|
query.retried
|
|
802
805
|
? errored(query.retried)
|
|
803
|
-
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier
|
|
806
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner && !barrier && !query.cancelled
|
|
804
807
|
&& (x[5] !== 69 || endsAbortedState.test(query.string))
|
|
805
808
|
? retry(query, errorResponse)
|
|
806
809
|
: errored(errorResponse)
|
|
@@ -814,6 +817,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
814
817
|
|
|
815
818
|
query = results = errorResponse = null
|
|
816
819
|
result = new Result()
|
|
820
|
+
rows = 0
|
|
817
821
|
|
|
818
822
|
dispatch('ready')
|
|
819
823
|
}
|
|
@@ -1046,8 +1050,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1046
1050
|
|
|
1047
1051
|
function mismatchReason(x, xs) {
|
|
1048
1052
|
return (
|
|
1053
|
+
(x === 'read-write' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1049
1054
|
(x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
|
|
1050
|
-
(x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1055
|
+
(x === 'read-only' && xs.in_hot_standby === 'off' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1051
1056
|
(x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1052
1057
|
(x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
|
|
1053
1058
|
(x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
|
|
@@ -1146,7 +1151,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1146
1151
|
|
|
1147
1152
|
function CopyOutResponse() {
|
|
1148
1153
|
stream = new Stream.Readable({
|
|
1149
|
-
read() { socket.resume() }
|
|
1154
|
+
read() { copyPaused = false; socket.resume() }
|
|
1150
1155
|
})
|
|
1151
1156
|
query.resolve(stream)
|
|
1152
1157
|
connection.owner && connection.owner.next()
|
|
@@ -1156,7 +1161,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1156
1161
|
function CopyBothResponse() {
|
|
1157
1162
|
stream = new Stream.Duplex({
|
|
1158
1163
|
autoDestroy: true,
|
|
1159
|
-
read() { socket.resume() },
|
|
1164
|
+
read() { copyPaused = false; socket.resume() },
|
|
1160
1165
|
/* c8 ignore next 11 */
|
|
1161
1166
|
write(chunk, encoding, callback) {
|
|
1162
1167
|
socket.write(b().d().raw(chunk).end(), callback)
|
|
@@ -1176,12 +1181,23 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1176
1181
|
}
|
|
1177
1182
|
|
|
1178
1183
|
function CopyData(x) {
|
|
1179
|
-
stream &&
|
|
1184
|
+
if (stream && !stream.push(x.subarray(5))) {
|
|
1185
|
+
copyPaused = true
|
|
1186
|
+
socket.pause()
|
|
1187
|
+
}
|
|
1180
1188
|
}
|
|
1181
1189
|
|
|
1182
1190
|
function CopyDone() {
|
|
1183
1191
|
stream && stream.push(null)
|
|
1184
1192
|
stream = null
|
|
1193
|
+
resumeCopy()
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function resumeCopy() {
|
|
1197
|
+
if (copyPaused) {
|
|
1198
|
+
copyPaused = false
|
|
1199
|
+
socket.resume()
|
|
1200
|
+
}
|
|
1185
1201
|
}
|
|
1186
1202
|
|
|
1187
1203
|
function NoticeResponse(x) {
|
package/src/errors.js
CHANGED
package/src/index.js
CHANGED
|
@@ -702,8 +702,8 @@ function parseOptions(a, b) {
|
|
|
702
702
|
...Object.entries(defaults).reduce(
|
|
703
703
|
(acc, [k, d]) => {
|
|
704
704
|
const value = k in o ? o[k] : k in query
|
|
705
|
-
? (query[k]
|
|
706
|
-
: env['PG' + k.toUpperCase()]
|
|
705
|
+
? off(query[k])
|
|
706
|
+
: env['PG' + k.toUpperCase()] ? off(env['PG' + k.toUpperCase()]) : d
|
|
707
707
|
acc[k] = typeof value === 'string' && ints.includes(k)
|
|
708
708
|
? +value
|
|
709
709
|
: value
|
|
@@ -736,6 +736,10 @@ function parseOptions(a, b) {
|
|
|
736
736
|
return options
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
+
function off(x) {
|
|
740
|
+
return x === 'disable' || x === 'false' ? false : x
|
|
741
|
+
}
|
|
742
|
+
|
|
739
743
|
function tsa(o, url, env) {
|
|
740
744
|
const x = o.target_session_attrs || url.searchParams.get('target_session_attrs') || env.PGTARGETSESSIONATTRS
|
|
741
745
|
if (!x || ['read-write', 'read-only', 'primary', 'standby', 'prefer-standby'].includes(x))
|
package/src/query.js
CHANGED
|
@@ -55,6 +55,7 @@ export class Query extends Promise {
|
|
|
55
55
|
if (!this.canceller || this.settled)
|
|
56
56
|
return Promise.resolve()
|
|
57
57
|
|
|
58
|
+
Promise.prototype.then.call(this, undefined, () => undefined)
|
|
58
59
|
const cancelled = this.canceller(this)
|
|
59
60
|
this.canceller = null
|
|
60
61
|
cancelled.catch(() => undefined)
|
package/src/types.js
CHANGED
|
@@ -346,14 +346,14 @@ function arrayParserLoop(s, x, parser, typarray) {
|
|
|
346
346
|
export const toCamel = x => {
|
|
347
347
|
let str = x[0]
|
|
348
348
|
for (let i = 1; i < x.length; i++)
|
|
349
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
349
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
350
350
|
return str
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
export const toPascal = x => {
|
|
354
354
|
let str = x[0].toUpperCase()
|
|
355
355
|
for (let i = 1; i < x.length; i++)
|
|
356
|
-
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
|
|
356
|
+
str += x[i] === '_' && i + 1 < x.length ? x[++i].toUpperCase() : x[i]
|
|
357
357
|
return str
|
|
358
358
|
}
|
|
359
359
|
|
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Readable, Writable } from 'node:stream'
|
|
1
|
+
import { Duplex, Readable, Writable } from 'node:stream'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Establish a connection to a PostgreSQL server.
|
|
@@ -45,6 +45,8 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
|
|
|
45
45
|
* @default false
|
|
46
46
|
*/
|
|
47
47
|
ssl: 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object;
|
|
48
|
+
/** Set to 'direct' to start TLS immediately with ALPN instead of sending an SSLRequest first */
|
|
49
|
+
sslnegotiation: 'direct' | 'postgres' | null;
|
|
48
50
|
/**
|
|
49
51
|
* Max number of connections
|
|
50
52
|
* @default 10
|
|
@@ -126,6 +128,8 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
|
|
|
126
128
|
backoff: boolean | ((attemptNum: number) => number);
|
|
127
129
|
max_lifetime: number | null;
|
|
128
130
|
keep_alive: number | null;
|
|
131
|
+
/** Function, sync or async, returning the socket-like duplex stream to connect through; it receives the parsed options */
|
|
132
|
+
socket?: (options: postgres.ParsedOptions) => Duplex | Promise<Duplex>;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
|
|
@@ -203,6 +207,8 @@ type Return<T, K extends readonly any[]> =
|
|
|
203
207
|
|
|
204
208
|
declare namespace postgres {
|
|
205
209
|
class PostgresError extends Error {
|
|
210
|
+
constructor(message: string);
|
|
211
|
+
constructor(fields: Partial<Omit<PostgresError, 'name' | 'stack' | 'cause'>>);
|
|
206
212
|
name: 'PostgresError';
|
|
207
213
|
severity_local: string;
|
|
208
214
|
severity: string;
|