@coffre/db 0.14.4 → 0.15.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.
Files changed (218) hide show
  1. package/README.md +2 -1
  2. package/dist/change-records-_fKvahvs.js +55 -0
  3. package/dist/connect.d.ts +22 -10
  4. package/dist/connect.js +74 -12
  5. package/dist/database-CVteLhp1.d.ts +410 -0
  6. package/dist/{database-BadNeeN-.js → database-D55ENPX-.js} +46 -25
  7. package/dist/dialect.d.ts +4 -3
  8. package/dist/dialect.js +14 -5
  9. package/dist/grants.d.ts +4 -4
  10. package/dist/grants.js +4 -4
  11. package/dist/hyperdrive.d.ts +48 -23
  12. package/dist/hyperdrive.js +168 -91
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.js +1 -1
  15. package/dist/log-DaD7fyUq.d.ts +722 -0
  16. package/dist/log.d.ts +2 -596
  17. package/dist/log.js +224 -10
  18. package/dist/migrate.d.ts +43 -3
  19. package/dist/migrate.js +149 -29
  20. package/dist/migrations/postgres/0021_prune_vault_releases.sql +19 -0
  21. package/dist/migrations/postgres/0022_log_015.sql +672 -0
  22. package/dist/migrations/postgres/0023_change_path.sql +153 -0
  23. package/dist/migrations/postgres/0024_use_015.sql +556 -0
  24. package/dist/migrations/postgres/0025_app_change.sql +221 -0
  25. package/dist/migrations/postgres/0026_issue_lock.sql +106 -0
  26. package/dist/migrations/postgres/0027_retention.sql +118 -0
  27. package/dist/migrations/postgres/0028_use_stalls.sql +594 -0
  28. package/dist/migrations/postgres/0029_use_objects.sql +887 -0
  29. package/dist/migrations/postgres/0030_unarchive_restores.sql +259 -0
  30. package/dist/migrations/postgres/0031_app_read.sql +65 -0
  31. package/dist/migrations/postgres/0032_secrets_live_index.sql +4 -0
  32. package/dist/migrations/postgres/0033_use_generations.sql +582 -0
  33. package/dist/migrations/postgres/0034_vault_credentials.sql +153 -0
  34. package/dist/migrations/postgres/0035_first_days.sql +3 -0
  35. package/dist/migrations/postgres/0036_vault_issues.sql +91 -0
  36. package/dist/migrations/postgres/0037_vault_bindings.sql +271 -0
  37. package/dist/migrations/postgres/0040_binding_origin.sql +159 -0
  38. package/dist/migrations/postgres/0041_cutover.sql +132 -0
  39. package/dist/migrations/postgres/0042_sign_in_index.sql +7 -0
  40. package/dist/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  41. package/dist/migrations/postgres/0044_use_by_name.sql +705 -0
  42. package/dist/migrations/postgres/0045_decide_first.sql +878 -0
  43. package/dist/migrations/postgres/0046_way_in_uses.sql +1149 -0
  44. package/dist/migrations/postgres/0047_cutover_finish.sql +268 -0
  45. package/dist/migrations/postgres/0048_use_batch.sql +59 -0
  46. package/dist/migrations/postgres/0049_reads.sql +171 -0
  47. package/dist/migrations/postgres/meta/0021_snapshot.json +3334 -0
  48. package/dist/migrations/postgres/meta/0022_snapshot.json +3831 -0
  49. package/dist/migrations/postgres/meta/0023_snapshot.json +3831 -0
  50. package/dist/migrations/postgres/meta/0024_snapshot.json +3831 -0
  51. package/dist/migrations/postgres/meta/0025_snapshot.json +3831 -0
  52. package/dist/migrations/postgres/meta/0026_snapshot.json +3831 -0
  53. package/dist/migrations/postgres/meta/0027_snapshot.json +3853 -0
  54. package/dist/migrations/postgres/meta/0028_snapshot.json +3853 -0
  55. package/dist/migrations/postgres/meta/0029_snapshot.json +3859 -0
  56. package/dist/migrations/postgres/meta/0030_snapshot.json +3859 -0
  57. package/dist/migrations/postgres/meta/0031_snapshot.json +3859 -0
  58. package/dist/migrations/postgres/meta/0032_snapshot.json +3875 -0
  59. package/dist/migrations/postgres/meta/0033_snapshot.json +3875 -0
  60. package/dist/migrations/postgres/meta/0034_snapshot.json +3875 -0
  61. package/dist/migrations/postgres/meta/0035_snapshot.json +3875 -0
  62. package/dist/migrations/postgres/meta/0036_snapshot.json +3875 -0
  63. package/dist/migrations/postgres/meta/0037_snapshot.json +3875 -0
  64. package/dist/migrations/postgres/meta/0038_snapshot.json +3881 -0
  65. package/dist/migrations/postgres/meta/0039_snapshot.json +3881 -0
  66. package/dist/migrations/postgres/meta/0040_snapshot.json +3931 -0
  67. package/dist/migrations/postgres/meta/0041_snapshot.json +3981 -0
  68. package/dist/migrations/postgres/meta/0042_snapshot.json +3981 -0
  69. package/dist/migrations/postgres/meta/0043_snapshot.json +3981 -0
  70. package/dist/migrations/postgres/meta/0044_snapshot.json +3991 -0
  71. package/dist/migrations/postgres/meta/0045_snapshot.json +3997 -0
  72. package/dist/migrations/postgres/meta/0046_snapshot.json +3997 -0
  73. package/dist/migrations/postgres/meta/0047_snapshot.json +4234 -0
  74. package/dist/migrations/postgres/meta/_journal.json +189 -0
  75. package/dist/migrations/sqlite/0018_log_015.sql +84 -0
  76. package/dist/migrations/sqlite/0019_retention.sql +2 -0
  77. package/dist/migrations/sqlite/0020_ended_references.sql +1 -0
  78. package/dist/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  79. package/dist/migrations/sqlite/0022_binding_origin.sql +1 -0
  80. package/dist/migrations/sqlite/0023_sign_in_index.sql +2 -0
  81. package/dist/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  82. package/dist/migrations/sqlite/0025_way_in_uses.sql +42 -0
  83. package/dist/migrations/sqlite/0026_cutover_finish.sql +11 -0
  84. package/dist/migrations/sqlite/0027_reads.sql +38 -0
  85. package/dist/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  86. package/dist/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  87. package/dist/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  88. package/dist/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  89. package/dist/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  90. package/dist/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  91. package/dist/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  92. package/dist/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  93. package/dist/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  94. package/dist/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  95. package/dist/migrations/sqlite/meta/_journal.json +70 -0
  96. package/dist/platform-Y3gICK6n.d.ts +58 -0
  97. package/dist/portable.d.ts +2 -2
  98. package/dist/postgres-BI3xTnyw.js +1718 -0
  99. package/dist/postgres-platform-node.d.ts +14 -0
  100. package/dist/postgres-platform-node.js +21 -0
  101. package/dist/postgres-platform-workerd.d.ts +61 -0
  102. package/dist/postgres-platform-workerd.js +163 -0
  103. package/dist/{schema-z8GcN_Xu.d.ts → schema-DgoPqqsI.d.ts} +1329 -9
  104. package/dist/schema-sqlite.d.ts +1308 -8
  105. package/dist/schema-sqlite.js +156 -20
  106. package/dist/schema-version.d.ts +1 -1
  107. package/dist/schema-version.js +261 -2
  108. package/dist/schema.d.ts +2 -2
  109. package/dist/schema.js +229 -22
  110. package/package.json +13 -5
  111. package/src/active-subject.ts +9 -0
  112. package/src/change-records.ts +38 -0
  113. package/src/connect.ts +67 -13
  114. package/src/database.ts +67 -51
  115. package/src/dialect.ts +17 -8
  116. package/src/grants.ts +3 -3
  117. package/src/hyperdrive.ts +181 -104
  118. package/src/log.ts +244 -11
  119. package/src/migrate.ts +157 -30
  120. package/src/migrations/postgres/0021_prune_vault_releases.sql +19 -0
  121. package/src/migrations/postgres/0022_log_015.sql +672 -0
  122. package/src/migrations/postgres/0023_change_path.sql +153 -0
  123. package/src/migrations/postgres/0024_use_015.sql +556 -0
  124. package/src/migrations/postgres/0025_app_change.sql +221 -0
  125. package/src/migrations/postgres/0026_issue_lock.sql +106 -0
  126. package/src/migrations/postgres/0027_retention.sql +118 -0
  127. package/src/migrations/postgres/0028_use_stalls.sql +594 -0
  128. package/src/migrations/postgres/0029_use_objects.sql +887 -0
  129. package/src/migrations/postgres/0030_unarchive_restores.sql +259 -0
  130. package/src/migrations/postgres/0031_app_read.sql +65 -0
  131. package/src/migrations/postgres/0032_secrets_live_index.sql +4 -0
  132. package/src/migrations/postgres/0033_use_generations.sql +582 -0
  133. package/src/migrations/postgres/0034_vault_credentials.sql +153 -0
  134. package/src/migrations/postgres/0035_first_days.sql +3 -0
  135. package/src/migrations/postgres/0036_vault_issues.sql +91 -0
  136. package/src/migrations/postgres/0037_vault_bindings.sql +271 -0
  137. package/src/migrations/postgres/0040_binding_origin.sql +159 -0
  138. package/src/migrations/postgres/0041_cutover.sql +132 -0
  139. package/src/migrations/postgres/0042_sign_in_index.sql +7 -0
  140. package/src/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  141. package/src/migrations/postgres/0044_use_by_name.sql +705 -0
  142. package/src/migrations/postgres/0045_decide_first.sql +878 -0
  143. package/src/migrations/postgres/0046_way_in_uses.sql +1149 -0
  144. package/src/migrations/postgres/0047_cutover_finish.sql +268 -0
  145. package/src/migrations/postgres/0048_use_batch.sql +59 -0
  146. package/src/migrations/postgres/0049_reads.sql +171 -0
  147. package/src/migrations/postgres/meta/0021_snapshot.json +3334 -0
  148. package/src/migrations/postgres/meta/0022_snapshot.json +3831 -0
  149. package/src/migrations/postgres/meta/0023_snapshot.json +3831 -0
  150. package/src/migrations/postgres/meta/0024_snapshot.json +3831 -0
  151. package/src/migrations/postgres/meta/0025_snapshot.json +3831 -0
  152. package/src/migrations/postgres/meta/0026_snapshot.json +3831 -0
  153. package/src/migrations/postgres/meta/0027_snapshot.json +3853 -0
  154. package/src/migrations/postgres/meta/0028_snapshot.json +3853 -0
  155. package/src/migrations/postgres/meta/0029_snapshot.json +3859 -0
  156. package/src/migrations/postgres/meta/0030_snapshot.json +3859 -0
  157. package/src/migrations/postgres/meta/0031_snapshot.json +3859 -0
  158. package/src/migrations/postgres/meta/0032_snapshot.json +3875 -0
  159. package/src/migrations/postgres/meta/0033_snapshot.json +3875 -0
  160. package/src/migrations/postgres/meta/0034_snapshot.json +3875 -0
  161. package/src/migrations/postgres/meta/0035_snapshot.json +3875 -0
  162. package/src/migrations/postgres/meta/0036_snapshot.json +3875 -0
  163. package/src/migrations/postgres/meta/0037_snapshot.json +3875 -0
  164. package/src/migrations/postgres/meta/0038_snapshot.json +3881 -0
  165. package/src/migrations/postgres/meta/0039_snapshot.json +3881 -0
  166. package/src/migrations/postgres/meta/0040_snapshot.json +3931 -0
  167. package/src/migrations/postgres/meta/0041_snapshot.json +3981 -0
  168. package/src/migrations/postgres/meta/0042_snapshot.json +3981 -0
  169. package/src/migrations/postgres/meta/0043_snapshot.json +3981 -0
  170. package/src/migrations/postgres/meta/0044_snapshot.json +3991 -0
  171. package/src/migrations/postgres/meta/0045_snapshot.json +3997 -0
  172. package/src/migrations/postgres/meta/0046_snapshot.json +3997 -0
  173. package/src/migrations/postgres/meta/0047_snapshot.json +4234 -0
  174. package/src/migrations/postgres/meta/_journal.json +189 -0
  175. package/src/migrations/sqlite/0018_log_015.sql +84 -0
  176. package/src/migrations/sqlite/0019_retention.sql +2 -0
  177. package/src/migrations/sqlite/0020_ended_references.sql +1 -0
  178. package/src/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  179. package/src/migrations/sqlite/0022_binding_origin.sql +1 -0
  180. package/src/migrations/sqlite/0023_sign_in_index.sql +2 -0
  181. package/src/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  182. package/src/migrations/sqlite/0025_way_in_uses.sql +42 -0
  183. package/src/migrations/sqlite/0026_cutover_finish.sql +11 -0
  184. package/src/migrations/sqlite/0027_reads.sql +38 -0
  185. package/src/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  186. package/src/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  187. package/src/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  188. package/src/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  189. package/src/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  190. package/src/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  191. package/src/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  192. package/src/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  193. package/src/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  194. package/src/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  195. package/src/migrations/sqlite/meta/_journal.json +70 -0
  196. package/src/postgres-js/README.md +183 -0
  197. package/src/postgres-js/UNLICENSE +24 -0
  198. package/src/postgres-js/bytes.ts +89 -0
  199. package/src/postgres-js/connection.ts +882 -0
  200. package/src/postgres-js/errors.ts +84 -0
  201. package/src/postgres-js/index.ts +516 -0
  202. package/src/postgres-js/platform.node.ts +29 -0
  203. package/src/postgres-js/platform.ts +60 -0
  204. package/src/postgres-js/platform.workerd.ts +225 -0
  205. package/src/postgres-js/query.ts +123 -0
  206. package/src/postgres-js/queue.ts +42 -0
  207. package/src/postgres-js/result.ts +46 -0
  208. package/src/postgres-js/types.ts +52 -0
  209. package/src/postgres.ts +375 -26
  210. package/src/schema-version.ts +2 -2
  211. package/src/schema.sqlite.ts +200 -9
  212. package/src/schema.ts +294 -8
  213. package/src/verify-runtime.ts +18 -29
  214. package/LICENSE +0 -21
  215. package/dist/database-CFhTnnzM.d.ts +0 -73
  216. package/dist/postgres-Bmar8y0q.js +0 -29
  217. package/dist/relations-BfR3JjT1.js +0 -38
  218. package/src/relations.ts +0 -40
@@ -0,0 +1,882 @@
1
+ // One connection to Postgres: it opens (TLS, startup, authentication), sends
2
+ // the statements the pool hands it, pipelined, and reads their answers.
3
+ import { Buffer } from 'node:buffer';
4
+
5
+ import * as platform from '#postgres/platform';
6
+
7
+ import { b, N } from './bytes.ts';
8
+ import * as Errors from './errors.ts';
9
+ import { FIELDS, type Notice } from './errors.ts';
10
+ import type { Settings } from './index.ts';
11
+ import type { Socket, TlsOptions } from './platform.ts';
12
+ import type { Member, Query } from './query.ts';
13
+ import { Queue } from './queue.ts';
14
+ import { Result, type Backend, type Column } from './result.ts';
15
+ import { inferType } from './types.ts';
16
+
17
+ /** A connection asked of the pool by `reserve()`: given the connection itself, rather than sent on it. */
18
+ export interface Reservation {
19
+ reserve(connection: Connection): void;
20
+ reject(error: unknown): void;
21
+ }
22
+
23
+ /** What waits for a connection: a statement, or a reservation. */
24
+ export type Waiting = Query | Reservation;
25
+
26
+ const isReservation = (waiting: Waiting): waiting is Reservation => 'reserve' in waiting;
27
+
28
+ /**
29
+ * A connection held for one caller, a transaction or a `reserve()`, until it
30
+ * gives it back (`release`): the pool sends it nothing else. `next` sends the
31
+ * caller's next statement.
32
+ */
33
+ export interface Reserved {
34
+ next(): void;
35
+ }
36
+
37
+ export interface Connection {
38
+ readonly id: number;
39
+ /** The pool's queue it is in: open, busy, full, connecting, reserved, closed or ended. */
40
+ queue: Queue<Connection>;
41
+ readonly idleTimer: Timer;
42
+ reserved: Reserved | null;
43
+ /** What its transaction hears if it closes: `begin` fails. */
44
+ onclose: ((error: Error) => void) | null;
45
+ /** Open it, for `first`. */
46
+ connect(first: Waiting): void;
47
+ /** Send `query`; whether more may follow it now. */
48
+ execute(query: Query): boolean;
49
+ /** Close it once what it was sent has answered. */
50
+ end(): Promise<void>;
51
+ /** Given back by what held it: closed if it was asked to end meanwhile, or the pool's again. */
52
+ release(): void;
53
+ /** Close it now, failing what it was sent: with `force`, the socket destroyed, whatever it is still writing or opening, for a peer
54
+ * that may never answer. */
55
+ terminate(force?: boolean): void;
56
+ }
57
+
58
+ export interface Events {
59
+ onopen(connection: Connection): void;
60
+ onend(connection: Connection): void;
61
+ onclose(connection: Connection, error: Error): void;
62
+ }
63
+
64
+ interface Timer {
65
+ start(): void;
66
+ cancel(): void;
67
+ }
68
+
69
+ let uid = 1;
70
+
71
+ const Sync = b.typed('S').end();
72
+ const Flush = b.typed('H').end();
73
+ const SSLRequest = b.untyped().inc(4).i32(80877103).end(0);
74
+ const ExecuteOnly = b.typed('E').str(N).i32(0).end();
75
+ const ExecuteUnnamed = Buffer.concat([ExecuteOnly, Sync]);
76
+ const BindNothing = b.typed('B').str(N).str(N).i16(0).i16(0).i16(0).end();
77
+ const DescribeUnnamed = b.typed('D').str('S').str(N).end();
78
+ const noop = () => {};
79
+
80
+ /** A connection, closed, in `queue`, the pool's queue of closed ones. */
81
+ export function createConnection(options: Settings, queue: Queue<Connection>, { onopen, onend, onclose }: Events): Connection {
82
+ const { sslnegotiation, ssl, max, user, database, parsers, serializers, onnotice, max_pipeline, keep_alive, backoff } = options;
83
+
84
+ const sent = new Queue<Query>();
85
+ const id = uid++;
86
+ const backend: Backend = { pid: null, secret: null };
87
+ const idleTimer = timer(end, options.idle_timeout);
88
+ const lifeTimer = timer(end, options.max_lifetime);
89
+ const connectTimer = timer(connectTimedOut, options.connect_timeout);
90
+
91
+ let socket: Socket | null = null;
92
+ let errorResponse: Error | null = null;
93
+ let result = new Result();
94
+ let results: Result[] | null = null;
95
+ let incoming: Buffer = Buffer.alloc(0);
96
+ let incomings: Buffer[] | null = null;
97
+ let closedTime = 0;
98
+ let remaining = 0;
99
+ let length = 0;
100
+ let delay = 0;
101
+ let rows = 0;
102
+ let serverSignature: string | null = null;
103
+ let nextWriteTimer: unknown = null;
104
+ let terminated = false;
105
+ /** A dial asked for after a close (`reconnect`), not made yet: a forced terminate takes it back. */
106
+ let redial: ReturnType<typeof setTimeout> | null = null;
107
+ let initial: Waiting | null = null;
108
+ let ending: Promise<void> | null = null;
109
+ let chunk: Buffer | null = null;
110
+ let ended: (() => void) | null = null;
111
+ let nonce: string | null = null;
112
+ /** The statement answering now; those sent after it, pipelined, wait in `sent`. */
113
+ let query: Query | null = null;
114
+
115
+ const connection: Connection = {
116
+ queue,
117
+ idleTimer,
118
+ reserved: null,
119
+ onclose: null,
120
+ connect(first) {
121
+ initial = first;
122
+ reconnect();
123
+ },
124
+ terminate,
125
+ execute,
126
+ end,
127
+ release,
128
+ id,
129
+ };
130
+
131
+ queue.push(connection);
132
+
133
+ return connection;
134
+
135
+ async function createSocket(): Promise<Socket | null> {
136
+ let x: Socket;
137
+ try {
138
+ x = options.socket
139
+ ? await options.socket()
140
+ : platform.tcp(options.path ? { path: options.path } : { host: options.host, port: options.port, tls: ssl !== false }, ssl ? () => void secure().catch(openingFailed) : connected);
141
+ } catch (e) {
142
+ // Coffre: and closes, so that the pool can open it again, backing off as from a refused connection; upstream left
143
+ // it opening for ever.
144
+ error(e);
145
+ void closed(true);
146
+ return null;
147
+ }
148
+ x.on('error', error);
149
+ x.on('close', closed);
150
+ x.on('drain', drain);
151
+ return x;
152
+ }
153
+
154
+ function execute(q: Query): boolean {
155
+ if (terminated) {
156
+ queryError(q, Errors.connection('CONNECTION_DESTROYED', options));
157
+ return false;
158
+ }
159
+
160
+ let message: Buffer;
161
+ try {
162
+ build(q);
163
+ message = toBuffer(q);
164
+ // What is sent, once it can be: its BEGIN first, if it carries one, and its values as asked.
165
+ if (typeof options.debug === 'function') {
166
+ if (q.begin !== null) options.debug(id, q.begin, [], []);
167
+ for (const member of q.group ?? [q]) options.debug(id, member.text, member.args, member.statement!.types);
168
+ }
169
+ } catch (e) {
170
+ // Coffre: a statement the client cannot write fails alone, and nothing of it is sent. On an idle connection it is
171
+ // answered all the same, a lone Sync's ReadyForQuery, which goes on as any answer does; behind one answering, it
172
+ // is dropped. Upstream failed the statement already answering there, and left this one waiting for an answer,
173
+ // its transaction with it.
174
+ queryError(q, e);
175
+ if (query === null) {
176
+ query = q;
177
+ write(Sync);
178
+ }
179
+ return true;
180
+ }
181
+ if (query) sent.push(q);
182
+ else query = q;
183
+ // Nothing follows a statement that carries its transaction's BEGIN until Postgres has said whether it took.
184
+ return write(message) && !q.describeFirst && q.begin === null && sent.length < max_pipeline;
185
+ }
186
+
187
+ function toBuffer(q: Query): Buffer {
188
+ if (q.parameters.length >= 65534) throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded');
189
+
190
+ // Coffre: a transaction's BEGIN goes with its first statement, a round trip less, and under the same Sync, so that
191
+ // a BEGIN that fails has Postgres skip the statement, which never runs outside the transaction: in one Query
192
+ // message, whose statements stop at the first that fails, or as the extended protocol's Parse, Bind and Execute
193
+ // ahead of the statement's. Upstream sent BEGIN alone and waited for it.
194
+ if (q.options.simple) return b.typed('Q').str((q.begin === null ? '' : q.begin + ';\n') + q.statement!.string + N).end();
195
+ // Coffre: a group's statements, each its Parse, Describe, Bind and Execute, and one Sync after the last.
196
+ if (q.group !== null) return Buffer.concat([...q.group.flatMap((member) => [Parse(member), DescribeUnnamed, Bind(member.parameters, member.statement!.types), ExecuteOnly]), Sync]);
197
+ const statement = q.describeFirst ? [Parse(q), DescribeUnnamed, Flush] : [Parse(q), DescribeUnnamed, bound(q)];
198
+ return Buffer.concat(q.begin === null ? statement : [b.typed('P').str(N).str(q.begin + N).i16(0).end(), BindNothing, ExecuteOnly, ...statement]);
199
+ }
200
+
201
+ /** The statement's values, bound, and its execution: what follows its Parse once the values can be written. */
202
+ function bound(q: Query): Buffer {
203
+ return Buffer.concat([Bind(q.parameters, q.statement!.types), ExecuteUnnamed]);
204
+ }
205
+
206
+ function build(q: Query): void {
207
+ if (q.group !== null) {
208
+ for (const [index, member] of q.group.entries()) {
209
+ // Named by the error, should this one be what the client cannot write.
210
+ q.member = index;
211
+ built(member);
212
+ q.parameters = member.parameters;
213
+ if (member.parameters.length >= 65534) throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded');
214
+ // A group goes in one write: none of its statements can wait for its parameters' types.
215
+ if (typedByServer(member.parameters, member.statement!.types)) throw Errors.generic('GROUP_UNTYPED', 'A statement of a group has a value whose type only the server can say');
216
+ }
217
+ return startMember(q, 0);
218
+ }
219
+ built(q);
220
+ // Coffre: an unnamed statement's plain values go with it, in one round trip (typedByServer, below).
221
+ q.describeFirst = q.parameters.length > 0 && typedByServer(q.parameters, q.statement!.types);
222
+ }
223
+
224
+ /** A statement's values as sent, and their types as the client knows them (0 for the server to say). */
225
+ function built(statement: Query | Member): void {
226
+ const parameters: unknown[] = [];
227
+ const types: number[] = [];
228
+ for (const x of statement.args) {
229
+ if (x === undefined) throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed');
230
+ parameters.push(x);
231
+ types.push(inferType(x));
232
+ }
233
+ statement.parameters = parameters;
234
+ statement.statement = { string: statement.text, types, columns: null };
235
+ }
236
+
237
+ /** A group's statement `index`, the one answering from now: its rows are read as it describes them. */
238
+ function startMember(q: Query, index: number): void {
239
+ const member = q.group![index]!;
240
+ q.member = index;
241
+ q.statement = member.statement;
242
+ q.arrays = member.arrays;
243
+ q.parameters = member.parameters;
244
+ }
245
+
246
+ function write(x: Buffer): boolean {
247
+ chunk = chunk ? Buffer.concat([chunk, x]) : Buffer.from(x);
248
+ if (chunk.length >= 1024) return nextWrite();
249
+ if (nextWriteTimer === null) nextWriteTimer = platform.setImmediate(nextWrite);
250
+ return true;
251
+ }
252
+
253
+ function nextWrite(): boolean {
254
+ const x = socket!.write(chunk!);
255
+ if (nextWriteTimer !== null) platform.clearImmediate(nextWriteTimer);
256
+ chunk = nextWriteTimer = null;
257
+ return x;
258
+ }
259
+
260
+ function connectTimedOut(): void {
261
+ errored(Errors.connection('CONNECT_TIMEOUT', options));
262
+ socket!.destroy();
263
+ }
264
+
265
+ async function secure(): Promise<void> {
266
+ if (sslnegotiation !== 'direct') {
267
+ const at = socket!;
268
+ write(SSLRequest);
269
+ // The server's answer, S or N: TLS is tried either way, and fails against a server that said no.
270
+ await new Promise<void>((resolve) => at.once('data', () => resolve()));
271
+ // Coffre: unless the socket closed meanwhile (on workerd its answer and its close can come in either order): the
272
+ // connection may be opening again, for another query.
273
+ if (socket !== at) return;
274
+ }
275
+
276
+ const tls: TlsOptions = {
277
+ // The host's name, for TLS's SNI; not an IP address's, nor a socket the caller made.
278
+ servername: options.socket || options.path || platform.isIP(options.host) ? undefined : options.host,
279
+ };
280
+ if (sslnegotiation === 'direct') tls.ALPNProtocols = ['postgresql'];
281
+ if (ssl) Object.assign(tls, ssl);
282
+
283
+ const plain = socket!;
284
+ plain.removeAllListeners();
285
+ try {
286
+ socket = platform.startTls(plain, tls);
287
+ } catch (e) {
288
+ // Coffre: TLS options it cannot load (a key, a password): the plain socket closes the connection, as openingFailed destroys it.
289
+ plain.on('close', closed);
290
+ throw e;
291
+ }
292
+ socket.on('secureConnect', connected);
293
+ socket.on('error', error);
294
+ socket.on('close', closed);
295
+ socket.on('drain', drain);
296
+ }
297
+
298
+ function drain(): void {
299
+ // Coffre: not a held connection, which its holder gives back: upstream could give one to the pool mid-transaction.
300
+ if (!query && !connection.reserved) onopen(connection);
301
+ }
302
+
303
+ function data(x: Buffer): void {
304
+ if (incomings) {
305
+ incomings.push(x);
306
+ remaining -= x.length;
307
+ if (remaining > 0) return;
308
+ }
309
+
310
+ incoming = incomings
311
+ ? Buffer.concat(incomings, length - remaining)
312
+ : incoming.length === 0
313
+ ? x
314
+ : Buffer.concat([incoming, x], incoming.length + x.length);
315
+
316
+ while (incoming.length > 4) {
317
+ length = incoming.readUInt32BE(1);
318
+ if (length >= incoming.length) {
319
+ remaining = length - incoming.length;
320
+ incomings = [incoming];
321
+ break;
322
+ }
323
+
324
+ try {
325
+ handle(incoming.subarray(0, length + 1));
326
+ } catch (e) {
327
+ if (query?.describeFirst) write(Sync);
328
+ errored(e);
329
+ }
330
+ incoming = incoming.subarray(length + 1);
331
+ remaining = 0;
332
+ incomings = null;
333
+ }
334
+ }
335
+
336
+ async function connect(): Promise<void> {
337
+ terminated = false;
338
+ socket ??= await createSocket();
339
+ if (!socket) return;
340
+
341
+ connectTimer.start();
342
+
343
+ // A socket of the pool's own is opening, heard from `tcp`; the caller's is open.
344
+ if (options.socket) return ssl ? secure().catch(openingFailed) : connected();
345
+ }
346
+
347
+ function reconnect(): void {
348
+ redial = setTimeout(() => {
349
+ redial = null;
350
+ void connect();
351
+ }, closedTime ? Math.max(0, closedTime + delay - platform.now()) : 0);
352
+ }
353
+
354
+ function connected(): void {
355
+ try {
356
+ lifeTimer.start();
357
+ socket!.on('data', data);
358
+ if (keep_alive) socket!.setKeepAlive?.(true, 1000 * keep_alive);
359
+ write(StartupMessage());
360
+ } catch (err) {
361
+ // Coffre: and closes; upstream left it opening until its connect_timeout, if any.
362
+ openingFailed(err);
363
+ }
364
+ }
365
+
366
+ function error(err: unknown): void {
367
+ errored(err);
368
+ while (sent.length) queryError(sent.shift()!, err);
369
+ }
370
+
371
+ function errored(err: unknown): void {
372
+ if (query) queryError(query, err);
373
+ if (initial) {
374
+ queryError(initial, err);
375
+ initial = null;
376
+ }
377
+ }
378
+
379
+ function queryError(q: Waiting, err: unknown): void {
380
+ if (isReservation(q)) return q.reject(err);
381
+
382
+ const e = err && typeof err === 'object' ? err : new Error(String(err));
383
+ // A statement that carried its transaction's BEGIN: whose error Postgres answered, the BEGIN's or its own. Sent in
384
+ // one Query message, the two are parsed together: an error placed past the BEGIN is the statement's, its position
385
+ // then counted from the statement's start.
386
+ let begin = false;
387
+ if (q.begin !== null && e instanceof Errors.PostgresError) {
388
+ const prefix = q.begin.length + 2;
389
+ const inStatement = q.options.simple && e.position !== undefined && Number(e.position) > prefix;
390
+ if (inStatement) Object.defineProperty(e, 'position', { value: String(Number(e.position) - prefix), enumerable: true });
391
+ begin = !q.began && !inStatement;
392
+ }
393
+ if (!('query' in e) && !('parameters' in e)) {
394
+ const debug = Boolean(options.debug);
395
+ Object.defineProperties(e, {
396
+ stack: { value: ('stack' in e ? String(e.stack) : '') + q.origin.replace(/.*\n/, '\n'), enumerable: debug },
397
+ query: { value: begin ? q.begin : q.group === null ? q.text : q.group[q.member]!.text, enumerable: debug },
398
+ parameters: { value: begin ? [] : q.parameters, enumerable: debug },
399
+ args: { value: q.group === null ? q.args : q.group[q.member]!.args, enumerable: debug },
400
+ types: { value: q.statement?.types, enumerable: debug },
401
+ });
402
+ }
403
+ q.reject(e);
404
+ }
405
+
406
+ function end(): Promise<void> {
407
+ if (ending) return ending;
408
+ if (!connection.reserved) onend(connection);
409
+ if (!connection.reserved && !initial && !query && sent.length === 0) {
410
+ terminate();
411
+ return new Promise((resolve) => (socket && socket.readyState !== 'closed' ? socket.once('close', () => resolve()) : resolve()));
412
+ }
413
+ return (ending = new Promise((resolve) => (ended = resolve)));
414
+ }
415
+
416
+ // Coffre: a held connection asked to end meanwhile (its lifetime, the client's end) closes once given back; upstream
417
+ // closed a transaction's once Postgres said it was idle, and gave a reserve()'s back to the pool, its end() still
418
+ // waiting, which a later end() waited on for ever.
419
+ function release(): void {
420
+ connection.reserved = null;
421
+ if (ending) terminate();
422
+ else onopen(connection);
423
+ }
424
+
425
+ function terminate(force = false): void {
426
+ terminated = true;
427
+ if (query || initial || sent.length) error(Errors.connection('CONNECTION_DESTROYED', options));
428
+
429
+ platform.clearImmediate(nextWriteTimer);
430
+ if (socket) {
431
+ socket.removeListener('data', data);
432
+ socket.removeListener('connect', connected);
433
+ // Coffre: ended with a Terminate when open; destroyed when forced (`end`'s timeout, a call cut: #438, T169), opening or
434
+ // not, or when not open: upstream wrote Terminate first, which a write stuck under backpressure holds back, left a
435
+ // socket still opening alone, and an end waits on a peer that may never answer (review r2 of #438, T169).
436
+ if (socket.readyState === 'open' && !force) socket.end(b.typed('X').end());
437
+ else socket.destroy();
438
+ }
439
+ // Coffre: and no dial a close asked for, waiting out its backoff, opens the connection again after (Fable's review of #438).
440
+ if (force && redial !== null) {
441
+ clearTimeout(redial);
442
+ redial = null;
443
+ }
444
+ if (ended) {
445
+ ended();
446
+ ending = ended = null;
447
+ }
448
+ }
449
+
450
+ async function closed(hadError?: boolean): Promise<void> {
451
+ incoming = Buffer.alloc(0);
452
+ remaining = 0;
453
+ incomings = null;
454
+ platform.clearImmediate(nextWriteTimer);
455
+ idleTimer.cancel();
456
+ lifeTimer.cancel();
457
+ connectTimer.cancel();
458
+
459
+ socket?.removeAllListeners();
460
+ socket = null;
461
+
462
+ // Coffre: a connection closed while it opened, with no error said, fails the query that opened it, as
463
+ // node-postgres's "Connection terminated unexpectedly" did, and is closed as any other. Upstream dialled again, at
464
+ // once and for ever, the query never answered and end() waiting on it; its retry was for the next of several hosts,
465
+ // which coffre never names.
466
+ if (initial) errored(Errors.connection('CONNECTION_CLOSED', options));
467
+
468
+ if (!hadError && (query || sent.length)) error(Errors.connection('CONNECTION_CLOSED', options));
469
+ // Coffre: the query cut short has failed; upstream kept it as the connection's, so that end() waited for it to
470
+ // answer, for ever, unless a later query opened the connection again. And an end() asked meanwhile, as the
471
+ // connection opened or answered, is done: the connection it waited to close has.
472
+ query = null;
473
+ // Coffre: and with it what its answer had left half read, its error, its rows, its results, and what was still to
474
+ // be written: upstream kept them, so the next query on the connection opened again failed with the dead one's
475
+ // error, or answered its own rows after the dead one's count, holes first.
476
+ errorResponse = null;
477
+ rows = 0;
478
+ result = new Result();
479
+ results = null;
480
+ chunk = null;
481
+ nextWriteTimer = null;
482
+ if (ended) {
483
+ ended();
484
+ ending = ended = null;
485
+ }
486
+ closedTime = platform.now();
487
+ if (hadError) options.shared.retries++;
488
+ delay = (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000;
489
+ onclose(connection, Errors.connection('CONNECTION_CLOSED', options));
490
+ }
491
+
492
+ /* Handlers */
493
+ function handle(x: Buffer): void {
494
+ switch (x[0]) {
495
+ case 68: return DataRow(x); // D
496
+ case 83: return ParameterStatus(x); // S
497
+ case 90: return ReadyForQuery(x); // Z
498
+ case 67: return CommandComplete(x); // C
499
+ case 50: return BindComplete(); // 2
500
+ case 49: return; // 1, ParseComplete
501
+ case 116: return ParameterDescription(x); // t
502
+ case 84: return RowDescription(x); // T
503
+ case 82: return authenticate(x); // R
504
+ case 110: return NoData(); // n
505
+ case 75: return BackendKeyData(x); // K
506
+ case 69: return ErrorResponse(x); // E
507
+ case 78: return NoticeResponse(x); // N
508
+ case 73: return; // I, EmptyQueryResponse
509
+ // No LISTEN is asked, but a NOTIFY to a channel listened on by hand would come: it is dropped.
510
+ case 65: return; // A, NotificationResponse
511
+ case 71: return CopyInResponse(); // G
512
+ case 72: // H, CopyOutResponse
513
+ case 87: return CopyOutResponse(); // W, CopyBothResponse
514
+ case 100: return; // d, CopyData
515
+ case 99: return; // c, CopyDone
516
+ case 86: return errored(Errors.notSupported('FunctionCallResponse')); // V
517
+ case 118: return errored(Errors.notSupported('NegotiateProtocolVersion')); // v
518
+ default: console.error('Postgres.js : Unknown Message:', x[0]);
519
+ }
520
+ }
521
+
522
+ function DataRow(x: Buffer): void {
523
+ const q = query!;
524
+ const columns = q.statement!.columns!;
525
+ let index = 7;
526
+ const row: unknown[] | Record<string, unknown> = q.arrays ? new Array(columns.length) : {};
527
+ for (let i = 0; i < columns.length; i++) {
528
+ const column = columns[i]!;
529
+ const length = x.readInt32BE(index);
530
+ index += 4;
531
+
532
+ const value = length === -1
533
+ ? null
534
+ : column.parser === undefined
535
+ ? x.toString('utf8', index, (index += length))
536
+ : column.parser(x.toString('utf8', index, (index += length)));
537
+
538
+ if (Array.isArray(row)) row[i] = value;
539
+ else row[column.name] = value;
540
+ }
541
+
542
+ result[rows++] = row;
543
+ }
544
+
545
+ function ParameterStatus(x: Buffer): void {
546
+ const [k, v] = x.toString('utf8', 5, x.length - 1).split(N);
547
+ options.parameters[k!] = v!;
548
+ }
549
+
550
+ function ReadyForQuery(x: Buffer): void {
551
+ if (query) {
552
+ if (errorResponse) errored(errorResponse);
553
+ else query.resolve(results || result);
554
+ } else if (errorResponse) {
555
+ errored(errorResponse);
556
+ }
557
+
558
+ query = results = errorResponse = null;
559
+ result = new Result();
560
+ connectTimer.cancel();
561
+
562
+ if (initial) {
563
+ // Open: what opened it goes now, a statement sent, a reservation given the connection.
564
+ if (!isReservation(initial)) execute(initial);
565
+ options.shared.retries = 0;
566
+ // Coffre: a reserve() that opened the connection is given it now, itself, even as the client ends: asked before,
567
+ // it is served, and its release closes the connection, which end() waits for (an end() asked in the moment before
568
+ // its holder resumes closes the connection then, and the holder fails, CONNECTION_DESTROYED). Upstream gave it after
569
+ // fetch_types's own query, and without fetch_types never; and it gave the connection to the pool's first waiting,
570
+ // which the reservation, taken from the queue to open this connection again after one closed, no longer was:
571
+ // that reserve() waited for ever.
572
+ const opened = initial;
573
+ initial = null;
574
+ if (opened !== null && isReservation(opened)) opened.reserve(connection);
575
+ return;
576
+ }
577
+
578
+ if (sent.length) query = sent.shift()!;
579
+ if (query) return; // Consider opening if able and sent.length < 50
580
+
581
+ if (connection.reserved) {
582
+ connection.reserved.next();
583
+ } else if (ending) {
584
+ terminate();
585
+ } else {
586
+ onopen(connection);
587
+ }
588
+ }
589
+
590
+ function CommandComplete(x: Buffer): void {
591
+ // The BEGIN sent ahead of the statement: its answer, not the statement's.
592
+ if (query!.begin !== null && !query!.began) {
593
+ query!.began = true;
594
+ return;
595
+ }
596
+ rows = 0;
597
+
598
+ for (let i = x.length - 1; i > 0; i--) {
599
+ if (x[i] === 32 && (x[i + 1] ?? 58) < 58 && result.count === null) result.count = +x.toString('utf8', i + 1, x.length - 1);
600
+ if (x[i - 1]! >= 65) {
601
+ result.command = x.toString('utf8', 5, i);
602
+ result.state = backend;
603
+ break;
604
+ }
605
+ }
606
+
607
+ if (result.command === 'BEGIN' && max !== 1 && !connection.reserved) {
608
+ return errored(Errors.generic('UNSAFE_TRANSACTION', 'Only use sql.begin, sql.reserved or max: 1'));
609
+ }
610
+
611
+ // Coffre: a group's statement answered: its rows are its own result, and the next statement's go in another.
612
+ const q = query!;
613
+ if (q.group !== null) {
614
+ results ??= [result];
615
+ if (q.member + 1 < q.group.length) {
616
+ results.push((result = new Result()));
617
+ startMember(q, q.member + 1);
618
+ }
619
+ return;
620
+ }
621
+
622
+ if (query!.options.simple) return BindComplete();
623
+ }
624
+
625
+ function BindComplete(): void {
626
+ if (!result.statement) result.statement = query!.statement;
627
+ result.columns = query!.statement!.columns;
628
+ }
629
+
630
+ function ParameterDescription(x: Buffer): void {
631
+ const q = query!;
632
+ const types = q.statement!.types;
633
+ const length = x.readUInt16BE(5);
634
+
635
+ for (let i = 0; i < length; ++i) if (!types[i]) types[i] = x.readUInt32BE(7 + i * 4);
636
+
637
+ if (q.describeFirst) {
638
+ write(bound(q));
639
+ q.describeFirst = false;
640
+ }
641
+ }
642
+
643
+ function RowDescription(x: Buffer): void {
644
+ const statement = query!.statement!;
645
+ if (result.command) {
646
+ results = results || [result];
647
+ results.push((result = new Result()));
648
+ result.count = null;
649
+ statement.columns = null;
650
+ }
651
+
652
+ const length = x.readUInt16BE(5);
653
+ let index = 7;
654
+ let start: number;
655
+
656
+ const columns: Column[] = (statement.columns = Array(length));
657
+
658
+ for (let i = 0; i < length; ++i) {
659
+ start = index;
660
+ while (x[index++] !== 0);
661
+ columns[i] = {
662
+ name: x.toString('utf8', start, index - 1),
663
+ parser: parsers[x.readUInt32BE(index + 6)],
664
+ table: x.readUInt32BE(index),
665
+ number: x.readUInt16BE(index + 4),
666
+ type: x.readUInt32BE(index + 6),
667
+ };
668
+ index += 18;
669
+ }
670
+
671
+ result.statement = statement;
672
+ }
673
+
674
+ // Coffre: a step that finishes once its socket has closed (on workerd the hashes are WebCrypto's promises) writes
675
+ // nothing and fails nothing: the connection may be opening again, on another socket, for another query.
676
+ function authenticate(x: Buffer): void {
677
+ const at = socket!;
678
+ Authentication(x, at).catch((err: unknown) => {
679
+ if (socket === at) openingFailed(err);
680
+ });
681
+ }
682
+
683
+ async function Authentication(x: Buffer, at: Socket): Promise<void> {
684
+ const type = x.readUInt32BE(5);
685
+ switch (type) {
686
+ case 0: return;
687
+ case 3: return AuthenticationCleartextPassword();
688
+ case 5: return AuthenticationMD5Password(x, at);
689
+ case 10: return SASL();
690
+ case 11: return SASLContinue(x, at);
691
+ case 12: return SASLFinal(x);
692
+ // Coffre: GSSAPI, SSPI, Kerberos: the connection fails, where upstream logged it and waited.
693
+ default: throw Errors.generic('AUTH_TYPE_NOT_SUPPORTED', `Postgres asked for an authentication the client has not: type ${type}`);
694
+ }
695
+ }
696
+
697
+ // Coffre: what the client failed to do to open the connection, its TLS (a key it cannot read) or its sign-in (a hash,
698
+ // the server's message), fails what waits for the connection, and the connection closes. Upstream left the rejection
699
+ // unhandled, which ends a Node process.
700
+ function openingFailed(err: unknown): void {
701
+ errored(err);
702
+ socket?.destroy();
703
+ }
704
+
705
+ function AuthenticationCleartextPassword(): void {
706
+ write(b.typed('p').str(options.pass).z(1).end());
707
+ }
708
+
709
+ async function AuthenticationMD5Password(x: Buffer, at: Socket): Promise<void> {
710
+ const payload = 'md5' + (await platform.md5(Buffer.concat([Buffer.from(await platform.md5(options.pass + user)), x.subarray(9)])));
711
+ if (socket !== at) return;
712
+ write(b.typed('p').str(payload).z(1).end());
713
+ }
714
+
715
+ function SASL(): void {
716
+ nonce = platform.randomBytes(18).toString('base64');
717
+ b.typed('p').str('SCRAM-SHA-256' + N);
718
+ const i = b.i;
719
+ write(b.inc(4).str('n,,n=*,r=' + nonce).i32(b.i - i - 4, i).end());
720
+ }
721
+
722
+ async function SASLContinue(x: Buffer, at: Socket): Promise<void> {
723
+ const res: Record<string, string> = {};
724
+ for (const part of x.toString('utf8', 9).split(',')) res[part[0]!] = part.slice(2);
725
+
726
+ const saltedPassword = await platform.pbkdf2(options.pass, Buffer.from(res.s!, 'base64'), parseInt(res.i!), 32);
727
+
728
+ const clientKey = await platform.hmac(saltedPassword, 'Client Key');
729
+
730
+ const auth = 'n=*,r=' + nonce + ','
731
+ + 'r=' + res.r + ',s=' + res.s + ',i=' + res.i
732
+ + ',c=biws,r=' + res.r;
733
+
734
+ const signature = (await platform.hmac(await platform.hmac(saltedPassword, 'Server Key'), auth)).toString('base64');
735
+
736
+ const payload = 'c=biws,r=' + res.r + ',p=' + xor(clientKey, await platform.hmac(await platform.sha256(clientKey), auth)).toString('base64');
737
+
738
+ if (socket !== at) return;
739
+ serverSignature = signature;
740
+ write(b.typed('p').str(payload).end());
741
+ }
742
+
743
+ function SASLFinal(x: Buffer): void {
744
+ if (x.toString('utf8', 9).split(N, 1)[0]!.slice(2) === serverSignature) return;
745
+ errored(Errors.generic('SASL_SIGNATURE_MISMATCH', 'The server did not return the correct signature'));
746
+ socket!.destroy();
747
+ }
748
+
749
+ function NoData(): void {
750
+ result.statement = query!.statement;
751
+ result.statement!.columns = [];
752
+ }
753
+
754
+ function BackendKeyData(x: Buffer): void {
755
+ backend.pid = x.readUInt32BE(5);
756
+ backend.secret = x.readUInt32BE(9);
757
+ }
758
+
759
+ function ErrorResponse(x: Buffer): void {
760
+ if (query) {
761
+ if (query.describeFirst) write(Sync);
762
+ errorResponse = Errors.postgres(parseError(x));
763
+ } else {
764
+ errored(Errors.postgres(parseError(x)));
765
+ }
766
+ }
767
+
768
+ // COPY: never asked by coffre, and refused if a statement asks for it. COPY FROM STDIN is failed, which ends it
769
+ // (CopyFail; after the extended protocol's, a Sync), and the statement fails with Postgres's error, which names
770
+ // this; COPY TO STDOUT's rows are dropped as they come, and the statement fails once Postgres is ready again.
771
+ function CopyInResponse(): void {
772
+ write(b.typed('f').str('COPY is not supported by this client' + N).end());
773
+ if (!query?.options.simple) write(Sync);
774
+ }
775
+
776
+ function CopyOutResponse(): void {
777
+ errorResponse ??= Errors.generic('COPY_NOT_SUPPORTED', 'COPY is not supported by this client');
778
+ }
779
+
780
+ function NoticeResponse(x: Buffer): void {
781
+ if (onnotice) onnotice(parseError(x));
782
+ else console.log(parseError(x));
783
+ }
784
+
785
+ /* Messages */
786
+ function Bind(parameters: unknown[], types: number[]): Buffer {
787
+ let prev: number;
788
+
789
+ b.typed('B').str(N).str(N).i16(0).i16(parameters.length);
790
+
791
+ parameters.forEach((x, i) => {
792
+ if (x === null) return void b.i32(0xffffffff);
793
+
794
+ const type = types[i]!;
795
+ const serialize = serializers[type];
796
+ parameters[i] = x = serialize ? serialize(x) : String(x);
797
+
798
+ prev = b.i;
799
+ b.inc(4).str(x as string).i32(b.i - prev - 4, prev);
800
+ });
801
+
802
+ b.i16(0);
803
+
804
+ return b.end();
805
+ }
806
+
807
+ function Parse(q: Query | Member): Buffer {
808
+ const { string, types } = q.statement!;
809
+ b.typed('P').str(N).str(string + N).i16(q.parameters.length);
810
+ q.parameters.forEach((_, i) => b.i32(types[i] || 0));
811
+ return b.end();
812
+ }
813
+
814
+ function StartupMessage(): Buffer {
815
+ return b
816
+ .untyped()
817
+ .inc(4)
818
+ .i16(3)
819
+ .z(2)
820
+ .str(
821
+ Object.entries({ user, database, client_encoding: 'UTF8', ...options.connection })
822
+ .filter(([, v]) => v)
823
+ .map(([k, v]) => k + N + String(v))
824
+ .join(N),
825
+ )
826
+ .z(2)
827
+ .end(0);
828
+ }
829
+ }
830
+
831
+ function parseError(x: Buffer): Notice {
832
+ const error: Record<string, string> = {};
833
+ let start = 5;
834
+ for (let i = 5; i < x.length - 1; i++) {
835
+ if (x[i] === 0) {
836
+ error[FIELDS[x[start] as keyof typeof FIELDS]] = x.toString('utf8', start + 1, i);
837
+ start = i + 1;
838
+ }
839
+ }
840
+ return error;
841
+ }
842
+
843
+ function xor(a: Buffer, b: Buffer): Buffer {
844
+ const length = Math.max(a.length, b.length);
845
+ const buffer = Buffer.allocUnsafe(length);
846
+ for (let i = 0; i < length; i++) buffer[i] = a[i]! ^ b[i]!;
847
+ return buffer;
848
+ }
849
+
850
+ /** `fn`, `seconds` after each `start` unless cancelled; nothing at all for no seconds. */
851
+ function timer(fn: () => unknown, seconds: number | null | undefined | (() => number)): Timer {
852
+ const s = typeof seconds === 'function' ? seconds() : seconds;
853
+ if (!s) return { cancel: noop, start: noop };
854
+
855
+ let handle: ReturnType<typeof setTimeout> | null = null;
856
+ return {
857
+ cancel() {
858
+ if (handle) {
859
+ clearTimeout(handle);
860
+ handle = null;
861
+ }
862
+ },
863
+ start() {
864
+ if (handle) clearTimeout(handle);
865
+ handle = setTimeout(() => {
866
+ fn();
867
+ handle = null;
868
+ }, s * 1000);
869
+ },
870
+ };
871
+ }
872
+
873
+ /**
874
+ * Whether an unnamed statement must learn its parameters' types before it
875
+ * can send them. Strings, numbers and nulls go as text, typed by the server,
876
+ * in the same flush as the statement, as libpq and node-postgres send them;
877
+ * only an array or an object whose type is not known yet has a text that
878
+ * depends on the type. porsager/postgres#1225
879
+ */
880
+ function typedByServer(parameters: unknown[], types: number[]): boolean {
881
+ return parameters.some((x, i) => !types[i] && x !== null && typeof x === 'object');
882
+ }