neon 3.1.0 → 3.2.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 (68) hide show
  1. package/README.md +30 -5
  2. package/dist/{_shared/auth_selection.js → _chunks/auth_selection-DGgq6ifc.js} +3 -3
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/{_shared/credentials.js → _chunks/credentials-MYdHdKah.js} +3 -3
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/{_shared/env-core/env.js → _chunks/env_services-Tz9G4JeT.js} +179 -43
  7. package/dist/{_shared/paths.js → _chunks/paths-DMq0Lt7a.js} +2 -2
  8. package/dist/{_shared/profiles.js → _chunks/profiles-Ir29rqns.js} +4 -13
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/{_virtual/_rolldown/runtime.js → _chunks/rolldown-runtime-D7D4PA-g.js} +1 -1
  11. package/dist/{_shared/secure_file.js → _chunks/secure_file-BucZj4yQ.js} +2 -4
  12. package/dist/analytics.js +2 -2
  13. package/dist/auth_context.js +1 -1
  14. package/dist/commands/api.js +2 -2
  15. package/dist/commands/api_keys.js +2 -2
  16. package/dist/commands/auth.js +7 -6
  17. package/dist/commands/bootstrap.js +3 -3
  18. package/dist/commands/branches.js +2 -2
  19. package/dist/commands/bucket.js +2 -2
  20. package/dist/commands/checkout.js +2 -2
  21. package/dist/commands/config.js +2 -2
  22. package/dist/commands/connection_string.js +2 -2
  23. package/dist/commands/data_api.js +2 -2
  24. package/dist/commands/databases.js +2 -2
  25. package/dist/commands/deploy.js +2 -2
  26. package/dist/commands/dev.js +3 -3
  27. package/dist/commands/diff.js +2 -2
  28. package/dist/commands/env.js +42 -32
  29. package/dist/commands/functions.js +2 -2
  30. package/dist/commands/index.js +35 -33
  31. package/dist/commands/init.js +3 -3
  32. package/dist/commands/inspect.js +2 -2
  33. package/dist/commands/ip_allow.js +2 -2
  34. package/dist/commands/link.js +2 -2
  35. package/dist/commands/logs.js +2 -2
  36. package/dist/commands/neon_auth.js +2 -2
  37. package/dist/commands/open.js +33 -0
  38. package/dist/commands/operations.js +2 -2
  39. package/dist/commands/orgs.js +2 -2
  40. package/dist/commands/profile.js +7 -7
  41. package/dist/commands/projects.js +2 -2
  42. package/dist/commands/psql.js +2 -2
  43. package/dist/commands/roles.js +2 -2
  44. package/dist/commands/set_context.js +2 -2
  45. package/dist/commands/snapshots.js +2 -2
  46. package/dist/commands/status.js +2 -2
  47. package/dist/commands/user.js +2 -2
  48. package/dist/commands/vpc_endpoints.js +2 -2
  49. package/dist/config.js +1 -1
  50. package/dist/dev/env.js +1 -364
  51. package/dist/env_services.js +2 -49
  52. package/dist/index.js +2 -1
  53. package/dist/init/auth.js +4 -4
  54. package/dist/psql/cli.js +1 -1
  55. package/dist/psql/command/cmd_copy.js +1 -1
  56. package/dist/psql/command/cmd_io.js +1 -1403
  57. package/dist/psql/command/cmd_pipeline.js +1 -595
  58. package/dist/psql/command/dispatch.js +1 -2
  59. package/dist/psql/core/common.js +1 -834
  60. package/dist/psql/core/mainloop.js +1 -2
  61. package/dist/psql/core/startup.js +1 -656
  62. package/dist/psql/index.js +1 -1516
  63. package/dist/psql/io/psqlrc.js +1 -1
  64. package/dist/psql/types/index.js +6 -0
  65. package/dist/utils/inspect_db.js +1 -1
  66. package/dist/utils/middlewares.js +2 -1
  67. package/package.json +7 -4
  68. package/dist/_shared/env-core/reuse-secrets.js +0 -179
@@ -1,1517 +1,2 @@
1
- import { createCondStack } from "./command/cmd_cond.js";
2
- import { syncConnectionVars } from "./core/syncVars.js";
3
- import { PgConnection } from "./wire/connection.js";
4
- import { defaultRegistry } from "./command/dispatch.js";
5
- import { runMainLoop } from "./core/mainloop.js";
6
- import { applyEnvOverrides, defaultSettings } from "./core/settings.js";
7
- import { loadPgPass } from "./io/pgpass.js";
8
- import { applyStartupArgs, parseStartupArgs } from "./core/startup.js";
9
- import { createVarStore } from "./core/variables.js";
10
- import { loadPgServices } from "./io/pgservice.js";
11
- import { executeInputString, loadPsqlrc } from "./io/psqlrc.js";
12
- import { existsSync, promises } from "node:fs";
13
- import * as path from "node:path";
14
- import * as os from "node:os";
15
- //#region src/psql/index.ts
16
- /**
17
- * Embedded TypeScript psql entrypoint.
18
- *
19
- * Argv shape mirrors the legacy native-psql call site:
20
- * argv[0] = connection URI (postgresql://user:pw@host:port/db?sslmode=...)
21
- * OR an empty string `''` when the caller provides the
22
- * connection target via libpq flags (-h/-p/-U/-d) and/or PG*
23
- * env. The downstream layered resolver picks those up just like
24
- * upstream psql does.
25
- * argv[1..] = forwarded psql args (parsed by `parseStartupArgs`).
26
- */
27
- const runPsql = async (argv, stdio = {}) => {
28
- const stdin = stdio.stdin ?? process.stdin;
29
- const stdout = stdio.stdout ?? process.stdout;
30
- const stderr = stdio.stderr ?? process.stderr;
31
- const connectionUri = argv[0] ?? "";
32
- let uriPartial = {};
33
- let uriService;
34
- if (connectionUri !== "" && looksLikeConnectionString(connectionUri)) try {
35
- if (connectionUri.startsWith("postgres://") || connectionUri.startsWith("postgresql://")) {
36
- uriPartial = parseConnectionUriPartial(connectionUri);
37
- uriService = parseConnectionUriService(connectionUri);
38
- } else {
39
- const parsed = parseConninfo(connectionUri);
40
- if (typeof parsed._service === "string" && parsed._service.length > 0) uriService = parsed._service;
41
- delete parsed._service;
42
- uriPartial = parsed;
43
- }
44
- } catch (err) {
45
- stderr.write(`psql: error: ${err.message}\n`);
46
- return 2;
47
- }
48
- const parsed = parseStartupArgs(argv.slice(1));
49
- if ("kind" in parsed) {
50
- if (parsed.kind === "help" || parsed.kind === "version") {
51
- stdout.write(parsed.message);
52
- if (!parsed.message.endsWith("\n")) stdout.write("\n");
53
- return 0;
54
- }
55
- stderr.write(`psql: error: ${parsed.message}\n`);
56
- return 1;
57
- }
58
- const vars = createVarStore();
59
- const settings = defaultSettings(vars);
60
- applyEnvOverrides(settings, process.env);
61
- settings.notty = !stdin.isTTY;
62
- const pgpassEntries = await loadPgPass(void 0, {
63
- env: process.env,
64
- stderr
65
- });
66
- let services;
67
- try {
68
- services = await loadPgServices();
69
- } catch (err) {
70
- stderr.write(`psql: error: ${err.message}\n`);
71
- return 2;
72
- }
73
- let resolved;
74
- try {
75
- resolved = applyStartupArgs(parsed, settings, void 0, {
76
- env: process.env,
77
- uriPartial,
78
- serviceName: uriService,
79
- pgpassEntries,
80
- services
81
- });
82
- } catch (err) {
83
- stderr.write(`psql: error: ${err.message}\n`);
84
- return 2;
85
- }
86
- const { connect: connectOpts, preActions } = resolved;
87
- let connection;
88
- try {
89
- connection = await PgConnection.connect(connectOpts);
90
- } catch (err) {
91
- const e = err;
92
- stderr.write(`psql: error: connection to server failed: ${e.message ?? String(err)}\n`);
93
- return 2;
94
- }
95
- settings.db = connection;
96
- syncConnectionVars(settings.vars, connection);
97
- const ctx = {
98
- settings,
99
- registry: defaultRegistry(),
100
- cond: createCondStack(),
101
- stdin,
102
- stdout,
103
- stderr
104
- };
105
- try {
106
- if (!settings.quiet && !settings.notty && preActions.length === 0) writeStartupBanner(connection, stdout);
107
- await loadPsqlrc(ctx, {
108
- skip: parsed.noPsqlrc,
109
- env: process.env
110
- });
111
- if (preActions.length > 0) return await runPreActions(ctx, preActions, parsed.singleTransaction);
112
- return await runMainLoop(ctx);
113
- } finally {
114
- try {
115
- await connection.close();
116
- } catch {}
117
- }
118
- };
119
- /**
120
- * Execute the ordered list of `-c` / `-f` actions and return the upstream
121
- * psql exit code.
122
- *
123
- * Upstream `process_psqlrc_and_targets()` (in `startup.c`) plus the
124
- * dispatcher in `MainLoop()` cooperate to give each switch one of three
125
- * outcomes:
126
- *
127
- * - `-c "SQL or \backslash"`:
128
- * * a client-side failure (e.g. bad `\copy`) marks the SWITCH itself
129
- * as failed → the overall exit code is non-zero, even when
130
- * ON_ERROR_STOP is off and the transaction commits.
131
- * * a server-side failure follows the same rule.
132
- *
133
- * - `-f file`:
134
- * * `process_file()` returns a success status by default, so a
135
- * failing statement inside the file does NOT bubble up to the
136
- * outer exit code (without ON_ERROR_STOP). Only an I/O failure
137
- * opening the file flips the switch status.
138
- *
139
- * - `--single-transaction`:
140
- * * before the FIRST action, issue `BEGIN`. After the LAST action,
141
- * issue `COMMIT` (success) or `ROLLBACK` (when ON_ERROR_STOP fired
142
- * and we stopped early).
143
- * * Without ON_ERROR_STOP, the transaction commits even when some
144
- * individual statements failed — the failing statements only
145
- * influence the exit code (see the `-c` / `-f` distinction above).
146
- */
147
- const runPreActions = async (ctx, preActions, singleTransaction) => {
148
- const { settings, stderr } = ctx;
149
- let status = 0;
150
- let beganTransaction = false;
151
- let earlyStopOnError = false;
152
- let connectionLost = false;
153
- if (singleTransaction && settings.db) try {
154
- await settings.db.execSimple("BEGIN");
155
- beganTransaction = true;
156
- } catch (err) {
157
- const msg = err instanceof Error ? err.message : String(err);
158
- stderr.write(`psql: error: could not begin transaction: ${msg}\n`);
159
- return 1;
160
- }
161
- for (const action of preActions) {
162
- if (connectionLost) break;
163
- if (action.kind === "command") {
164
- let outcome;
165
- try {
166
- outcome = await executeInputString(action.sql, ctx, { print: true });
167
- } catch (err) {
168
- const msg = err instanceof Error ? err.message : String(err);
169
- stderr.write(`psql: ERROR: ${msg}\n`);
170
- status = 1;
171
- if (settings.onErrorStop) {
172
- earlyStopOnError = true;
173
- break;
174
- }
175
- continue;
176
- }
177
- if (outcome.connectionLost) {
178
- connectionLost = true;
179
- status = 2;
180
- break;
181
- }
182
- if (outcome.hadError || outcome.stoppedOnError) status = 1;
183
- if (outcome.stoppedOnError) {
184
- earlyStopOnError = true;
185
- break;
186
- }
187
- } else {
188
- let contents;
189
- try {
190
- contents = await promises.readFile(action.path, "utf8");
191
- } catch (err) {
192
- const msg = err instanceof Error ? err.message : String(err);
193
- stderr.write(`psql: error: ${msg}\n`);
194
- status = 1;
195
- if (settings.onErrorStop) {
196
- earlyStopOnError = true;
197
- break;
198
- }
199
- continue;
200
- }
201
- let outcome;
202
- try {
203
- outcome = await executeInputString(contents, ctx, { print: true });
204
- } catch (err) {
205
- const msg = err instanceof Error ? err.message : String(err);
206
- stderr.write(`psql: ERROR: ${msg}\n`);
207
- status = 3;
208
- if (settings.onErrorStop) {
209
- earlyStopOnError = true;
210
- break;
211
- }
212
- continue;
213
- }
214
- if (outcome.connectionLost) {
215
- connectionLost = true;
216
- status = 2;
217
- break;
218
- }
219
- if (outcome.stoppedOnError) {
220
- status = 3;
221
- earlyStopOnError = true;
222
- break;
223
- }
224
- }
225
- }
226
- if (beganTransaction && settings.db && !connectionLost) {
227
- const closing = earlyStopOnError ? "ROLLBACK" : "COMMIT";
228
- try {
229
- await settings.db.execSimple(closing);
230
- } catch (err) {
231
- const msg = err instanceof Error ? err.message : String(err);
232
- stderr.write(`psql: error: ${closing} failed: ${msg}\n`);
233
- if (status === 0) status = 1;
234
- }
235
- }
236
- return status;
237
- };
238
- const writeStartupBanner = (connection, out) => {
239
- const serverVersion = connection.parameterStatus("server_version") ?? "unknown";
240
- out.write(`psql-ts (neon, server ${serverVersion})\n`);
241
- const tls = connection.getTlsInfo();
242
- if (tls) {
243
- const parts = [
244
- `protocol: ${tls.protocol}`,
245
- `cipher: ${tls.cipher}`,
246
- `compression: ${tls.compression}`
247
- ];
248
- if (tls.alpn) parts.push(`ALPN: ${tls.alpn}`);
249
- out.write(`SSL connection (${parts.join(", ")})\n`);
250
- }
251
- out.write("Type \"help\" for help.\n\n");
252
- };
253
- const KNOWN_QUERY_KEYS = /* @__PURE__ */ new Set([
254
- "host",
255
- "hostaddr",
256
- "port",
257
- "dbname",
258
- "user",
259
- "password",
260
- "passfile",
261
- "channel_binding",
262
- "require_auth",
263
- "connect_timeout",
264
- "client_encoding",
265
- "options",
266
- "application_name",
267
- "fallback_application_name",
268
- "keepalives",
269
- "keepalives_idle",
270
- "keepalives_interval",
271
- "keepalives_count",
272
- "sslmode",
273
- "sslnegotiation",
274
- "sslcompression",
275
- "sslcert",
276
- "sslkey",
277
- "sslcertmode",
278
- "sslrootcert",
279
- "sslcrl",
280
- "sslcrldir",
281
- "sslkeylogfile",
282
- "sslsni",
283
- "requirepeer",
284
- "ssl_min_protocol_version",
285
- "ssl_max_protocol_version",
286
- "krbsrvname",
287
- "gsslib",
288
- "gssencmode",
289
- "service",
290
- "target_session_attrs",
291
- "load_balance_hosts",
292
- "replication"
293
- ]);
294
- /**
295
- * Tokenize a postgres connection URI into raw components.
296
- *
297
- * Hand-rolled rather than using `new URL()` because libpq accepts shapes
298
- * the WHATWG URL parser rejects, e.g. `postgresql://user@` (userinfo with
299
- * no host), `postgres://:12345/` (port-only), `postgres://uri-user@/db`
300
- * (userinfo with empty host). The upstream conformance suite in
301
- * `src/interfaces/libpq/t/001_uri.pl` exercises these forms.
302
- */
303
- const tokenizeConnectionUri = (uri) => {
304
- let rest;
305
- if (uri.startsWith("postgresql://")) rest = uri.slice(13);
306
- else if (uri.startsWith("postgres://")) rest = uri.slice(11);
307
- else throw new Error(`unsupported scheme in URI: ${uri}`);
308
- let query = "";
309
- const qIdx = rest.indexOf("?");
310
- if (qIdx >= 0) {
311
- query = rest.slice(qIdx + 1);
312
- rest = rest.slice(0, qIdx);
313
- }
314
- let database;
315
- const pIdx = rest.indexOf("/");
316
- if (pIdx >= 0) {
317
- const pathRaw = rest.slice(pIdx + 1);
318
- database = pathRaw === "" ? void 0 : decodePercent(pathRaw);
319
- rest = rest.slice(0, pIdx);
320
- }
321
- let userinfo;
322
- const atIdx = rest.lastIndexOf("@");
323
- if (atIdx >= 0) {
324
- userinfo = rest.slice(0, atIdx);
325
- rest = rest.slice(atIdx + 1);
326
- }
327
- let user;
328
- let password;
329
- if (userinfo !== void 0) {
330
- const colon = userinfo.indexOf(":");
331
- if (colon >= 0) {
332
- user = decodePercent(userinfo.slice(0, colon));
333
- password = decodePercent(userinfo.slice(colon + 1));
334
- } else user = decodePercent(userinfo);
335
- }
336
- const tuples = splitAuthorityTuples(rest, uri);
337
- let host;
338
- let port;
339
- const hosts = [];
340
- for (const tuple of tuples) {
341
- const parsed = parseAuthorityTuple(tuple, uri);
342
- if (parsed.host !== void 0) hosts.push({
343
- host: parsed.host,
344
- port: parsed.port
345
- });
346
- }
347
- if (hosts.length > 0) {
348
- host = hosts[0].host;
349
- port = hosts[0].port;
350
- }
351
- const queryMap = parseQuery(query);
352
- return {
353
- user,
354
- password,
355
- host,
356
- port,
357
- ...hosts.length > 1 ? { hosts } : {},
358
- database,
359
- query: queryMap
360
- };
361
- };
362
- /**
363
- * Split a multi-host authority string into one tuple per top-level comma.
364
- * IPv6 bracket regions are atomic — commas inside `[…]` don't split.
365
- *
366
- * Examples:
367
- * `h1:5432,h2,h3:5434` -> ['h1:5432','h2','h3:5434']
368
- * `[::1]:5432,[2001:db8::1]` -> ['[::1]:5432','[2001:db8::1]']
369
- * `h1` -> ['h1']
370
- * `` -> [''] (single empty tuple — caller may
371
- * treat as no-host)
372
- */
373
- const splitAuthorityTuples = (rest, uri) => {
374
- if (rest === "") return [""];
375
- const tuples = [];
376
- let start = 0;
377
- let i = 0;
378
- while (i < rest.length) {
379
- const ch = rest[i];
380
- if (ch === "[") {
381
- const closeIdx = rest.indexOf("]", i);
382
- if (closeIdx < 0) throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
383
- i = closeIdx + 1;
384
- continue;
385
- }
386
- if (ch === ",") {
387
- tuples.push(rest.slice(start, i));
388
- i += 1;
389
- start = i;
390
- continue;
391
- }
392
- i += 1;
393
- }
394
- tuples.push(rest.slice(start));
395
- return tuples;
396
- };
397
- const parseAuthorityTuple = (tuple, uri) => {
398
- if (tuple === "") return {};
399
- if (tuple.startsWith("[")) {
400
- const closeIdx = tuple.indexOf("]");
401
- if (closeIdx < 0) throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
402
- const host = tuple.slice(1, closeIdx);
403
- if (host === "") throw new Error(`IPv6 host address may not be empty: ${uri}`);
404
- const after = tuple.slice(closeIdx + 1);
405
- if (after === "") return { host };
406
- if (after.startsWith(":")) return {
407
- host,
408
- port: after.slice(1)
409
- };
410
- throw new Error(`unexpected characters after IPv6 host address in URI: ${uri}`);
411
- }
412
- const colon = tuple.indexOf(":");
413
- if (colon >= 0) return {
414
- host: decodePercent(tuple.slice(0, colon)),
415
- port: tuple.slice(colon + 1)
416
- };
417
- return { host: decodePercent(tuple) };
418
- };
419
- const parseQuery = (raw) => {
420
- const out = /* @__PURE__ */ new Map();
421
- if (raw === "") return out;
422
- for (const segment of raw.split("&")) {
423
- if (segment === "") continue;
424
- const eq = segment.indexOf("=");
425
- if (eq < 0) throw new Error(`missing "=" after "${segment.trim()}" in connection info string`);
426
- const keyRaw = segment.slice(0, eq);
427
- const valueRaw = segment.slice(eq + 1);
428
- if (valueRaw.includes("=")) throw new Error(`extra "=" in query parameter "${decodePercent(keyRaw).trim()}"`);
429
- const key = decodePercent(keyRaw).trim();
430
- const value = decodePercent(valueRaw).trim();
431
- if (key === "") continue;
432
- if (!KNOWN_QUERY_KEYS.has(key)) throw new Error(`invalid URI query parameter: "${key}"`);
433
- out.set(key, value);
434
- }
435
- return out;
436
- };
437
- /**
438
- * Percent-decode a URI component. libpq strictly validates percent-encoding:
439
- * - `%XX` must be two hex digits
440
- * - bare `%` or `%X` is invalid
441
- * - `%00` is forbidden (NUL bytes can't appear in connection params)
442
- *
443
- * `decodeURIComponent` throws URIError on malformed escapes — we surface that
444
- * as a clear Error. It accepts `%00` (returns `\0`); we explicitly reject.
445
- */
446
- const decodePercent = (s) => {
447
- let decoded;
448
- try {
449
- decoded = decodeURIComponent(s);
450
- } catch {
451
- throw new Error(`invalid percent-encoded token in URI: ${s}`);
452
- }
453
- if (decoded.includes("\0")) throw new Error(`forbidden NUL byte (%00) in URI: ${s}`);
454
- return decoded;
455
- };
456
- const parseConnectionUri = (uri) => {
457
- const raw = tokenizeConnectionUri(uri);
458
- const queryUser = raw.query.get("user");
459
- const queryPassword = raw.query.get("password");
460
- const queryPort = raw.query.get("port");
461
- const queryDbname = raw.query.get("dbname");
462
- const queryHost = raw.query.get("host");
463
- const hostsTuples = computeHostsTuples({
464
- rawHost: raw.host,
465
- rawPort: raw.port,
466
- rawAuthorityHosts: raw.hosts,
467
- queryHost,
468
- queryPort
469
- });
470
- const host = hostsTuples.length > 0 && hostsTuples[0].host !== "" ? hostsTuples[0].host : "localhost";
471
- const port = hostsTuples.length > 0 ? hostsTuples[0].port : 5432;
472
- const user = queryUser !== void 0 && queryUser !== "" ? queryUser : raw.user !== void 0 && raw.user !== "" ? raw.user : process.env.USER ?? "";
473
- const password = queryPassword ?? raw.password;
474
- const database = queryDbname ?? raw.database ?? user;
475
- let ssl = normalizeSslMode(raw.query.get("sslmode") ?? null);
476
- const channelBinding = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
477
- validateGssEncMode(raw.query.get("gssencmode") ?? null);
478
- const options = raw.query.get("options");
479
- const applicationName = raw.query.get("application_name") ?? "psql";
480
- const replication = normalizeReplication(raw.query.get("replication") ?? null);
481
- const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
482
- const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
483
- const sslcert = nonEmpty(raw.query.get("sslcert"));
484
- const sslkey = nonEmpty(raw.query.get("sslkey"));
485
- const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
486
- const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
487
- const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
488
- const sslcrl = nonEmpty(raw.query.get("sslcrl"));
489
- const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
490
- const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
491
- const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
492
- const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
493
- const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
494
- const requirepeer = nonEmpty(raw.query.get("requirepeer"));
495
- if (sslrootcert === "system" && ssl !== "verify-full") ssl = "verify-full";
496
- const hostaddr = nonEmpty(raw.query.get("hostaddr"));
497
- const sslMinProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
498
- const sslMaxProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
499
- assertTlsProtocolRange(sslMinProtocolVersion, sslMaxProtocolVersion);
500
- assertTlsMaxProtocolSupported(sslMaxProtocolVersion);
501
- assertSslNegotiationModeCompatible(ssl, sslnegotiation);
502
- return {
503
- host,
504
- port,
505
- user,
506
- password,
507
- database,
508
- ssl,
509
- channelBinding,
510
- applicationName,
511
- options,
512
- ...sslnegotiation !== void 0 ? { sslnegotiation } : {},
513
- ...hostaddr !== void 0 ? { hostaddr } : {},
514
- ...replication !== void 0 ? { replication } : {},
515
- ...hostsTuples.length > 1 ? { hosts: hostsTuples.map((t) => ({
516
- host: t.host,
517
- port: t.port
518
- })) } : {},
519
- ...targetSessionAttrs !== void 0 ? { targetSessionAttrs } : {},
520
- ...loadBalanceHosts !== void 0 ? { loadBalanceHosts } : {},
521
- ...sslcert !== void 0 ? { sslcert } : {},
522
- ...sslkey !== void 0 ? { sslkey } : {},
523
- ...sslcertmode !== void 0 ? { sslcertmode } : {},
524
- ...sslrootcert !== void 0 ? { sslrootcert } : {},
525
- ...sslcrl !== void 0 ? { sslcrl } : {},
526
- ...sslcrldir !== void 0 ? { sslcrldir } : {},
527
- ...sslkeylogfile !== void 0 ? { sslkeylogfile } : {},
528
- ...sslsni !== void 0 ? { sslsni } : {},
529
- ...keepalives !== void 0 ? { keepalives } : {},
530
- ...keepalivesIdle !== void 0 ? { keepalivesIdle } : {},
531
- ...requirepeer !== void 0 ? { requirepeer } : {},
532
- ...sslMinProtocolVersion !== void 0 ? { sslMinProtocolVersion } : {},
533
- ...sslMaxProtocolVersion !== void 0 ? { sslMaxProtocolVersion } : {}
534
- };
535
- };
536
- /**
537
- * Parse a libpq 0/1 boolean connection parameter (`sslsni`, `keepalives`).
538
- * libpq's `parse_bool_with_len` accepts `1`/`0`, `true`/`false`, `yes`/`no`,
539
- * `on`/`off` (case-insensitive). Returns `undefined` for unset / empty /
540
- * unrecognised so the caller falls back to libpq's default (enabled).
541
- */
542
- const parseLibpqBool = (raw) => {
543
- if (raw === void 0 || raw === "") return void 0;
544
- switch (raw.toLowerCase()) {
545
- case "1":
546
- case "true":
547
- case "yes":
548
- case "on": return true;
549
- case "0":
550
- case "false":
551
- case "no":
552
- case "off": return false;
553
- default: return;
554
- }
555
- };
556
- /**
557
- * Parse `keepalives_idle` (seconds, non-negative integer). Returns the value
558
- * in seconds, or `undefined` if unset / malformed (the wire layer converts to
559
- * milliseconds for `socket.setKeepAlive`'s `initialDelay`).
560
- */
561
- const parseKeepalivesIdle = (raw) => {
562
- if (raw === void 0 || raw === "") return void 0;
563
- const n = Number.parseInt(raw, 10);
564
- return Number.isFinite(n) && n >= 0 ? n : void 0;
565
- };
566
- const nonEmpty = (v) => v === void 0 || v === "" ? void 0 : v;
567
- /**
568
- * Resolve the final {host, port}[] list for a URI:
569
- *
570
- * 1. Start from the authority. `?host=`/`?port=` query overrides take
571
- * precedence (libpq semantics).
572
- * 2. If `host=h1,h2,…` is supplied, parse the comma-list. Ports come from
573
- * `port=p1,p2,…`; must match the host count or be a single value
574
- * (broadcast to all hosts).
575
- * 3. If only the authority had multi-host tuples (`postgresql://h1,h2/db`),
576
- * use those.
577
- * 4. Otherwise fall back to single-host.
578
- *
579
- * Validates every port is in 1..65535 and surfaces a clear error otherwise.
580
- */
581
- const computeHostsTuples = (input) => {
582
- const { rawHost, rawPort, rawAuthorityHosts, queryHost, queryPort } = input;
583
- if (queryHost !== void 0 && queryHost !== "") {
584
- const hosts = queryHost.split(",").map((h) => h.trim());
585
- const portList = queryPort !== void 0 && queryPort !== "" ? queryPort.split(",").map((p) => p.trim()) : null;
586
- if (portList !== null && portList.length !== 1 && portList.length !== hosts.length) throw new Error(`could not match ${String(portList.length)} port numbers to ${String(hosts.length)} hosts`);
587
- return hosts.map((h, idx) => {
588
- let portStr;
589
- if (portList !== null) portStr = portList.length === 1 ? portList[0] : portList[idx];
590
- else portStr = rawPort;
591
- return {
592
- host: h,
593
- port: parsePort(portStr)
594
- };
595
- });
596
- }
597
- if (rawAuthorityHosts !== void 0 && rawAuthorityHosts.length > 0) {
598
- const portList = queryPort !== void 0 && queryPort !== "" ? queryPort.split(",").map((p) => p.trim()) : null;
599
- if (portList !== null && portList.length !== 1 && portList.length !== rawAuthorityHosts.length) throw new Error(`could not match ${String(portList.length)} port numbers to ${String(rawAuthorityHosts.length)} hosts`);
600
- return rawAuthorityHosts.map((t, idx) => ({
601
- host: t.host,
602
- port: parsePort(portList !== null ? portList.length === 1 ? portList[0] : portList[idx] : t.port)
603
- }));
604
- }
605
- return [{
606
- host: rawHost !== void 0 && rawHost !== "" ? rawHost : "",
607
- port: parsePort(queryPort ?? rawPort)
608
- }];
609
- };
610
- const parsePort = (raw) => {
611
- if (raw === void 0 || raw === "") return 5432;
612
- if (/^\d/.test(raw) && !/^\d+$/.test(raw)) throw new Error(`invalid integer value "${raw}" for connection option "port"`);
613
- const p = Number.parseInt(raw, 10);
614
- if (!Number.isFinite(p) || p <= 0 || p > 65535) throw new Error(`invalid port: ${raw}`);
615
- return p;
616
- };
617
- /**
618
- * Parse a libpq-style conninfo string into a `Partial<ConnectOptions>`.
619
- *
620
- * The grammar is roughly `key = value` pairs separated by whitespace, where
621
- * values may be quoted with single quotes (libpq's `\'` is honored as an
622
- * embedded single-quote, but we don't model the full backslash-escape
623
- * universe — there's no test corpus that exercises it). Unknown keys are
624
- * rejected so a typo like `replicate=database` produces a clear error
625
- * rather than silently dropping the parameter.
626
- *
627
- * Recognised keys mirror the URI-side `KNOWN_QUERY_KEYS` allowlist plus
628
- * the authority-style keys (`host`, `port`, `user`, `dbname`, `password`).
629
- * `replication` is normalised through `normalizeReplication` for libpq
630
- * value-set compatibility.
631
- *
632
- * Out of scope: percent-decoding (conninfo strings are NOT percent-encoded),
633
- * `service` resolution from `pg_service.conf`, `passfile` resolution.
634
- */
635
- const parseConninfo = (input) => {
636
- const out = {};
637
- let i = 0;
638
- const n = input.length;
639
- while (i < n) {
640
- while (i < n && /\s/.test(input[i])) i += 1;
641
- if (i >= n) break;
642
- const keyStart = i;
643
- while (i < n && input[i] !== "=" && !/\s/.test(input[i])) i += 1;
644
- const key = input.slice(keyStart, i).toLowerCase();
645
- if (key === "") break;
646
- while (i < n && /\s/.test(input[i])) i += 1;
647
- if (i >= n || input[i] !== "=") throw new Error(`missing "=" after "${key}" in conninfo string`);
648
- i += 1;
649
- while (i < n && /\s/.test(input[i])) i += 1;
650
- let value;
651
- if (i < n && input[i] === "'") {
652
- i += 1;
653
- const parts = [];
654
- while (i < n && input[i] !== "'") if (input[i] === "\\" && i + 1 < n) {
655
- parts.push(input[i + 1]);
656
- i += 2;
657
- } else {
658
- parts.push(input[i]);
659
- i += 1;
660
- }
661
- if (i >= n) throw new Error(`unterminated single quote in conninfo string for key "${key}"`);
662
- i += 1;
663
- value = parts.join("");
664
- } else {
665
- const valStart = i;
666
- while (i < n && !/\s/.test(input[i])) i += 1;
667
- value = input.slice(valStart, i);
668
- }
669
- applyConninfoPair(out, key, value);
670
- }
671
- const hostList = out._hostList;
672
- const portList = out._portList;
673
- if (hostList !== void 0 && hostList.length > 0) {
674
- if (portList !== void 0 && portList.length !== 1 && portList.length !== hostList.length) throw new Error(`could not match ${String(portList.length)} port numbers to ${String(hostList.length)} hosts`);
675
- if (hostList.length > 1) out.hosts = hostList.map((h, idx) => ({
676
- host: h,
677
- port: portList === void 0 ? out.port ?? 5432 : portList.length === 1 ? parsePort(portList[0]) : parsePort(portList[idx])
678
- }));
679
- }
680
- delete out._hostList;
681
- delete out._portList;
682
- assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
683
- assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
684
- return out;
685
- };
686
- const applyConninfoPair = (out, key, value) => {
687
- switch (key) {
688
- case "host":
689
- if (value.includes(",")) {
690
- out._hostList = value.split(",").map((h) => h.trim());
691
- out.host = out._hostList[0];
692
- } else {
693
- out.host = value;
694
- out._hostList = void 0;
695
- }
696
- return;
697
- case "port":
698
- if (value.includes(",")) {
699
- out._portList = value.split(",").map((p) => p.trim());
700
- out.port = parsePort(out._portList[0]);
701
- } else {
702
- out.port = parsePort(value);
703
- out._portList = void 0;
704
- }
705
- return;
706
- case "user":
707
- out.user = value;
708
- return;
709
- case "password":
710
- out.password = value;
711
- return;
712
- case "dbname":
713
- out.database = value;
714
- return;
715
- case "application_name":
716
- out.applicationName = value;
717
- return;
718
- case "sslmode":
719
- out.ssl = normalizeSslMode(value);
720
- return;
721
- case "channel_binding": {
722
- const cb = normalizeChannelBinding(value);
723
- if (cb !== void 0) out.channelBinding = cb;
724
- return;
725
- }
726
- case "require_auth": {
727
- const ra = normalizeRequireAuth(value);
728
- if (ra !== void 0) out.requireAuth = ra;
729
- return;
730
- }
731
- case "connect_timeout": {
732
- const t = Number.parseInt(value, 10);
733
- if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
734
- return;
735
- }
736
- case "client_encoding":
737
- out.clientEncoding = value;
738
- return;
739
- case "options":
740
- out.options = value;
741
- return;
742
- case "replication": {
743
- const rep = normalizeReplication(value);
744
- if (rep !== void 0) out.replication = rep;
745
- return;
746
- }
747
- case "target_session_attrs": {
748
- const tsa = normalizeTargetSessionAttrs(value);
749
- if (tsa !== void 0) out.targetSessionAttrs = tsa;
750
- return;
751
- }
752
- case "load_balance_hosts": {
753
- const lbh = normalizeLoadBalanceHosts(value);
754
- if (lbh !== void 0) out.loadBalanceHosts = lbh;
755
- return;
756
- }
757
- case "gssencmode":
758
- validateGssEncMode(value);
759
- return;
760
- case "sslcert":
761
- if (value !== "") out.sslcert = value;
762
- return;
763
- case "sslkey":
764
- if (value !== "") out.sslkey = value;
765
- return;
766
- case "sslcertmode": {
767
- const cm = normalizeSslCertMode(value);
768
- if (cm !== void 0) out.sslcertmode = cm;
769
- return;
770
- }
771
- case "sslnegotiation": {
772
- const sn = normalizeSslNegotiation(value);
773
- if (sn !== void 0) out.sslnegotiation = sn;
774
- return;
775
- }
776
- case "sslrootcert":
777
- if (value !== "") out.sslrootcert = value;
778
- return;
779
- case "sslcrl":
780
- if (value !== "") out.sslcrl = value;
781
- return;
782
- case "sslcrldir":
783
- if (value !== "") out.sslcrldir = value;
784
- return;
785
- case "sslkeylogfile":
786
- if (value !== "") out.sslkeylogfile = value;
787
- return;
788
- case "hostaddr":
789
- if (value !== "") out.hostaddr = value;
790
- return;
791
- case "ssl_min_protocol_version": {
792
- const v = normalizeTlsProtocolVersion(value === "" ? void 0 : value, "ssl_min_protocol_version");
793
- if (v !== void 0) out.sslMinProtocolVersion = v;
794
- return;
795
- }
796
- case "ssl_max_protocol_version": {
797
- const v = normalizeTlsProtocolVersion(value === "" ? void 0 : value, "ssl_max_protocol_version");
798
- if (v !== void 0) out.sslMaxProtocolVersion = v;
799
- return;
800
- }
801
- case "sslsni": {
802
- const b = parseLibpqBool(value);
803
- if (b !== void 0) out.sslsni = b;
804
- return;
805
- }
806
- case "keepalives": {
807
- const b = parseLibpqBool(value);
808
- if (b !== void 0) out.keepalives = b;
809
- return;
810
- }
811
- case "keepalives_idle": {
812
- const n = parseKeepalivesIdle(value);
813
- if (n !== void 0) out.keepalivesIdle = n;
814
- return;
815
- }
816
- case "requirepeer":
817
- if (value !== "") out.requirepeer = value;
818
- return;
819
- case "passfile":
820
- case "sslcompression":
821
- case "krbsrvname":
822
- case "gsslib":
823
- case "fallback_application_name":
824
- case "keepalives_interval":
825
- case "keepalives_count": return;
826
- case "service":
827
- out._service = value;
828
- return;
829
- default: throw new Error(`invalid conninfo key: "${key}"`);
830
- }
831
- };
832
- /**
833
- * Heuristic: does the `-d` value look like a connection URI or a conninfo
834
- * string (vs. a bare database name)? Mirrors libpq's
835
- * `recognized_connection_string()` test.
836
- */
837
- const looksLikeConnectionString = (s) => {
838
- if (s.startsWith("postgresql://") || s.startsWith("postgres://")) return true;
839
- const eq = s.indexOf("=");
840
- if (eq < 0) return false;
841
- const head = s.slice(0, eq);
842
- return !/\s/.test(head);
843
- };
844
- const normalizeSslMode = (raw) => {
845
- if (raw === null) return "prefer";
846
- const value = raw.toLowerCase();
847
- switch (value) {
848
- case "disable":
849
- case "allow":
850
- case "prefer":
851
- case "require":
852
- case "verify-ca":
853
- case "verify-full": return value;
854
- default: throw new Error(`invalid sslmode value: "${raw}"`);
855
- }
856
- };
857
- /**
858
- * libpq's accepted TLS protocol-version names, in ascending order. The
859
- * index doubles as the comparison key for the `min > max` check. Matching
860
- * is case-insensitive on input (libpq lowercases before comparing) but we
861
- * keep the canonical mixed-case spelling Node's `tls` module expects for
862
- * `minVersion` / `maxVersion`.
863
- */
864
- const TLS_PROTOCOL_VERSIONS = [
865
- "TLSv1",
866
- "TLSv1.1",
867
- "TLSv1.2",
868
- "TLSv1.3"
869
- ];
870
- /**
871
- * Validate and canonicalise a `ssl_{min,max}_protocol_version` value. Returns
872
- * the canonical spelling (`TLSv1.2` etc.) or `undefined` for empty / unset.
873
- * Throws libpq's `invalid <key> value: "<raw>"` wording on a malformed value.
874
- */
875
- const normalizeTlsProtocolVersion = (raw, key) => {
876
- if (raw === void 0 || raw === "") return void 0;
877
- const match = TLS_PROTOCOL_VERSIONS.find((v) => v.toLowerCase() === raw.toLowerCase());
878
- if (match === void 0) throw new Error(`invalid ${key} value: "${raw}"`);
879
- return match;
880
- };
881
- /**
882
- * Reject a `ssl_max_protocol_version` ceiling below TLSv1.2. TLS 1.0/1.1 are
883
- * disabled in Node's bundled OpenSSL, so capping the ceiling there leaves no
884
- * negotiable protocol and the handshake otherwise fails with an opaque
885
- * `ERR_SSL_NO_PROTOCOLS_AVAILABLE` — surface an actionable message at parse
886
- * time instead. Only the MAX is gated: a low *min*
887
- * (`ssl_min_protocol_version=TLSv1.1`) is harmless because Node still
888
- * negotiates the highest mutually-supported version (1.2/1.3), exactly as
889
- * libpq does on a modern OpenSSL. Called AFTER {@link assertTlsProtocolRange}
890
- * so an inverted range (min > max) reports the range error first, matching
891
- * libpq's ordering.
892
- */
893
- const assertTlsMaxProtocolSupported = (max) => {
894
- if (max === "TLSv1" || max === "TLSv1.1") throw new Error(`ssl_max_protocol_version "${max}" is not supported by this runtime's TLS library — TLS 1.0/1.1 are disabled in Node's OpenSSL; the minimum negotiable version is TLSv1.2`);
895
- };
896
- /**
897
- * Reject a `ssl_min_protocol_version` that is higher than
898
- * `ssl_max_protocol_version`, matching libpq's
899
- * `ssl_min_protocol_version must be <= ssl_max_protocol_version` diagnostic.
900
- * Both arguments must already be canonicalised by
901
- * {@link normalizeTlsProtocolVersion}.
902
- */
903
- const assertTlsProtocolRange = (min, max) => {
904
- if (min === void 0 || max === void 0) return;
905
- if (TLS_PROTOCOL_VERSIONS.indexOf(min) > TLS_PROTOCOL_VERSIONS.indexOf(max)) throw new Error(`ssl_min_protocol_version must be <= ssl_max_protocol_version`);
906
- };
907
- const normalizeChannelBinding = (raw) => {
908
- if (raw === null || raw === "") return void 0;
909
- const value = raw.toLowerCase();
910
- switch (value) {
911
- case "disable":
912
- case "prefer":
913
- case "require": return value;
914
- default: throw new Error(`invalid channel_binding value: "${raw}"`);
915
- }
916
- };
917
- /**
918
- * Validate libpq's `gssencmode` (GSSAPI transport encryption).
919
- *
920
- * This client has NO GSSAPI support: GSS-API `gss_wrap`/`gss_unwrap` would
921
- * require a native Kerberos addon (e.g. the `kerberos` npm), which the
922
- * embedded psql deliberately avoids (pure-TS, zero native bindings — the
923
- * same reason the line editor is hand-rolled). `node-postgres` doesn't
924
- * support it either. So:
925
- * - `disable` / `prefer` — accepted and ignored: neither needs GSS
926
- * (`prefer` means "try GSS, else fall back", and falling back to the
927
- * non-GSS path is exactly what we always do).
928
- * - `require` — rejected with a clear diagnostic; we cannot satisfy it.
929
- * - anything else — `invalid gssencmode value`.
930
- * We recognise the parameter (rather than rejecting it as an unknown key)
931
- * so the many tools that always append `gssencmode=...` to a URI keep
932
- * working against Neon.
933
- */
934
- const validateGssEncMode = (raw) => {
935
- if (raw === null || raw === "") return;
936
- const value = raw.toLowerCase();
937
- if (value === "disable" || value === "prefer") return;
938
- if (value === "require") throw new Error("gssencmode=require is not supported: this client has no GSSAPI support");
939
- throw new Error(`invalid gssencmode value: "${raw}"`);
940
- };
941
- /**
942
- * Parse libpq's `sslcertmode` value (`disable` / `allow` / `require`).
943
- * Empty / unset returns `undefined` so the wire-layer default (`allow`)
944
- * applies. A malformed value throws libpq's
945
- * `invalid sslcertmode value: "<raw>"` diagnostic.
946
- */
947
- const normalizeSslCertMode = (raw) => {
948
- if (raw === null || raw === "") return void 0;
949
- const value = raw.toLowerCase();
950
- switch (value) {
951
- case "disable":
952
- case "allow":
953
- case "require": return value;
954
- default: throw new Error(`invalid sslcertmode value: "${raw}"`);
955
- }
956
- };
957
- /**
958
- * Parse libpq's `sslnegotiation` value (`postgres` / `direct`). Empty / unset
959
- * returns `undefined` so the wire-layer default (`postgres`, the classic
960
- * SSLRequest flow) applies. A malformed value throws libpq's
961
- * `invalid sslnegotiation value: "<raw>"` diagnostic.
962
- */
963
- const normalizeSslNegotiation = (raw) => {
964
- if (raw === null || raw === "") return void 0;
965
- const value = raw.toLowerCase();
966
- switch (value) {
967
- case "postgres":
968
- case "direct": return value;
969
- default: throw new Error(`invalid sslnegotiation value: "${raw}"`);
970
- }
971
- };
972
- /**
973
- * libpq constraint: `sslnegotiation=direct` may only be used with an encrypted
974
- * sslmode (`require` / `verify-ca` / `verify-full`). Direct SSL starts the TLS
975
- * handshake immediately with no plaintext fallback, so a "weak" mode that could
976
- * end up unencrypted (`disable` / `allow` / `prefer`) is rejected with libpq's
977
- * exact `pqConnectOptions2` wording. No-op unless `sslnegotiation` is `direct`.
978
- */
979
- const assertSslNegotiationModeCompatible = (ssl, sslnegotiation) => {
980
- if (sslnegotiation !== "direct") return;
981
- if (ssl === "require" || ssl === "verify-ca" || ssl === "verify-full") return;
982
- throw new Error(`weak sslmode "${ssl}" may not be used with sslnegotiation=direct`);
983
- };
984
- const VALID_REQUIRE_AUTH_METHODS = /* @__PURE__ */ new Set([
985
- "password",
986
- "md5",
987
- "gss",
988
- "sspi",
989
- "scram-sha-256",
990
- "creds",
991
- "none"
992
- ]);
993
- /**
994
- * Parse libpq's `require_auth` value: a comma-separated list of method
995
- * names where each entry may be prefixed with `!` to negate. Mixing
996
- * positive and negative entries is forbidden (libpq matches this).
997
- *
998
- * Returns `undefined` for empty input so the wire-layer default applies.
999
- * Throws on invalid syntax with libpq-parity wording, surfaced via the
1000
- * outer `psql: error: ...` channel.
1001
- */
1002
- const normalizeRequireAuth = (raw) => {
1003
- if (raw === null || raw === "") return void 0;
1004
- const tokens = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1005
- if (tokens.length === 0) return void 0;
1006
- const methods = /* @__PURE__ */ new Set();
1007
- let polarity = null;
1008
- for (const token of tokens) {
1009
- const isNeg = token.startsWith("!");
1010
- const name = (isNeg ? token.slice(1) : token).toLowerCase();
1011
- if (!VALID_REQUIRE_AUTH_METHODS.has(name)) throw new Error(`invalid require_auth method: "${token}"`);
1012
- if (polarity === null) polarity = isNeg;
1013
- else if (polarity !== isNeg) throw new Error("require_auth methods cannot mix positive and negative entries");
1014
- methods.add(name);
1015
- }
1016
- return {
1017
- methods,
1018
- negated: polarity ?? false
1019
- };
1020
- };
1021
- /**
1022
- * Accept the libpq-spec set for `target_session_attrs`. Aliases `read-write`/
1023
- * `primary` and `read-only`/`standby` are kept distinct because the wire
1024
- * layer treats the canonical four values identically — we only normalise
1025
- * unknown / empty inputs to `undefined` so the wire-layer default ('any')
1026
- * applies. Throws on unrecognised values, matching libpq behaviour.
1027
- */
1028
- const normalizeTargetSessionAttrs = (raw) => {
1029
- if (raw === null || raw === "") return void 0;
1030
- const value = raw.toLowerCase();
1031
- switch (value) {
1032
- case "any":
1033
- case "read-write":
1034
- case "read-only":
1035
- case "primary":
1036
- case "standby":
1037
- case "prefer-standby": return value;
1038
- default: throw new Error(`invalid value for "target_session_attrs": "${raw}"`);
1039
- }
1040
- };
1041
- /**
1042
- * Accept the libpq-spec set for `load_balance_hosts`:
1043
- * - `disable` (default) — preserve list order
1044
- * - `random` — shuffle before iteration
1045
- *
1046
- * Unknown values throw; empty / unset returns `undefined` so the wire layer
1047
- * default ('disable') applies.
1048
- */
1049
- const normalizeLoadBalanceHosts = (raw) => {
1050
- if (raw === null || raw === "") return void 0;
1051
- const value = raw.toLowerCase();
1052
- if (value === "disable" || value === "random") return value;
1053
- throw new Error(`invalid value for "load_balance_hosts": "${raw}"`);
1054
- };
1055
- /**
1056
- * libpq accepts a wide set of "truthy" values for `replication`:
1057
- * - `true` / `on` / `yes` / `1` → physical replication (mapped to `true`)
1058
- * - `false` / `off` / `no` / `0` → not a walsender (no replication mode)
1059
- * - `database` → logical replication on that DB
1060
- *
1061
- * Returns `undefined` when no value was supplied or when it explicitly
1062
- * disables replication; throws for unrecognised input (matching libpq's
1063
- * `"invalid <…> value"` semantics so users see a clear error rather than
1064
- * silently sending an unexpected startup-message parameter).
1065
- */
1066
- const normalizeReplication = (raw) => {
1067
- if (raw === null || raw === "") return void 0;
1068
- const value = raw.toLowerCase();
1069
- if (value === "database") return "database";
1070
- if (value === "true" || value === "on" || value === "yes" || value === "1") return "true";
1071
- if (value === "false" || value === "off" || value === "no" || value === "0") return;
1072
- throw new Error(`invalid value for "replication": "${raw}"`);
1073
- };
1074
- /**
1075
- * Service name extracted from a connection URI's `?service=` query
1076
- * parameter, if any. Surfaced alongside `parseConnectionUriPartial` so the
1077
- * caller can route the lookup into `applyStartupArgs` without re-parsing
1078
- * the URI.
1079
- */
1080
- const parseConnectionUriService = (uri) => {
1081
- const value = tokenizeConnectionUri(uri).query.get("service");
1082
- return value === void 0 || value === "" ? void 0 : value;
1083
- };
1084
- /**
1085
- * Parse a URI into a `Partial<ConnectOptions>` containing only the fields
1086
- * the URI explicitly supplied. Returned shape:
1087
- *
1088
- * - missing fields are absent (no `undefined` placeholders)
1089
- * - `host`/`port` are populated only when the URI authority or `?host=`
1090
- * specified them
1091
- * - `user`/`password`/`database`/`ssl`/`channelBinding`/... follow the
1092
- * same rule — present iff explicitly set
1093
- *
1094
- * This is the building block for the layered merge in `applyStartupArgs`.
1095
- * The full-defaults variant `parseConnectionUri` remains the right choice
1096
- * for callers that want a complete `ConnectOptions` (e.g. the existing
1097
- * `-d URI` path); it's kept untouched for back-compat.
1098
- */
1099
- const parseConnectionUriPartial = (uri) => {
1100
- const raw = tokenizeConnectionUri(uri);
1101
- const queryUser = raw.query.get("user");
1102
- const queryPassword = raw.query.get("password");
1103
- const queryPort = raw.query.get("port");
1104
- const queryDbname = raw.query.get("dbname");
1105
- const queryHost = raw.query.get("host");
1106
- const hostsTuples = computeHostsTuples({
1107
- rawHost: raw.host,
1108
- rawPort: raw.port,
1109
- rawAuthorityHosts: raw.hosts,
1110
- queryHost,
1111
- queryPort
1112
- });
1113
- const portInUri = raw.port !== void 0 && raw.port !== "" || queryPort !== void 0 && queryPort !== "" || (raw.hosts?.some((t) => t.port !== void 0 && t.port !== "") ?? false);
1114
- const out = {};
1115
- if (hostsTuples.length > 0) {
1116
- if (hostsTuples[0].host !== "") out.host = hostsTuples[0].host;
1117
- if (portInUri && hostsTuples[0].port !== 0) out.port = hostsTuples[0].port;
1118
- if (hostsTuples.length > 1) out.hosts = hostsTuples.map((t) => ({
1119
- host: t.host,
1120
- port: t.port
1121
- }));
1122
- }
1123
- const userExplicit = queryUser !== void 0 && queryUser !== "" ? queryUser : raw.user !== void 0 && raw.user !== "" ? raw.user : void 0;
1124
- if (userExplicit !== void 0) out.user = userExplicit;
1125
- const password = queryPassword ?? raw.password;
1126
- if (password !== void 0) out.password = password;
1127
- const database = queryDbname ?? raw.database;
1128
- if (database !== void 0) out.database = database;
1129
- const sslRaw = raw.query.get("sslmode");
1130
- if (sslRaw !== void 0 && sslRaw !== "") out.ssl = normalizeSslMode(sslRaw);
1131
- const cb = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
1132
- if (cb !== void 0) out.channelBinding = cb;
1133
- const ra = normalizeRequireAuth(raw.query.get("require_auth") ?? null);
1134
- if (ra !== void 0) out.requireAuth = ra;
1135
- const options = raw.query.get("options");
1136
- if (options !== void 0 && options !== "") out.options = options;
1137
- const appName = raw.query.get("application_name");
1138
- if (appName !== void 0 && appName !== "") out.applicationName = appName;
1139
- const replication = normalizeReplication(raw.query.get("replication") ?? null);
1140
- if (replication !== void 0) out.replication = replication;
1141
- const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
1142
- if (targetSessionAttrs !== void 0) out.targetSessionAttrs = targetSessionAttrs;
1143
- const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
1144
- if (loadBalanceHosts !== void 0) out.loadBalanceHosts = loadBalanceHosts;
1145
- const sslcert = nonEmpty(raw.query.get("sslcert"));
1146
- if (sslcert !== void 0) out.sslcert = sslcert;
1147
- const sslkey = nonEmpty(raw.query.get("sslkey"));
1148
- if (sslkey !== void 0) out.sslkey = sslkey;
1149
- const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
1150
- if (sslcertmode !== void 0) out.sslcertmode = sslcertmode;
1151
- const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
1152
- if (sslnegotiation !== void 0) out.sslnegotiation = sslnegotiation;
1153
- const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
1154
- if (sslrootcert !== void 0) out.sslrootcert = sslrootcert;
1155
- const sslcrl = nonEmpty(raw.query.get("sslcrl"));
1156
- if (sslcrl !== void 0) out.sslcrl = sslcrl;
1157
- const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
1158
- if (sslcrldir !== void 0) out.sslcrldir = sslcrldir;
1159
- const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
1160
- if (sslkeylogfile !== void 0) out.sslkeylogfile = sslkeylogfile;
1161
- const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
1162
- if (sslsni !== void 0) out.sslsni = sslsni;
1163
- const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
1164
- if (keepalives !== void 0) out.keepalives = keepalives;
1165
- const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
1166
- if (keepalivesIdle !== void 0) out.keepalivesIdle = keepalivesIdle;
1167
- const requirepeer = nonEmpty(raw.query.get("requirepeer"));
1168
- if (requirepeer !== void 0) out.requirepeer = requirepeer;
1169
- const hostaddr = nonEmpty(raw.query.get("hostaddr"));
1170
- if (hostaddr !== void 0) out.hostaddr = hostaddr;
1171
- const sslMin = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
1172
- if (sslMin !== void 0) out.sslMinProtocolVersion = sslMin;
1173
- const sslMax = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
1174
- if (sslMax !== void 0) out.sslMaxProtocolVersion = sslMax;
1175
- assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
1176
- assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
1177
- const connectTimeoutSec = raw.query.get("connect_timeout");
1178
- if (connectTimeoutSec !== void 0 && connectTimeoutSec !== "") {
1179
- const t = Number.parseInt(connectTimeoutSec, 10);
1180
- if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1181
- }
1182
- const clientEncoding = raw.query.get("client_encoding");
1183
- if (clientEncoding !== void 0 && clientEncoding !== "") out.clientEncoding = clientEncoding;
1184
- return out;
1185
- };
1186
- const PG_ENV_FIELD_MAP = {
1187
- PGHOST: "host",
1188
- PGHOSTADDR: "hostaddr",
1189
- PGPORT: "port",
1190
- PGUSER: "user",
1191
- PGDATABASE: "database",
1192
- PGPASSWORD: "password",
1193
- PGAPPNAME: "applicationName",
1194
- PGOPTIONS: "options",
1195
- PGCLIENTENCODING: "clientEncoding",
1196
- PGSSLMODE: "ssl",
1197
- PGSSLROOTCERT: "sslrootcert",
1198
- PGSSLCERT: "sslcert",
1199
- PGSSLKEY: "sslkey",
1200
- PGSSLCERTMODE: "sslcertmode",
1201
- PGSSLNEGOTIATION: "sslnegotiation",
1202
- PGSSLCRL: "sslcrl",
1203
- PGSSLCRLDIR: "sslcrldir",
1204
- PGSSLKEYLOGFILE: "sslkeylogfile",
1205
- PGCHANNELBINDING: "channelBinding"
1206
- };
1207
- /**
1208
- * Resolve the PG* env vars into a `Partial<ConnectOptions>`. Only set keys
1209
- * end up in the result; unset / empty env vars are skipped so the caller
1210
- * can layer this between URI overrides and pgpass / service / libpq
1211
- * defaults without clobbering anything.
1212
- *
1213
- * Validation: any malformed value (e.g. `PGPORT=abc`) is silently dropped.
1214
- * libpq behaves the same — the connection then fails later with a clearer
1215
- * "could not parse" message, but the env-var lookup itself does not throw.
1216
- *
1217
- * Notes:
1218
- * - `PGHOSTADDR` maps to {@link ConnectOptions.hostaddr}: the wire layer
1219
- * dials this fixed IP while `PGHOST` still drives TLS SNI / cert
1220
- * verification.
1221
- * - `PGCONNECT_TIMEOUT` is in seconds; we convert to milliseconds.
1222
- * - `PGCHANNELBINDING` accepts disable/prefer/require.
1223
- * - `PGSERVICE` is consumed by the caller (it drives the
1224
- * pg_service.conf lookup) and is NOT a direct ConnectOptions field.
1225
- * - `PGSERVICEFILE`, `PGSYSCONFDIR`, `PGPASSFILE` are likewise consumed
1226
- * by the loaders, not surfaced here.
1227
- */
1228
- const envConnectionDefaults = (env) => {
1229
- const out = {};
1230
- const get = (k) => {
1231
- const v = env[k];
1232
- return v !== void 0 && v !== "" ? v : void 0;
1233
- };
1234
- for (const [envName, field] of Object.entries(PG_ENV_FIELD_MAP)) {
1235
- const value = get(envName);
1236
- if (value === void 0) continue;
1237
- applyEnvValue(out, field, value);
1238
- }
1239
- const timeoutRaw = get("PGCONNECT_TIMEOUT");
1240
- if (timeoutRaw !== void 0) {
1241
- const t = Number.parseInt(timeoutRaw, 10);
1242
- if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1243
- }
1244
- validateGssEncMode(get("PGGSSENCMODE") ?? null);
1245
- return out;
1246
- };
1247
- const applyEnvValue = (out, field, value) => {
1248
- switch (field) {
1249
- case "host":
1250
- out.host = value;
1251
- return;
1252
- case "port": {
1253
- const p = Number.parseInt(value, 10);
1254
- if (Number.isFinite(p) && p > 0 && p <= 65535) out.port = p;
1255
- return;
1256
- }
1257
- case "user":
1258
- out.user = value;
1259
- return;
1260
- case "database":
1261
- out.database = value;
1262
- return;
1263
- case "password":
1264
- out.password = value;
1265
- return;
1266
- case "applicationName":
1267
- out.applicationName = value;
1268
- return;
1269
- case "options":
1270
- out.options = value;
1271
- return;
1272
- case "clientEncoding":
1273
- out.clientEncoding = value;
1274
- return;
1275
- case "ssl":
1276
- out.ssl = normalizeSslMode(value);
1277
- return;
1278
- case "sslrootcert":
1279
- out.sslrootcert = value;
1280
- return;
1281
- case "sslcert":
1282
- out.sslcert = value;
1283
- return;
1284
- case "sslkey":
1285
- out.sslkey = value;
1286
- return;
1287
- case "sslcertmode": {
1288
- const cm = normalizeSslCertMode(value);
1289
- if (cm !== void 0) out.sslcertmode = cm;
1290
- return;
1291
- }
1292
- case "sslnegotiation": {
1293
- const sn = normalizeSslNegotiation(value);
1294
- if (sn !== void 0) out.sslnegotiation = sn;
1295
- return;
1296
- }
1297
- case "sslcrl":
1298
- out.sslcrl = value;
1299
- return;
1300
- case "sslcrldir":
1301
- out.sslcrldir = value;
1302
- return;
1303
- case "sslkeylogfile":
1304
- out.sslkeylogfile = value;
1305
- return;
1306
- case "hostaddr":
1307
- out.hostaddr = value;
1308
- return;
1309
- case "channelBinding": {
1310
- const cb = normalizeChannelBinding(value);
1311
- if (cb !== void 0) out.channelBinding = cb;
1312
- return;
1313
- }
1314
- default: return;
1315
- }
1316
- };
1317
- /**
1318
- * libpq compiled-in defaults. The lowest-priority layer in the merge chain.
1319
- *
1320
- * - host: 'localhost'
1321
- * - port: 5432
1322
- * - user: $USER ?? '' (the wire layer surfaces a clear error if the user
1323
- * is still empty at connect time)
1324
- * - database: deferred — libpq defaults dbname to the user; we wire that
1325
- * in `mergeConnectOptions` after layering so a `PGUSER` env can flow
1326
- * into `database` when the user didn't specify one.
1327
- * - ssl: 'prefer'
1328
- * - applicationName: 'psql' — matches upstream so `pg_stat_activity` shows
1329
- * the value users expect.
1330
- * - sslcert / sslkey: libpq auto-loads the default client cert/key at
1331
- * `~/.postgresql/postgresql.crt` / `.key` when neither is configured AND
1332
- * the file exists. We seed these as the lowest-priority defaults via
1333
- * {@link defaultClientCertDefaults}; any explicit URI / env / conninfo
1334
- * value overrides them. A non-existent default file is simply not set
1335
- * (no error), matching libpq — only an explicit path that's missing
1336
- * surfaces an error (at TLS-load time, in the wire layer).
1337
- */
1338
- const libpqConnectionDefaults = (env) => ({
1339
- host: "localhost",
1340
- port: 5432,
1341
- user: env.USER ?? "",
1342
- database: "",
1343
- ssl: "prefer",
1344
- applicationName: "psql",
1345
- ...defaultClientCertDefaults(env)
1346
- });
1347
- /**
1348
- * libpq default client-certificate discovery. When the user has NOT set
1349
- * `sslcert` / `sslkey` (explicit paths and `PGSSLCERT` / `PGSSLKEY` are
1350
- * higher-priority layers), libpq falls back to `~/.postgresql/postgresql.crt`
1351
- * and `~/.postgresql/postgresql.key` — but only if those files actually
1352
- * exist. We mirror that here so a present default cert satisfies e.g.
1353
- * `sslcertmode=require`.
1354
- *
1355
- * The home directory is taken from `env.HOME` (falling back to
1356
- * `os.homedir()`), the same convention as the pgpass / pgservice loaders;
1357
- * passing a synthetic `HOME` keeps this hermetic in tests.
1358
- *
1359
- * Exported for unit testing.
1360
- */
1361
- const defaultClientCertDefaults = (env) => {
1362
- const home = env.HOME ?? os.homedir();
1363
- if (home === void 0 || home === "") return {};
1364
- const out = {};
1365
- const certPath = path.join(home, ".postgresql", "postgresql.crt");
1366
- if (existsSync(certPath)) out.sslcert = certPath;
1367
- const keyPath = path.join(home, ".postgresql", "postgresql.key");
1368
- if (existsSync(keyPath)) out.sslkey = keyPath;
1369
- return out;
1370
- };
1371
- /**
1372
- * Translate a `pg_service.conf` entry into a `Partial<ConnectOptions>`.
1373
- * Unknown keys are silently dropped — the service file format admits
1374
- * arbitrary keys but only the libpq-spec subset maps to ConnectOptions.
1375
- *
1376
- * Numeric / enum validation mirrors `parseConninfo` so an out-of-range
1377
- * port or bogus sslmode in the service file fails the same way (silently
1378
- * dropped here, since libpq itself only warns on invalid service values).
1379
- */
1380
- const serviceEntryToConnectOptions = (entry) => {
1381
- const out = {};
1382
- for (const [k, v] of Object.entries(entry)) switch (k.toLowerCase()) {
1383
- case "host":
1384
- if (v !== "") out.host = v;
1385
- break;
1386
- case "port": {
1387
- const p = Number.parseInt(v, 10);
1388
- if (Number.isFinite(p) && p > 0 && p <= 65535) out.port = p;
1389
- break;
1390
- }
1391
- case "user":
1392
- if (v !== "") out.user = v;
1393
- break;
1394
- case "dbname":
1395
- if (v !== "") out.database = v;
1396
- break;
1397
- case "password":
1398
- out.password = v;
1399
- break;
1400
- case "application_name":
1401
- if (v !== "") out.applicationName = v;
1402
- break;
1403
- case "sslmode":
1404
- if (v !== "") out.ssl = normalizeSslMode(v);
1405
- break;
1406
- case "channel_binding": {
1407
- const cb = normalizeChannelBinding(v);
1408
- if (cb !== void 0) out.channelBinding = cb;
1409
- break;
1410
- }
1411
- case "require_auth": {
1412
- const ra = normalizeRequireAuth(v);
1413
- if (ra !== void 0) out.requireAuth = ra;
1414
- break;
1415
- }
1416
- case "options":
1417
- if (v !== "") out.options = v;
1418
- break;
1419
- case "client_encoding":
1420
- if (v !== "") out.clientEncoding = v;
1421
- break;
1422
- case "sslcert":
1423
- if (v !== "") out.sslcert = v;
1424
- break;
1425
- case "sslkey":
1426
- if (v !== "") out.sslkey = v;
1427
- break;
1428
- case "sslcertmode": {
1429
- const cm = normalizeSslCertMode(v);
1430
- if (cm !== void 0) out.sslcertmode = cm;
1431
- break;
1432
- }
1433
- case "sslnegotiation": {
1434
- const sn = normalizeSslNegotiation(v);
1435
- if (sn !== void 0) out.sslnegotiation = sn;
1436
- break;
1437
- }
1438
- case "sslrootcert":
1439
- if (v !== "") out.sslrootcert = v;
1440
- break;
1441
- case "sslcrl":
1442
- if (v !== "") out.sslcrl = v;
1443
- break;
1444
- case "sslcrldir":
1445
- if (v !== "") out.sslcrldir = v;
1446
- break;
1447
- case "sslkeylogfile":
1448
- if (v !== "") out.sslkeylogfile = v;
1449
- break;
1450
- case "sslsni": {
1451
- const b = parseLibpqBool(v);
1452
- if (b !== void 0) out.sslsni = b;
1453
- break;
1454
- }
1455
- case "keepalives": {
1456
- const b = parseLibpqBool(v);
1457
- if (b !== void 0) out.keepalives = b;
1458
- break;
1459
- }
1460
- case "keepalives_idle": {
1461
- const n = parseKeepalivesIdle(v);
1462
- if (n !== void 0) out.keepalivesIdle = n;
1463
- break;
1464
- }
1465
- case "requirepeer":
1466
- if (v !== "") out.requirepeer = v;
1467
- break;
1468
- case "hostaddr":
1469
- if (v !== "") out.hostaddr = v;
1470
- break;
1471
- case "ssl_min_protocol_version": {
1472
- const pv = normalizeTlsProtocolVersion(v === "" ? void 0 : v, "ssl_min_protocol_version");
1473
- if (pv !== void 0) out.sslMinProtocolVersion = pv;
1474
- break;
1475
- }
1476
- case "ssl_max_protocol_version": {
1477
- const pv = normalizeTlsProtocolVersion(v === "" ? void 0 : v, "ssl_max_protocol_version");
1478
- if (pv !== void 0) out.sslMaxProtocolVersion = pv;
1479
- break;
1480
- }
1481
- case "connect_timeout": {
1482
- const t = Number.parseInt(v, 10);
1483
- if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1484
- break;
1485
- }
1486
- }
1487
- assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
1488
- assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
1489
- return out;
1490
- };
1491
- /**
1492
- * Merge layered partial ConnectOptions into a complete ConnectOptions.
1493
- *
1494
- * Layers are listed in PRIORITY order (highest first). For each output
1495
- * field, the first layer that supplies a value wins. The implementation
1496
- * walks the layers in reverse so the spread-into semantics match.
1497
- *
1498
- * `database` has a libpq-specific fallback: if every layer omits it, the
1499
- * default is the resolved `user` (so `psql -U alice` connects to a
1500
- * database named `alice`). We apply this AFTER all layers have run.
1501
- */
1502
- const mergeConnectOptions = (layers, defaults) => {
1503
- let out = { ...defaults };
1504
- for (let i = layers.length - 1; i >= 0; i--) {
1505
- const layer = layers[i];
1506
- out = {
1507
- ...out,
1508
- ...layer
1509
- };
1510
- }
1511
- if (out.database === "") out.database = out.user;
1512
- if (out.sslrootcert === "system" && out.ssl !== "verify-full") out.ssl = "verify-full";
1513
- assertSslNegotiationModeCompatible(out.ssl, out.sslnegotiation);
1514
- return out;
1515
- };
1516
- //#endregion
1
+ import { a as mergeConnectOptions, c as parseConnectionUriService, d as serviceEntryToConnectOptions, i as looksLikeConnectionString, l as parseConninfo, n as envConnectionDefaults, o as parseConnectionUri, r as libpqConnectionDefaults, s as parseConnectionUriPartial, t as defaultClientCertDefaults, u as runPsql } from "../_chunks/psql-DWH-kc69.js";
1517
2
  export { defaultClientCertDefaults, envConnectionDefaults, libpqConnectionDefaults, looksLikeConnectionString, mergeConnectOptions, parseConnectionUri, parseConnectionUriPartial, parseConnectionUriService, parseConninfo, runPsql, serviceEntryToConnectOptions };