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
@@ -0,0 +1,2169 @@
1
+ import { createCondStack } from "../psql/command/cmd_cond.js";
2
+ import { setStartupVars, syncConnectionVars } from "../psql/core/syncVars.js";
3
+ import { PgConnection } from "../psql/wire/connection.js";
4
+ import { helpVariables, slashUsage, usage } from "../psql/core/help.js";
5
+ import { defaultRegistry } from "../psql/command/dispatch.js";
6
+ import { runMainLoop } from "../psql/core/mainloop.js";
7
+ import { applyEnvOverrides, defaultSettings } from "../psql/core/settings.js";
8
+ import { loadPgPass, lookupPgPass } from "../psql/io/pgpass.js";
9
+ import { createVarStore } from "../psql/core/variables.js";
10
+ import { loadPgServices } from "../psql/io/pgservice.js";
11
+ import { executeInputString, loadPsqlrc } from "../psql/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/core/startup.ts
16
+ /**
17
+ * psql startup-args parser + applier.
18
+ *
19
+ * TypeScript port of `parse_psql_options()` / the top of `main()` in
20
+ * `src/bin/psql/startup.c`. Two responsibilities:
21
+ *
22
+ * 1. `parseStartupArgs(argv)` — hand-written getopt_long-equivalent that
23
+ * walks the argv array once, decoding short (`-c`) and long (`--command`)
24
+ * options, and accumulates an ordered list of `-c`/`-f` actions plus a
25
+ * bag of settings flags. Positional arguments follow the upstream
26
+ * `[DBNAME [USERNAME]]` rule.
27
+ *
28
+ * We hand-roll the parser instead of pulling in `yargs` (or similar)
29
+ * because upstream psql has subtle precedence rules — actions are
30
+ * ordered, `-v NAME` without `=` deletes the variable, `-X` interacts
31
+ * with `.psqlrc` discovery, etc. The parser stays under ~250 LOC and is
32
+ * fully covered by unit tests.
33
+ *
34
+ * 2. `applyStartupArgs(args, settings, baseConnect)` — mutates `settings`
35
+ * (and its var store) in-place to reflect parsed flags, then returns
36
+ * the connect options the caller should use plus the ordered list of
37
+ * pre-REPL actions to execute (currently just `-c` / `-f`).
38
+ *
39
+ * Out-of-scope for WP-26 but the table mentions them so they're listed below:
40
+ * - `-1` / `--single-transaction` — we accept and record on the args but
41
+ * do not wire its semantics in the caller. Tracked for a follow-up.
42
+ * - `-C` — accepted as a no-op (since-PG17 connection-check skip).
43
+ * - `-l` / `--list` — recorded; the caller may use it later to short-circuit
44
+ * the REPL with a `\l` and exit. Not wired here.
45
+ * - `-L logfile`, `-o output` — recorded on args. Routing into the printer
46
+ * is owned by other WPs (logfile streaming and output redirection).
47
+ * - `--help[=topic]` — returns a ParseError with `kind: 'help'` and the
48
+ * help text already rendered as `message`. Caller writes and exits 0.
49
+ * - `-V` / `--version` — returns `kind: 'version'` with the version string.
50
+ * Caller writes and exits 0.
51
+ *
52
+ * Help/version text is rendered through `./help.ts` so the formatting stays
53
+ * in lock-step with `\?` output (WP-18). The version string mirrors upstream
54
+ * `psql (PostgreSQL) PG_VERSION` and is currently hard-coded — the embedded
55
+ * psql doesn't have a build-system PG_VERSION constant. A follow-up could
56
+ * surface the real PG version from a const, but for now we expose the
57
+ * embedded build's version label.
58
+ */
59
+ /** Short options that consume the next argv slot (or attached value). */
60
+ const SHORT_WITH_ARG = /* @__PURE__ */ new Set([
61
+ "c",
62
+ "d",
63
+ "f",
64
+ "F",
65
+ "h",
66
+ "L",
67
+ "o",
68
+ "p",
69
+ "P",
70
+ "R",
71
+ "T",
72
+ "U",
73
+ "v"
74
+ ]);
75
+ /** Short flags (no argument). */
76
+ const SHORT_NO_ARG = /* @__PURE__ */ new Set([
77
+ "a",
78
+ "A",
79
+ "b",
80
+ "C",
81
+ "e",
82
+ "E",
83
+ "H",
84
+ "l",
85
+ "n",
86
+ "q",
87
+ "s",
88
+ "S",
89
+ "t",
90
+ "V",
91
+ "w",
92
+ "W",
93
+ "x",
94
+ "X",
95
+ "z",
96
+ "0",
97
+ "1",
98
+ "?"
99
+ ]);
100
+ /** Long option → canonical short equivalent (or sentinel string). */
101
+ const LONG_MAP = {
102
+ "echo-all": "a",
103
+ "no-align": "A",
104
+ command: "c",
105
+ dbname: "d",
106
+ "echo-queries": "e",
107
+ "echo-errors": "b",
108
+ "echo-hidden": "E",
109
+ file: "f",
110
+ "field-separator": "F",
111
+ "field-separator-zero": "z",
112
+ host: "h",
113
+ html: "H",
114
+ list: "l",
115
+ "log-file": "L",
116
+ "no-readline": "n",
117
+ "single-transaction": "1",
118
+ output: "o",
119
+ port: "p",
120
+ pset: "P",
121
+ quiet: "q",
122
+ "record-separator": "R",
123
+ "record-separator-zero": "0",
124
+ "single-step": "s",
125
+ "single-line": "S",
126
+ "tuples-only": "t",
127
+ "table-attr": "T",
128
+ username: "U",
129
+ set: "v",
130
+ variable: "v",
131
+ version: "V",
132
+ "no-password": "w",
133
+ password: "W",
134
+ expanded: "x",
135
+ "no-psqlrc": "X",
136
+ "no-pager": "__no_pager__",
137
+ "on-error-stop": "__on_error_stop__",
138
+ help: "__help__",
139
+ csv: "__csv__"
140
+ };
141
+ /** Long options that REQUIRE an attached value (or one in the next argv). */
142
+ const LONG_WITH_ARG = /* @__PURE__ */ new Set([
143
+ "command",
144
+ "dbname",
145
+ "file",
146
+ "field-separator",
147
+ "host",
148
+ "log-file",
149
+ "output",
150
+ "port",
151
+ "pset",
152
+ "record-separator",
153
+ "table-attr",
154
+ "username",
155
+ "set",
156
+ "variable"
157
+ ]);
158
+ /** Long options whose value is optional (only attached with `=`). */
159
+ const LONG_OPTIONAL_ARG = /* @__PURE__ */ new Set(["help", "echo-hidden"]);
160
+ const blankArgs = () => ({
161
+ actions: [],
162
+ variables: [],
163
+ noPsqlrc: false,
164
+ noReadline: false,
165
+ echoAll: false,
166
+ echoHidden: "off",
167
+ echoErrors: false,
168
+ echoQueries: false,
169
+ quiet: false,
170
+ singleline: false,
171
+ singlestep: false,
172
+ noAlign: false,
173
+ noPager: false,
174
+ htmlMode: false,
175
+ tuplesOnly: false,
176
+ expanded: false,
177
+ csvOutput: false,
178
+ onErrorStop: false,
179
+ fieldSepZero: false,
180
+ recordSepZero: false,
181
+ pset: [],
182
+ list: false,
183
+ singleTransaction: false,
184
+ help: false,
185
+ version: false,
186
+ positional: []
187
+ });
188
+ const renderToString = (fn) => {
189
+ const chunks = [];
190
+ fn({ write(chunk) {
191
+ chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
192
+ return true;
193
+ } });
194
+ return chunks.join("");
195
+ };
196
+ const renderHelp = (topic) => {
197
+ if (topic === void 0 || topic === "options") return renderToString((out) => {
198
+ usage(out);
199
+ });
200
+ if (topic === "commands") return renderToString((out) => {
201
+ slashUsage(out, false);
202
+ });
203
+ if (topic === "variables") return renderToString((out) => {
204
+ helpVariables(out);
205
+ });
206
+ return "";
207
+ };
208
+ /**
209
+ * Version label for the embedded TypeScript psql. This is the "client"
210
+ * identifier — the analogue of upstream psql's compiled-in `PG_VERSION`.
211
+ * The embedded psql is not a real PostgreSQL build, so we track our own
212
+ * implementation version here. It seeds the `VERSION_NAME` psql var (and,
213
+ * via {@link setStartupVars}, the numeric `VERSION_NUM`); keep it as a
214
+ * `MAJOR.MINOR.PATCH` string so the numeric mapping stays meaningful. Bump
215
+ * alongside meaningful behaviour changes to the embedded psql.
216
+ */
217
+ const CLIENT_VERSION = "1.0.0";
218
+ const VERSION_STRING = `psql (PostgreSQL) embedded-ts (neon ${CLIENT_VERSION})`;
219
+ const pushAction = (acts, a) => {
220
+ acts.push(a);
221
+ };
222
+ const pushVariable = (vars, raw) => {
223
+ const eq = raw.indexOf("=");
224
+ if (eq < 0) {
225
+ vars.push({
226
+ name: raw,
227
+ value: ""
228
+ });
229
+ return;
230
+ }
231
+ vars.push({
232
+ name: raw.slice(0, eq),
233
+ value: raw.slice(eq + 1)
234
+ });
235
+ };
236
+ /**
237
+ * `-d VALUE` resolution. libpq accepts three shapes for the value:
238
+ *
239
+ * 1. Bare database name (no `=`, no `postgres[ql]://` prefix) — set
240
+ * `args.database` directly. This is by far the common case.
241
+ * 2. Connection URI (`postgresql://…` / `postgres://…`) — parse it and
242
+ * lift the fields it specifies onto `args` (host, port, user, etc.).
243
+ * 3. Conninfo key=value pairs separated by whitespace — same, but parsed
244
+ * via `parseConninfo`.
245
+ *
246
+ * The test corpus for upstream `001_basic.pl` uses shape (3) to open a
247
+ * walsender connection: `-d "dbname=postgres replication=database"`. We
248
+ * fold any conninfo-only fields (currently only `replication`) onto a
249
+ * dedicated `args.replication` slot that `applyStartupArgs` threads into
250
+ * `ConnectOptions.replication`.
251
+ */
252
+ const applyDashD = (args, value) => {
253
+ if (!looksLikeConnectionString(value)) {
254
+ args.database = value;
255
+ return;
256
+ }
257
+ const parsed = value.startsWith("postgresql://") || value.startsWith("postgres://") ? parseConnectionUriPartial(value) : parseConninfo(value);
258
+ args.dashD = {
259
+ ...args.dashD ?? {},
260
+ ...parsed
261
+ };
262
+ if (parsed.host !== void 0 && args.host === void 0) args.host = parsed.host;
263
+ if (parsed.port !== void 0 && args.port === void 0) args.port = parsed.port;
264
+ if (parsed.user !== void 0 && args.user === void 0) args.user = parsed.user;
265
+ if (parsed.password !== void 0 && args.password === void 0) args.password = parsed.password;
266
+ if (parsed.database !== void 0 && args.database === void 0) args.database = parsed.database;
267
+ if (parsed.replication !== void 0) args.replication = parsed.replication;
268
+ };
269
+ const parseStartupArgs = (argv) => {
270
+ const args = blankArgs();
271
+ const actions = [];
272
+ const variables = [];
273
+ const pset = [];
274
+ let i = 0;
275
+ const n = argv.length;
276
+ const needValue = (flagDisplay) => {
277
+ if (i + 1 >= n) return {
278
+ kind: "missing-arg",
279
+ message: `option requires an argument -- ${flagDisplay}`
280
+ };
281
+ i += 1;
282
+ return argv[i];
283
+ };
284
+ const applyShort = (letter, attachedValue, flagDisplay) => {
285
+ const takesArg = SHORT_WITH_ARG.has(letter);
286
+ let value;
287
+ if (takesArg) if (attachedValue !== void 0) value = attachedValue;
288
+ else {
289
+ const v = needValue(flagDisplay);
290
+ if (typeof v !== "string") return v;
291
+ value = v;
292
+ }
293
+ else if (attachedValue !== void 0) return {
294
+ kind: "invalid-option",
295
+ message: `option does not take an argument -- ${letter}`
296
+ };
297
+ switch (letter) {
298
+ case "a":
299
+ args.echoAll = true;
300
+ return null;
301
+ case "A":
302
+ args.noAlign = true;
303
+ return null;
304
+ case "b":
305
+ args.echoErrors = true;
306
+ return null;
307
+ case "C": return null;
308
+ case "c":
309
+ pushAction(actions, {
310
+ kind: "command",
311
+ sql: value
312
+ });
313
+ return null;
314
+ case "d":
315
+ applyDashD(args, value);
316
+ return null;
317
+ case "e":
318
+ args.echoQueries = true;
319
+ return null;
320
+ case "E":
321
+ args.echoHidden = "on";
322
+ return null;
323
+ case "f":
324
+ pushAction(actions, {
325
+ kind: "file",
326
+ path: value
327
+ });
328
+ return null;
329
+ case "F":
330
+ args.fieldSep = value;
331
+ args.fieldSepZero = false;
332
+ return null;
333
+ case "h":
334
+ args.host = value;
335
+ return null;
336
+ case "H":
337
+ args.htmlMode = true;
338
+ return null;
339
+ case "l":
340
+ args.list = true;
341
+ return null;
342
+ case "L":
343
+ args.log = value;
344
+ return null;
345
+ case "n":
346
+ args.noReadline = true;
347
+ return null;
348
+ case "o":
349
+ args.output = value;
350
+ return null;
351
+ case "p": {
352
+ const p = Number.parseInt(value, 10);
353
+ if (!Number.isFinite(p) || p <= 0 || p > 65535) return {
354
+ kind: "invalid-value",
355
+ message: `invalid port number: "${value}"`
356
+ };
357
+ args.port = p;
358
+ return null;
359
+ }
360
+ case "P":
361
+ pset.push(value);
362
+ return null;
363
+ case "q":
364
+ args.quiet = true;
365
+ return null;
366
+ case "R":
367
+ args.recordSep = value;
368
+ args.recordSepZero = false;
369
+ return null;
370
+ case "s":
371
+ args.singlestep = true;
372
+ return null;
373
+ case "S":
374
+ args.singleline = true;
375
+ return null;
376
+ case "t":
377
+ args.tuplesOnly = true;
378
+ return null;
379
+ case "T":
380
+ pset.push(`tableattr=${value}`);
381
+ return null;
382
+ case "U":
383
+ args.user = value;
384
+ return null;
385
+ case "v":
386
+ pushVariable(variables, value);
387
+ return null;
388
+ case "V": return {
389
+ kind: "version",
390
+ message: VERSION_STRING
391
+ };
392
+ case "w":
393
+ args.promptPassword = false;
394
+ return null;
395
+ case "W":
396
+ args.promptPassword = true;
397
+ return null;
398
+ case "x":
399
+ args.expanded = true;
400
+ return null;
401
+ case "X":
402
+ args.noPsqlrc = true;
403
+ return null;
404
+ case "z":
405
+ args.fieldSepZero = true;
406
+ return null;
407
+ case "0":
408
+ args.recordSepZero = true;
409
+ return null;
410
+ case "1":
411
+ args.singleTransaction = true;
412
+ return null;
413
+ case "?": return {
414
+ kind: "help",
415
+ message: renderHelp(void 0)
416
+ };
417
+ default: return {
418
+ kind: "invalid-option",
419
+ message: `invalid option -- ${letter}`
420
+ };
421
+ }
422
+ };
423
+ while (i < n) {
424
+ const tok = argv[i];
425
+ if (tok === "--") {
426
+ i += 1;
427
+ while (i < n) {
428
+ args.positional.push(argv[i]);
429
+ i += 1;
430
+ }
431
+ break;
432
+ }
433
+ if (tok.startsWith("--")) {
434
+ const eq = tok.indexOf("=");
435
+ const name = eq < 0 ? tok.slice(2) : tok.slice(2, eq);
436
+ const attached = eq < 0 ? void 0 : tok.slice(eq + 1);
437
+ if (name === "csv") {
438
+ if (attached !== void 0) return {
439
+ kind: "invalid-option",
440
+ message: `option does not take an argument: --${name}`
441
+ };
442
+ args.csvOutput = true;
443
+ i += 1;
444
+ continue;
445
+ }
446
+ if (name === "no-pager") {
447
+ if (attached !== void 0) return {
448
+ kind: "invalid-option",
449
+ message: `option does not take an argument: --${name}`
450
+ };
451
+ args.noPager = true;
452
+ i += 1;
453
+ continue;
454
+ }
455
+ if (name === "on-error-stop") {
456
+ if (attached !== void 0) return {
457
+ kind: "invalid-option",
458
+ message: `option does not take an argument: --${name}`
459
+ };
460
+ args.onErrorStop = true;
461
+ variables.push({
462
+ name: "ON_ERROR_STOP",
463
+ value: "on"
464
+ });
465
+ i += 1;
466
+ continue;
467
+ }
468
+ if (name === "help") {
469
+ const topic = attached;
470
+ if (topic !== void 0 && topic !== "options" && topic !== "commands" && topic !== "variables") return {
471
+ kind: "invalid-option",
472
+ message: `unrecognized help topic: ${topic}`
473
+ };
474
+ return {
475
+ kind: "help",
476
+ message: renderHelp(topic)
477
+ };
478
+ }
479
+ if (name === "echo-hidden") {
480
+ if (attached === void 0) args.echoHidden = "on";
481
+ else if (attached === "noexec") args.echoHidden = "noexec";
482
+ else if (attached === "" || attached === "on") args.echoHidden = "on";
483
+ else return {
484
+ kind: "invalid-value",
485
+ message: `invalid value for --echo-hidden: "${attached}"`
486
+ };
487
+ i += 1;
488
+ continue;
489
+ }
490
+ const short = LONG_MAP[name];
491
+ if (short === void 0) return {
492
+ kind: "invalid-option",
493
+ message: `unrecognized option: --${name}`
494
+ };
495
+ const requiresArg = LONG_WITH_ARG.has(name);
496
+ const optionalArg = LONG_OPTIONAL_ARG.has(name);
497
+ let value = attached;
498
+ if (requiresArg && value === void 0) {
499
+ if (i + 1 >= n) return {
500
+ kind: "missing-arg",
501
+ message: `option requires an argument: --${name}`
502
+ };
503
+ i += 1;
504
+ value = argv[i];
505
+ } else if (!requiresArg && !optionalArg && value !== void 0) return {
506
+ kind: "invalid-option",
507
+ message: `option does not take an argument: --${name}`
508
+ };
509
+ const err = applyShort(short, value, `--${name}`);
510
+ if (err) return err;
511
+ i += 1;
512
+ continue;
513
+ }
514
+ if (tok.startsWith("-") && tok.length > 1) {
515
+ let k = 1;
516
+ while (k < tok.length) {
517
+ const letter = tok[k];
518
+ if (!SHORT_WITH_ARG.has(letter) && !SHORT_NO_ARG.has(letter)) return {
519
+ kind: "invalid-option",
520
+ message: `invalid option -- ${letter}`
521
+ };
522
+ if (SHORT_WITH_ARG.has(letter)) {
523
+ const remainder = tok.slice(k + 1);
524
+ const err = applyShort(letter, remainder.length > 0 ? remainder : void 0, letter);
525
+ if (err) return err;
526
+ k = tok.length;
527
+ break;
528
+ }
529
+ const err = applyShort(letter, void 0, letter);
530
+ if (err) return err;
531
+ k += 1;
532
+ }
533
+ i += 1;
534
+ continue;
535
+ }
536
+ args.positional.push(tok);
537
+ i += 1;
538
+ }
539
+ return {
540
+ ...args,
541
+ actions,
542
+ variables,
543
+ pset
544
+ };
545
+ };
546
+ const applyStartupArgs = (args, settings, baseConnectOpts, resolution) => {
547
+ setStartupVars(settings.vars, CLIENT_VERSION);
548
+ for (const v of args.variables) if (v.value === "") settings.vars.unset(v.name);
549
+ else settings.vars.set(v.name, v.value);
550
+ if (args.echoAll) settings.vars.set("ECHO", "all");
551
+ if (args.echoErrors) settings.vars.set("ECHO", "errors");
552
+ if (args.echoQueries) settings.vars.set("ECHO", "queries");
553
+ if (args.echoHidden !== "off") {
554
+ settings.echoHidden = args.echoHidden;
555
+ settings.vars.set("ECHO_HIDDEN", args.echoHidden === "noexec" ? "noexec" : "on");
556
+ }
557
+ if (args.quiet) {
558
+ settings.quiet = true;
559
+ settings.vars.set("QUIET", "on");
560
+ }
561
+ if (args.singleline) {
562
+ settings.singleline = true;
563
+ settings.vars.set("SINGLELINE", "on");
564
+ }
565
+ if (args.singlestep) {
566
+ settings.singlestep = true;
567
+ settings.vars.set("SINGLESTEP", "on");
568
+ }
569
+ if (args.onErrorStop) {
570
+ settings.onErrorStop = true;
571
+ settings.vars.set("ON_ERROR_STOP", "on");
572
+ }
573
+ if (args.noAlign) settings.popt.topt.format = "unaligned";
574
+ if (args.htmlMode) settings.popt.topt.format = "html";
575
+ if (args.csvOutput) settings.popt.topt.format = "csv";
576
+ if (args.tuplesOnly) settings.popt.topt.tuplesOnly = true;
577
+ if (args.expanded) settings.popt.topt.expanded = "on";
578
+ if (args.fieldSepZero) settings.popt.topt.fieldSep = "\0";
579
+ else if (args.fieldSep !== void 0) settings.popt.topt.fieldSep = args.fieldSep;
580
+ if (args.recordSepZero) settings.popt.topt.recordSep = "\0";
581
+ else if (args.recordSep !== void 0) settings.popt.topt.recordSep = args.recordSep;
582
+ if (args.noPager) settings.popt.topt.pager = "off";
583
+ for (const directive of args.pset) {
584
+ const eq = directive.indexOf("=");
585
+ if (eq >= 0) {
586
+ const key = directive.slice(0, eq);
587
+ const val = directive.slice(eq + 1);
588
+ if (key === "tableattr" || key === "T") settings.popt.topt.tableAttr = val.length > 0 ? val : null;
589
+ else if (key === "pager") {
590
+ if (val === "on" || val === "off" || val === "always") settings.popt.topt.pager = val;
591
+ }
592
+ }
593
+ }
594
+ let dbname = args.database;
595
+ let username = args.user;
596
+ if (args.positional[0] && !dbname) dbname = args.positional[0];
597
+ if (args.positional[1] && !username) username = args.positional[1];
598
+ const argvLayer = { ...args.dashD ?? {} };
599
+ if (args.host !== void 0) argvLayer.host = args.host;
600
+ if (args.port !== void 0) argvLayer.port = args.port;
601
+ if (username !== void 0) argvLayer.user = username;
602
+ if (dbname !== void 0) argvLayer.database = dbname;
603
+ if (args.password !== void 0) argvLayer.password = args.password;
604
+ if (args.replication !== void 0) argvLayer.replication = args.replication;
605
+ let connect;
606
+ if (resolution === void 0) connect = {
607
+ ...baseConnectOpts ?? {
608
+ host: "localhost",
609
+ port: 5432,
610
+ user: "",
611
+ database: "",
612
+ ssl: "prefer"
613
+ },
614
+ ...argvLayer
615
+ };
616
+ else connect = resolveLayeredConnect(argvLayer, resolution, baseConnectOpts);
617
+ return {
618
+ connect,
619
+ preActions: args.actions
620
+ };
621
+ };
622
+ /**
623
+ * Run the layered connection-parameter merge.
624
+ *
625
+ * argv > URI partial > PG* env > .pgpass (password only) > service > libpq defaults
626
+ *
627
+ * `baseConnectOpts` (the legacy `ConnectOptions` argument) is treated as a
628
+ * pre-baked URI partial when the resolution path is used WITHOUT an explicit
629
+ * `uriPartial`. This keeps the door open for callers that still want to
630
+ * pass a fully-defaulted URI shape from `parseConnectionUri`.
631
+ */
632
+ const resolveLayeredConnect = (argvLayer, resolution, legacyBase) => {
633
+ const env = resolution.env ?? {};
634
+ const uriPartial = resolution.uriPartial ?? (legacyBase !== void 0 ? legacyBase : {});
635
+ const serviceName = resolution.serviceName ?? env.PGSERVICE;
636
+ let serviceEntry;
637
+ if (serviceName !== void 0 && serviceName !== "") {
638
+ serviceEntry = resolution.services?.get(serviceName);
639
+ if (serviceEntry === void 0) throw new Error(`definition of service "${serviceName}" not found`);
640
+ }
641
+ const serviceLayer = serviceEntry !== void 0 ? serviceEntryToConnectOptions(serviceEntry) : {};
642
+ const envLayer = envConnectionDefaults(env);
643
+ const defaults = libpqConnectionDefaults(env);
644
+ const layers = [
645
+ argvLayer,
646
+ uriPartial,
647
+ envLayer,
648
+ serviceLayer
649
+ ];
650
+ let merged = mergeConnectOptions(layers, defaults);
651
+ if (merged.password === void 0) {
652
+ const pgpassPwd = lookupPgPass(resolution.pgpassEntries ?? [], {
653
+ host: merged.host,
654
+ port: merged.port,
655
+ database: merged.database !== "" ? merged.database : merged.user,
656
+ user: merged.user
657
+ });
658
+ if (pgpassPwd !== void 0) {
659
+ const pgpassLayer = { password: pgpassPwd };
660
+ merged = mergeConnectOptions([...layers, pgpassLayer], defaults);
661
+ }
662
+ }
663
+ return merged;
664
+ };
665
+ const formatParseError = (err) => err.message;
666
+ //#endregion
667
+ //#region src/psql/index.ts
668
+ /**
669
+ * Embedded TypeScript psql entrypoint.
670
+ *
671
+ * Argv shape mirrors the legacy native-psql call site:
672
+ * argv[0] = connection URI (postgresql://user:pw@host:port/db?sslmode=...)
673
+ * OR an empty string `''` when the caller provides the
674
+ * connection target via libpq flags (-h/-p/-U/-d) and/or PG*
675
+ * env. The downstream layered resolver picks those up just like
676
+ * upstream psql does.
677
+ * argv[1..] = forwarded psql args (parsed by `parseStartupArgs`).
678
+ */
679
+ const runPsql = async (argv, stdio = {}) => {
680
+ const stdin = stdio.stdin ?? process.stdin;
681
+ const stdout = stdio.stdout ?? process.stdout;
682
+ const stderr = stdio.stderr ?? process.stderr;
683
+ const connectionUri = argv[0] ?? "";
684
+ let uriPartial = {};
685
+ let uriService;
686
+ if (connectionUri !== "" && looksLikeConnectionString(connectionUri)) try {
687
+ if (connectionUri.startsWith("postgres://") || connectionUri.startsWith("postgresql://")) {
688
+ uriPartial = parseConnectionUriPartial(connectionUri);
689
+ uriService = parseConnectionUriService(connectionUri);
690
+ } else {
691
+ const parsed = parseConninfo(connectionUri);
692
+ if (typeof parsed._service === "string" && parsed._service.length > 0) uriService = parsed._service;
693
+ delete parsed._service;
694
+ uriPartial = parsed;
695
+ }
696
+ } catch (err) {
697
+ stderr.write(`psql: error: ${err.message}\n`);
698
+ return 2;
699
+ }
700
+ const parsed = parseStartupArgs(argv.slice(1));
701
+ if ("kind" in parsed) {
702
+ if (parsed.kind === "help" || parsed.kind === "version") {
703
+ stdout.write(parsed.message);
704
+ if (!parsed.message.endsWith("\n")) stdout.write("\n");
705
+ return 0;
706
+ }
707
+ stderr.write(`psql: error: ${parsed.message}\n`);
708
+ return 1;
709
+ }
710
+ const vars = createVarStore();
711
+ const settings = defaultSettings(vars);
712
+ applyEnvOverrides(settings, process.env);
713
+ settings.notty = !stdin.isTTY;
714
+ const pgpassEntries = await loadPgPass(void 0, {
715
+ env: process.env,
716
+ stderr
717
+ });
718
+ let services;
719
+ try {
720
+ services = await loadPgServices();
721
+ } catch (err) {
722
+ stderr.write(`psql: error: ${err.message}\n`);
723
+ return 2;
724
+ }
725
+ let resolved;
726
+ try {
727
+ resolved = applyStartupArgs(parsed, settings, void 0, {
728
+ env: process.env,
729
+ uriPartial,
730
+ serviceName: uriService,
731
+ pgpassEntries,
732
+ services
733
+ });
734
+ } catch (err) {
735
+ stderr.write(`psql: error: ${err.message}\n`);
736
+ return 2;
737
+ }
738
+ const { connect: connectOpts, preActions } = resolved;
739
+ let connection;
740
+ try {
741
+ connection = await PgConnection.connect(connectOpts);
742
+ } catch (err) {
743
+ const e = err;
744
+ stderr.write(`psql: error: connection to server failed: ${e.message ?? String(err)}\n`);
745
+ return 2;
746
+ }
747
+ settings.db = connection;
748
+ syncConnectionVars(settings.vars, connection);
749
+ const ctx = {
750
+ settings,
751
+ registry: defaultRegistry(),
752
+ cond: createCondStack(),
753
+ stdin,
754
+ stdout,
755
+ stderr
756
+ };
757
+ try {
758
+ if (!settings.quiet && !settings.notty && preActions.length === 0) writeStartupBanner(connection, stdout);
759
+ await loadPsqlrc(ctx, {
760
+ skip: parsed.noPsqlrc,
761
+ env: process.env
762
+ });
763
+ if (preActions.length > 0) return await runPreActions(ctx, preActions, parsed.singleTransaction);
764
+ return await runMainLoop(ctx);
765
+ } finally {
766
+ try {
767
+ await connection.close();
768
+ } catch {}
769
+ }
770
+ };
771
+ /**
772
+ * Execute the ordered list of `-c` / `-f` actions and return the upstream
773
+ * psql exit code.
774
+ *
775
+ * Upstream `process_psqlrc_and_targets()` (in `startup.c`) plus the
776
+ * dispatcher in `MainLoop()` cooperate to give each switch one of three
777
+ * outcomes:
778
+ *
779
+ * - `-c "SQL or \backslash"`:
780
+ * * a client-side failure (e.g. bad `\copy`) marks the SWITCH itself
781
+ * as failed → the overall exit code is non-zero, even when
782
+ * ON_ERROR_STOP is off and the transaction commits.
783
+ * * a server-side failure follows the same rule.
784
+ *
785
+ * - `-f file`:
786
+ * * `process_file()` returns a success status by default, so a
787
+ * failing statement inside the file does NOT bubble up to the
788
+ * outer exit code (without ON_ERROR_STOP). Only an I/O failure
789
+ * opening the file flips the switch status.
790
+ *
791
+ * - `--single-transaction`:
792
+ * * before the FIRST action, issue `BEGIN`. After the LAST action,
793
+ * issue `COMMIT` (success) or `ROLLBACK` (when ON_ERROR_STOP fired
794
+ * and we stopped early).
795
+ * * Without ON_ERROR_STOP, the transaction commits even when some
796
+ * individual statements failed — the failing statements only
797
+ * influence the exit code (see the `-c` / `-f` distinction above).
798
+ */
799
+ const runPreActions = async (ctx, preActions, singleTransaction) => {
800
+ const { settings, stderr } = ctx;
801
+ let status = 0;
802
+ let beganTransaction = false;
803
+ let earlyStopOnError = false;
804
+ let connectionLost = false;
805
+ if (singleTransaction && settings.db) try {
806
+ await settings.db.execSimple("BEGIN");
807
+ beganTransaction = true;
808
+ } catch (err) {
809
+ const msg = err instanceof Error ? err.message : String(err);
810
+ stderr.write(`psql: error: could not begin transaction: ${msg}\n`);
811
+ return 1;
812
+ }
813
+ for (const action of preActions) {
814
+ if (connectionLost) break;
815
+ if (action.kind === "command") {
816
+ let outcome;
817
+ try {
818
+ outcome = await executeInputString(action.sql, ctx, { print: true });
819
+ } catch (err) {
820
+ const msg = err instanceof Error ? err.message : String(err);
821
+ stderr.write(`psql: ERROR: ${msg}\n`);
822
+ status = 1;
823
+ if (settings.onErrorStop) {
824
+ earlyStopOnError = true;
825
+ break;
826
+ }
827
+ continue;
828
+ }
829
+ if (outcome.connectionLost) {
830
+ connectionLost = true;
831
+ status = 2;
832
+ break;
833
+ }
834
+ if (outcome.hadError || outcome.stoppedOnError) status = 1;
835
+ if (outcome.stoppedOnError) {
836
+ earlyStopOnError = true;
837
+ break;
838
+ }
839
+ } else {
840
+ let contents;
841
+ try {
842
+ contents = await promises.readFile(action.path, "utf8");
843
+ } catch (err) {
844
+ const msg = err instanceof Error ? err.message : String(err);
845
+ stderr.write(`psql: error: ${msg}\n`);
846
+ status = 1;
847
+ if (settings.onErrorStop) {
848
+ earlyStopOnError = true;
849
+ break;
850
+ }
851
+ continue;
852
+ }
853
+ let outcome;
854
+ try {
855
+ outcome = await executeInputString(contents, ctx, { print: true });
856
+ } catch (err) {
857
+ const msg = err instanceof Error ? err.message : String(err);
858
+ stderr.write(`psql: ERROR: ${msg}\n`);
859
+ status = 3;
860
+ if (settings.onErrorStop) {
861
+ earlyStopOnError = true;
862
+ break;
863
+ }
864
+ continue;
865
+ }
866
+ if (outcome.connectionLost) {
867
+ connectionLost = true;
868
+ status = 2;
869
+ break;
870
+ }
871
+ if (outcome.stoppedOnError) {
872
+ status = 3;
873
+ earlyStopOnError = true;
874
+ break;
875
+ }
876
+ }
877
+ }
878
+ if (beganTransaction && settings.db && !connectionLost) {
879
+ const closing = earlyStopOnError ? "ROLLBACK" : "COMMIT";
880
+ try {
881
+ await settings.db.execSimple(closing);
882
+ } catch (err) {
883
+ const msg = err instanceof Error ? err.message : String(err);
884
+ stderr.write(`psql: error: ${closing} failed: ${msg}\n`);
885
+ if (status === 0) status = 1;
886
+ }
887
+ }
888
+ return status;
889
+ };
890
+ const writeStartupBanner = (connection, out) => {
891
+ const serverVersion = connection.parameterStatus("server_version") ?? "unknown";
892
+ out.write(`psql-ts (neon, server ${serverVersion})\n`);
893
+ const tls = connection.getTlsInfo();
894
+ if (tls) {
895
+ const parts = [
896
+ `protocol: ${tls.protocol}`,
897
+ `cipher: ${tls.cipher}`,
898
+ `compression: ${tls.compression}`
899
+ ];
900
+ if (tls.alpn) parts.push(`ALPN: ${tls.alpn}`);
901
+ out.write(`SSL connection (${parts.join(", ")})\n`);
902
+ }
903
+ out.write("Type \"help\" for help.\n\n");
904
+ };
905
+ const KNOWN_QUERY_KEYS = /* @__PURE__ */ new Set([
906
+ "host",
907
+ "hostaddr",
908
+ "port",
909
+ "dbname",
910
+ "user",
911
+ "password",
912
+ "passfile",
913
+ "channel_binding",
914
+ "require_auth",
915
+ "connect_timeout",
916
+ "client_encoding",
917
+ "options",
918
+ "application_name",
919
+ "fallback_application_name",
920
+ "keepalives",
921
+ "keepalives_idle",
922
+ "keepalives_interval",
923
+ "keepalives_count",
924
+ "sslmode",
925
+ "sslnegotiation",
926
+ "sslcompression",
927
+ "sslcert",
928
+ "sslkey",
929
+ "sslcertmode",
930
+ "sslrootcert",
931
+ "sslcrl",
932
+ "sslcrldir",
933
+ "sslkeylogfile",
934
+ "sslsni",
935
+ "requirepeer",
936
+ "ssl_min_protocol_version",
937
+ "ssl_max_protocol_version",
938
+ "krbsrvname",
939
+ "gsslib",
940
+ "gssencmode",
941
+ "service",
942
+ "target_session_attrs",
943
+ "load_balance_hosts",
944
+ "replication"
945
+ ]);
946
+ /**
947
+ * Tokenize a postgres connection URI into raw components.
948
+ *
949
+ * Hand-rolled rather than using `new URL()` because libpq accepts shapes
950
+ * the WHATWG URL parser rejects, e.g. `postgresql://user@` (userinfo with
951
+ * no host), `postgres://:12345/` (port-only), `postgres://uri-user@/db`
952
+ * (userinfo with empty host). The upstream conformance suite in
953
+ * `src/interfaces/libpq/t/001_uri.pl` exercises these forms.
954
+ */
955
+ const tokenizeConnectionUri = (uri) => {
956
+ let rest;
957
+ if (uri.startsWith("postgresql://")) rest = uri.slice(13);
958
+ else if (uri.startsWith("postgres://")) rest = uri.slice(11);
959
+ else throw new Error(`unsupported scheme in URI: ${uri}`);
960
+ let query = "";
961
+ const qIdx = rest.indexOf("?");
962
+ if (qIdx >= 0) {
963
+ query = rest.slice(qIdx + 1);
964
+ rest = rest.slice(0, qIdx);
965
+ }
966
+ let database;
967
+ const pIdx = rest.indexOf("/");
968
+ if (pIdx >= 0) {
969
+ const pathRaw = rest.slice(pIdx + 1);
970
+ database = pathRaw === "" ? void 0 : decodePercent(pathRaw);
971
+ rest = rest.slice(0, pIdx);
972
+ }
973
+ let userinfo;
974
+ const atIdx = rest.lastIndexOf("@");
975
+ if (atIdx >= 0) {
976
+ userinfo = rest.slice(0, atIdx);
977
+ rest = rest.slice(atIdx + 1);
978
+ }
979
+ let user;
980
+ let password;
981
+ if (userinfo !== void 0) {
982
+ const colon = userinfo.indexOf(":");
983
+ if (colon >= 0) {
984
+ user = decodePercent(userinfo.slice(0, colon));
985
+ password = decodePercent(userinfo.slice(colon + 1));
986
+ } else user = decodePercent(userinfo);
987
+ }
988
+ const tuples = splitAuthorityTuples(rest, uri);
989
+ let host;
990
+ let port;
991
+ const hosts = [];
992
+ for (const tuple of tuples) {
993
+ const parsed = parseAuthorityTuple(tuple, uri);
994
+ if (parsed.host !== void 0) hosts.push({
995
+ host: parsed.host,
996
+ port: parsed.port
997
+ });
998
+ }
999
+ if (hosts.length > 0) {
1000
+ host = hosts[0].host;
1001
+ port = hosts[0].port;
1002
+ }
1003
+ const queryMap = parseQuery(query);
1004
+ return {
1005
+ user,
1006
+ password,
1007
+ host,
1008
+ port,
1009
+ ...hosts.length > 1 ? { hosts } : {},
1010
+ database,
1011
+ query: queryMap
1012
+ };
1013
+ };
1014
+ /**
1015
+ * Split a multi-host authority string into one tuple per top-level comma.
1016
+ * IPv6 bracket regions are atomic — commas inside `[…]` don't split.
1017
+ *
1018
+ * Examples:
1019
+ * `h1:5432,h2,h3:5434` -> ['h1:5432','h2','h3:5434']
1020
+ * `[::1]:5432,[2001:db8::1]` -> ['[::1]:5432','[2001:db8::1]']
1021
+ * `h1` -> ['h1']
1022
+ * `` -> [''] (single empty tuple — caller may
1023
+ * treat as no-host)
1024
+ */
1025
+ const splitAuthorityTuples = (rest, uri) => {
1026
+ if (rest === "") return [""];
1027
+ const tuples = [];
1028
+ let start = 0;
1029
+ let i = 0;
1030
+ while (i < rest.length) {
1031
+ const ch = rest[i];
1032
+ if (ch === "[") {
1033
+ const closeIdx = rest.indexOf("]", i);
1034
+ if (closeIdx < 0) throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
1035
+ i = closeIdx + 1;
1036
+ continue;
1037
+ }
1038
+ if (ch === ",") {
1039
+ tuples.push(rest.slice(start, i));
1040
+ i += 1;
1041
+ start = i;
1042
+ continue;
1043
+ }
1044
+ i += 1;
1045
+ }
1046
+ tuples.push(rest.slice(start));
1047
+ return tuples;
1048
+ };
1049
+ const parseAuthorityTuple = (tuple, uri) => {
1050
+ if (tuple === "") return {};
1051
+ if (tuple.startsWith("[")) {
1052
+ const closeIdx = tuple.indexOf("]");
1053
+ if (closeIdx < 0) throw new Error(`missing matching "]" in IPv6 host address: ${uri}`);
1054
+ const host = tuple.slice(1, closeIdx);
1055
+ if (host === "") throw new Error(`IPv6 host address may not be empty: ${uri}`);
1056
+ const after = tuple.slice(closeIdx + 1);
1057
+ if (after === "") return { host };
1058
+ if (after.startsWith(":")) return {
1059
+ host,
1060
+ port: after.slice(1)
1061
+ };
1062
+ throw new Error(`unexpected characters after IPv6 host address in URI: ${uri}`);
1063
+ }
1064
+ const colon = tuple.indexOf(":");
1065
+ if (colon >= 0) return {
1066
+ host: decodePercent(tuple.slice(0, colon)),
1067
+ port: tuple.slice(colon + 1)
1068
+ };
1069
+ return { host: decodePercent(tuple) };
1070
+ };
1071
+ const parseQuery = (raw) => {
1072
+ const out = /* @__PURE__ */ new Map();
1073
+ if (raw === "") return out;
1074
+ for (const segment of raw.split("&")) {
1075
+ if (segment === "") continue;
1076
+ const eq = segment.indexOf("=");
1077
+ if (eq < 0) throw new Error(`missing "=" after "${segment.trim()}" in connection info string`);
1078
+ const keyRaw = segment.slice(0, eq);
1079
+ const valueRaw = segment.slice(eq + 1);
1080
+ if (valueRaw.includes("=")) throw new Error(`extra "=" in query parameter "${decodePercent(keyRaw).trim()}"`);
1081
+ const key = decodePercent(keyRaw).trim();
1082
+ const value = decodePercent(valueRaw).trim();
1083
+ if (key === "") continue;
1084
+ if (!KNOWN_QUERY_KEYS.has(key)) throw new Error(`invalid URI query parameter: "${key}"`);
1085
+ out.set(key, value);
1086
+ }
1087
+ return out;
1088
+ };
1089
+ /**
1090
+ * Percent-decode a URI component. libpq strictly validates percent-encoding:
1091
+ * - `%XX` must be two hex digits
1092
+ * - bare `%` or `%X` is invalid
1093
+ * - `%00` is forbidden (NUL bytes can't appear in connection params)
1094
+ *
1095
+ * `decodeURIComponent` throws URIError on malformed escapes — we surface that
1096
+ * as a clear Error. It accepts `%00` (returns `\0`); we explicitly reject.
1097
+ */
1098
+ const decodePercent = (s) => {
1099
+ let decoded;
1100
+ try {
1101
+ decoded = decodeURIComponent(s);
1102
+ } catch {
1103
+ throw new Error(`invalid percent-encoded token in URI: ${s}`);
1104
+ }
1105
+ if (decoded.includes("\0")) throw new Error(`forbidden NUL byte (%00) in URI: ${s}`);
1106
+ return decoded;
1107
+ };
1108
+ const parseConnectionUri = (uri) => {
1109
+ const raw = tokenizeConnectionUri(uri);
1110
+ const queryUser = raw.query.get("user");
1111
+ const queryPassword = raw.query.get("password");
1112
+ const queryPort = raw.query.get("port");
1113
+ const queryDbname = raw.query.get("dbname");
1114
+ const queryHost = raw.query.get("host");
1115
+ const hostsTuples = computeHostsTuples({
1116
+ rawHost: raw.host,
1117
+ rawPort: raw.port,
1118
+ rawAuthorityHosts: raw.hosts,
1119
+ queryHost,
1120
+ queryPort
1121
+ });
1122
+ const host = hostsTuples.length > 0 && hostsTuples[0].host !== "" ? hostsTuples[0].host : "localhost";
1123
+ const port = hostsTuples.length > 0 ? hostsTuples[0].port : 5432;
1124
+ const user = queryUser !== void 0 && queryUser !== "" ? queryUser : raw.user !== void 0 && raw.user !== "" ? raw.user : process.env.USER ?? "";
1125
+ const password = queryPassword ?? raw.password;
1126
+ const database = queryDbname ?? raw.database ?? user;
1127
+ let ssl = normalizeSslMode(raw.query.get("sslmode") ?? null);
1128
+ const channelBinding = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
1129
+ validateGssEncMode(raw.query.get("gssencmode") ?? null);
1130
+ const options = raw.query.get("options");
1131
+ const applicationName = raw.query.get("application_name") ?? "psql";
1132
+ const replication = normalizeReplication(raw.query.get("replication") ?? null);
1133
+ const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
1134
+ const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
1135
+ const sslcert = nonEmpty(raw.query.get("sslcert"));
1136
+ const sslkey = nonEmpty(raw.query.get("sslkey"));
1137
+ const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
1138
+ const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
1139
+ const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
1140
+ const sslcrl = nonEmpty(raw.query.get("sslcrl"));
1141
+ const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
1142
+ const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
1143
+ const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
1144
+ const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
1145
+ const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
1146
+ const requirepeer = nonEmpty(raw.query.get("requirepeer"));
1147
+ if (sslrootcert === "system" && ssl !== "verify-full") ssl = "verify-full";
1148
+ const hostaddr = nonEmpty(raw.query.get("hostaddr"));
1149
+ const sslMinProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
1150
+ const sslMaxProtocolVersion = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
1151
+ assertTlsProtocolRange(sslMinProtocolVersion, sslMaxProtocolVersion);
1152
+ assertTlsMaxProtocolSupported(sslMaxProtocolVersion);
1153
+ assertSslNegotiationModeCompatible(ssl, sslnegotiation);
1154
+ return {
1155
+ host,
1156
+ port,
1157
+ user,
1158
+ password,
1159
+ database,
1160
+ ssl,
1161
+ channelBinding,
1162
+ applicationName,
1163
+ options,
1164
+ ...sslnegotiation !== void 0 ? { sslnegotiation } : {},
1165
+ ...hostaddr !== void 0 ? { hostaddr } : {},
1166
+ ...replication !== void 0 ? { replication } : {},
1167
+ ...hostsTuples.length > 1 ? { hosts: hostsTuples.map((t) => ({
1168
+ host: t.host,
1169
+ port: t.port
1170
+ })) } : {},
1171
+ ...targetSessionAttrs !== void 0 ? { targetSessionAttrs } : {},
1172
+ ...loadBalanceHosts !== void 0 ? { loadBalanceHosts } : {},
1173
+ ...sslcert !== void 0 ? { sslcert } : {},
1174
+ ...sslkey !== void 0 ? { sslkey } : {},
1175
+ ...sslcertmode !== void 0 ? { sslcertmode } : {},
1176
+ ...sslrootcert !== void 0 ? { sslrootcert } : {},
1177
+ ...sslcrl !== void 0 ? { sslcrl } : {},
1178
+ ...sslcrldir !== void 0 ? { sslcrldir } : {},
1179
+ ...sslkeylogfile !== void 0 ? { sslkeylogfile } : {},
1180
+ ...sslsni !== void 0 ? { sslsni } : {},
1181
+ ...keepalives !== void 0 ? { keepalives } : {},
1182
+ ...keepalivesIdle !== void 0 ? { keepalivesIdle } : {},
1183
+ ...requirepeer !== void 0 ? { requirepeer } : {},
1184
+ ...sslMinProtocolVersion !== void 0 ? { sslMinProtocolVersion } : {},
1185
+ ...sslMaxProtocolVersion !== void 0 ? { sslMaxProtocolVersion } : {}
1186
+ };
1187
+ };
1188
+ /**
1189
+ * Parse a libpq 0/1 boolean connection parameter (`sslsni`, `keepalives`).
1190
+ * libpq's `parse_bool_with_len` accepts `1`/`0`, `true`/`false`, `yes`/`no`,
1191
+ * `on`/`off` (case-insensitive). Returns `undefined` for unset / empty /
1192
+ * unrecognised so the caller falls back to libpq's default (enabled).
1193
+ */
1194
+ const parseLibpqBool = (raw) => {
1195
+ if (raw === void 0 || raw === "") return void 0;
1196
+ switch (raw.toLowerCase()) {
1197
+ case "1":
1198
+ case "true":
1199
+ case "yes":
1200
+ case "on": return true;
1201
+ case "0":
1202
+ case "false":
1203
+ case "no":
1204
+ case "off": return false;
1205
+ default: return;
1206
+ }
1207
+ };
1208
+ /**
1209
+ * Parse `keepalives_idle` (seconds, non-negative integer). Returns the value
1210
+ * in seconds, or `undefined` if unset / malformed (the wire layer converts to
1211
+ * milliseconds for `socket.setKeepAlive`'s `initialDelay`).
1212
+ */
1213
+ const parseKeepalivesIdle = (raw) => {
1214
+ if (raw === void 0 || raw === "") return void 0;
1215
+ const n = Number.parseInt(raw, 10);
1216
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
1217
+ };
1218
+ const nonEmpty = (v) => v === void 0 || v === "" ? void 0 : v;
1219
+ /**
1220
+ * Resolve the final {host, port}[] list for a URI:
1221
+ *
1222
+ * 1. Start from the authority. `?host=`/`?port=` query overrides take
1223
+ * precedence (libpq semantics).
1224
+ * 2. If `host=h1,h2,…` is supplied, parse the comma-list. Ports come from
1225
+ * `port=p1,p2,…`; must match the host count or be a single value
1226
+ * (broadcast to all hosts).
1227
+ * 3. If only the authority had multi-host tuples (`postgresql://h1,h2/db`),
1228
+ * use those.
1229
+ * 4. Otherwise fall back to single-host.
1230
+ *
1231
+ * Validates every port is in 1..65535 and surfaces a clear error otherwise.
1232
+ */
1233
+ const computeHostsTuples = (input) => {
1234
+ const { rawHost, rawPort, rawAuthorityHosts, queryHost, queryPort } = input;
1235
+ if (queryHost !== void 0 && queryHost !== "") {
1236
+ const hosts = queryHost.split(",").map((h) => h.trim());
1237
+ const portList = queryPort !== void 0 && queryPort !== "" ? queryPort.split(",").map((p) => p.trim()) : null;
1238
+ 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`);
1239
+ return hosts.map((h, idx) => {
1240
+ let portStr;
1241
+ if (portList !== null) portStr = portList.length === 1 ? portList[0] : portList[idx];
1242
+ else portStr = rawPort;
1243
+ return {
1244
+ host: h,
1245
+ port: parsePort(portStr)
1246
+ };
1247
+ });
1248
+ }
1249
+ if (rawAuthorityHosts !== void 0 && rawAuthorityHosts.length > 0) {
1250
+ const portList = queryPort !== void 0 && queryPort !== "" ? queryPort.split(",").map((p) => p.trim()) : null;
1251
+ 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`);
1252
+ return rawAuthorityHosts.map((t, idx) => ({
1253
+ host: t.host,
1254
+ port: parsePort(portList !== null ? portList.length === 1 ? portList[0] : portList[idx] : t.port)
1255
+ }));
1256
+ }
1257
+ return [{
1258
+ host: rawHost !== void 0 && rawHost !== "" ? rawHost : "",
1259
+ port: parsePort(queryPort ?? rawPort)
1260
+ }];
1261
+ };
1262
+ const parsePort = (raw) => {
1263
+ if (raw === void 0 || raw === "") return 5432;
1264
+ if (/^\d/.test(raw) && !/^\d+$/.test(raw)) throw new Error(`invalid integer value "${raw}" for connection option "port"`);
1265
+ const p = Number.parseInt(raw, 10);
1266
+ if (!Number.isFinite(p) || p <= 0 || p > 65535) throw new Error(`invalid port: ${raw}`);
1267
+ return p;
1268
+ };
1269
+ /**
1270
+ * Parse a libpq-style conninfo string into a `Partial<ConnectOptions>`.
1271
+ *
1272
+ * The grammar is roughly `key = value` pairs separated by whitespace, where
1273
+ * values may be quoted with single quotes (libpq's `\'` is honored as an
1274
+ * embedded single-quote, but we don't model the full backslash-escape
1275
+ * universe — there's no test corpus that exercises it). Unknown keys are
1276
+ * rejected so a typo like `replicate=database` produces a clear error
1277
+ * rather than silently dropping the parameter.
1278
+ *
1279
+ * Recognised keys mirror the URI-side `KNOWN_QUERY_KEYS` allowlist plus
1280
+ * the authority-style keys (`host`, `port`, `user`, `dbname`, `password`).
1281
+ * `replication` is normalised through `normalizeReplication` for libpq
1282
+ * value-set compatibility.
1283
+ *
1284
+ * Out of scope: percent-decoding (conninfo strings are NOT percent-encoded),
1285
+ * `service` resolution from `pg_service.conf`, `passfile` resolution.
1286
+ */
1287
+ const parseConninfo = (input) => {
1288
+ const out = {};
1289
+ let i = 0;
1290
+ const n = input.length;
1291
+ while (i < n) {
1292
+ while (i < n && /\s/.test(input[i])) i += 1;
1293
+ if (i >= n) break;
1294
+ const keyStart = i;
1295
+ while (i < n && input[i] !== "=" && !/\s/.test(input[i])) i += 1;
1296
+ const key = input.slice(keyStart, i).toLowerCase();
1297
+ if (key === "") break;
1298
+ while (i < n && /\s/.test(input[i])) i += 1;
1299
+ if (i >= n || input[i] !== "=") throw new Error(`missing "=" after "${key}" in conninfo string`);
1300
+ i += 1;
1301
+ while (i < n && /\s/.test(input[i])) i += 1;
1302
+ let value;
1303
+ if (i < n && input[i] === "'") {
1304
+ i += 1;
1305
+ const parts = [];
1306
+ while (i < n && input[i] !== "'") if (input[i] === "\\" && i + 1 < n) {
1307
+ parts.push(input[i + 1]);
1308
+ i += 2;
1309
+ } else {
1310
+ parts.push(input[i]);
1311
+ i += 1;
1312
+ }
1313
+ if (i >= n) throw new Error(`unterminated single quote in conninfo string for key "${key}"`);
1314
+ i += 1;
1315
+ value = parts.join("");
1316
+ } else {
1317
+ const valStart = i;
1318
+ while (i < n && !/\s/.test(input[i])) i += 1;
1319
+ value = input.slice(valStart, i);
1320
+ }
1321
+ applyConninfoPair(out, key, value);
1322
+ }
1323
+ const hostList = out._hostList;
1324
+ const portList = out._portList;
1325
+ if (hostList !== void 0 && hostList.length > 0) {
1326
+ 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`);
1327
+ if (hostList.length > 1) out.hosts = hostList.map((h, idx) => ({
1328
+ host: h,
1329
+ port: portList === void 0 ? out.port ?? 5432 : portList.length === 1 ? parsePort(portList[0]) : parsePort(portList[idx])
1330
+ }));
1331
+ }
1332
+ delete out._hostList;
1333
+ delete out._portList;
1334
+ assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
1335
+ assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
1336
+ return out;
1337
+ };
1338
+ const applyConninfoPair = (out, key, value) => {
1339
+ switch (key) {
1340
+ case "host":
1341
+ if (value.includes(",")) {
1342
+ out._hostList = value.split(",").map((h) => h.trim());
1343
+ out.host = out._hostList[0];
1344
+ } else {
1345
+ out.host = value;
1346
+ out._hostList = void 0;
1347
+ }
1348
+ return;
1349
+ case "port":
1350
+ if (value.includes(",")) {
1351
+ out._portList = value.split(",").map((p) => p.trim());
1352
+ out.port = parsePort(out._portList[0]);
1353
+ } else {
1354
+ out.port = parsePort(value);
1355
+ out._portList = void 0;
1356
+ }
1357
+ return;
1358
+ case "user":
1359
+ out.user = value;
1360
+ return;
1361
+ case "password":
1362
+ out.password = value;
1363
+ return;
1364
+ case "dbname":
1365
+ out.database = value;
1366
+ return;
1367
+ case "application_name":
1368
+ out.applicationName = value;
1369
+ return;
1370
+ case "sslmode":
1371
+ out.ssl = normalizeSslMode(value);
1372
+ return;
1373
+ case "channel_binding": {
1374
+ const cb = normalizeChannelBinding(value);
1375
+ if (cb !== void 0) out.channelBinding = cb;
1376
+ return;
1377
+ }
1378
+ case "require_auth": {
1379
+ const ra = normalizeRequireAuth(value);
1380
+ if (ra !== void 0) out.requireAuth = ra;
1381
+ return;
1382
+ }
1383
+ case "connect_timeout": {
1384
+ const t = Number.parseInt(value, 10);
1385
+ if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1386
+ return;
1387
+ }
1388
+ case "client_encoding":
1389
+ out.clientEncoding = value;
1390
+ return;
1391
+ case "options":
1392
+ out.options = value;
1393
+ return;
1394
+ case "replication": {
1395
+ const rep = normalizeReplication(value);
1396
+ if (rep !== void 0) out.replication = rep;
1397
+ return;
1398
+ }
1399
+ case "target_session_attrs": {
1400
+ const tsa = normalizeTargetSessionAttrs(value);
1401
+ if (tsa !== void 0) out.targetSessionAttrs = tsa;
1402
+ return;
1403
+ }
1404
+ case "load_balance_hosts": {
1405
+ const lbh = normalizeLoadBalanceHosts(value);
1406
+ if (lbh !== void 0) out.loadBalanceHosts = lbh;
1407
+ return;
1408
+ }
1409
+ case "gssencmode":
1410
+ validateGssEncMode(value);
1411
+ return;
1412
+ case "sslcert":
1413
+ if (value !== "") out.sslcert = value;
1414
+ return;
1415
+ case "sslkey":
1416
+ if (value !== "") out.sslkey = value;
1417
+ return;
1418
+ case "sslcertmode": {
1419
+ const cm = normalizeSslCertMode(value);
1420
+ if (cm !== void 0) out.sslcertmode = cm;
1421
+ return;
1422
+ }
1423
+ case "sslnegotiation": {
1424
+ const sn = normalizeSslNegotiation(value);
1425
+ if (sn !== void 0) out.sslnegotiation = sn;
1426
+ return;
1427
+ }
1428
+ case "sslrootcert":
1429
+ if (value !== "") out.sslrootcert = value;
1430
+ return;
1431
+ case "sslcrl":
1432
+ if (value !== "") out.sslcrl = value;
1433
+ return;
1434
+ case "sslcrldir":
1435
+ if (value !== "") out.sslcrldir = value;
1436
+ return;
1437
+ case "sslkeylogfile":
1438
+ if (value !== "") out.sslkeylogfile = value;
1439
+ return;
1440
+ case "hostaddr":
1441
+ if (value !== "") out.hostaddr = value;
1442
+ return;
1443
+ case "ssl_min_protocol_version": {
1444
+ const v = normalizeTlsProtocolVersion(value === "" ? void 0 : value, "ssl_min_protocol_version");
1445
+ if (v !== void 0) out.sslMinProtocolVersion = v;
1446
+ return;
1447
+ }
1448
+ case "ssl_max_protocol_version": {
1449
+ const v = normalizeTlsProtocolVersion(value === "" ? void 0 : value, "ssl_max_protocol_version");
1450
+ if (v !== void 0) out.sslMaxProtocolVersion = v;
1451
+ return;
1452
+ }
1453
+ case "sslsni": {
1454
+ const b = parseLibpqBool(value);
1455
+ if (b !== void 0) out.sslsni = b;
1456
+ return;
1457
+ }
1458
+ case "keepalives": {
1459
+ const b = parseLibpqBool(value);
1460
+ if (b !== void 0) out.keepalives = b;
1461
+ return;
1462
+ }
1463
+ case "keepalives_idle": {
1464
+ const n = parseKeepalivesIdle(value);
1465
+ if (n !== void 0) out.keepalivesIdle = n;
1466
+ return;
1467
+ }
1468
+ case "requirepeer":
1469
+ if (value !== "") out.requirepeer = value;
1470
+ return;
1471
+ case "passfile":
1472
+ case "sslcompression":
1473
+ case "krbsrvname":
1474
+ case "gsslib":
1475
+ case "fallback_application_name":
1476
+ case "keepalives_interval":
1477
+ case "keepalives_count": return;
1478
+ case "service":
1479
+ out._service = value;
1480
+ return;
1481
+ default: throw new Error(`invalid conninfo key: "${key}"`);
1482
+ }
1483
+ };
1484
+ /**
1485
+ * Heuristic: does the `-d` value look like a connection URI or a conninfo
1486
+ * string (vs. a bare database name)? Mirrors libpq's
1487
+ * `recognized_connection_string()` test.
1488
+ */
1489
+ const looksLikeConnectionString = (s) => {
1490
+ if (s.startsWith("postgresql://") || s.startsWith("postgres://")) return true;
1491
+ const eq = s.indexOf("=");
1492
+ if (eq < 0) return false;
1493
+ const head = s.slice(0, eq);
1494
+ return !/\s/.test(head);
1495
+ };
1496
+ const normalizeSslMode = (raw) => {
1497
+ if (raw === null) return "prefer";
1498
+ const value = raw.toLowerCase();
1499
+ switch (value) {
1500
+ case "disable":
1501
+ case "allow":
1502
+ case "prefer":
1503
+ case "require":
1504
+ case "verify-ca":
1505
+ case "verify-full": return value;
1506
+ default: throw new Error(`invalid sslmode value: "${raw}"`);
1507
+ }
1508
+ };
1509
+ /**
1510
+ * libpq's accepted TLS protocol-version names, in ascending order. The
1511
+ * index doubles as the comparison key for the `min > max` check. Matching
1512
+ * is case-insensitive on input (libpq lowercases before comparing) but we
1513
+ * keep the canonical mixed-case spelling Node's `tls` module expects for
1514
+ * `minVersion` / `maxVersion`.
1515
+ */
1516
+ const TLS_PROTOCOL_VERSIONS = [
1517
+ "TLSv1",
1518
+ "TLSv1.1",
1519
+ "TLSv1.2",
1520
+ "TLSv1.3"
1521
+ ];
1522
+ /**
1523
+ * Validate and canonicalise a `ssl_{min,max}_protocol_version` value. Returns
1524
+ * the canonical spelling (`TLSv1.2` etc.) or `undefined` for empty / unset.
1525
+ * Throws libpq's `invalid <key> value: "<raw>"` wording on a malformed value.
1526
+ */
1527
+ const normalizeTlsProtocolVersion = (raw, key) => {
1528
+ if (raw === void 0 || raw === "") return void 0;
1529
+ const match = TLS_PROTOCOL_VERSIONS.find((v) => v.toLowerCase() === raw.toLowerCase());
1530
+ if (match === void 0) throw new Error(`invalid ${key} value: "${raw}"`);
1531
+ return match;
1532
+ };
1533
+ /**
1534
+ * Reject a `ssl_max_protocol_version` ceiling below TLSv1.2. TLS 1.0/1.1 are
1535
+ * disabled in Node's bundled OpenSSL, so capping the ceiling there leaves no
1536
+ * negotiable protocol and the handshake otherwise fails with an opaque
1537
+ * `ERR_SSL_NO_PROTOCOLS_AVAILABLE` — surface an actionable message at parse
1538
+ * time instead. Only the MAX is gated: a low *min*
1539
+ * (`ssl_min_protocol_version=TLSv1.1`) is harmless because Node still
1540
+ * negotiates the highest mutually-supported version (1.2/1.3), exactly as
1541
+ * libpq does on a modern OpenSSL. Called AFTER {@link assertTlsProtocolRange}
1542
+ * so an inverted range (min > max) reports the range error first, matching
1543
+ * libpq's ordering.
1544
+ */
1545
+ const assertTlsMaxProtocolSupported = (max) => {
1546
+ 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`);
1547
+ };
1548
+ /**
1549
+ * Reject a `ssl_min_protocol_version` that is higher than
1550
+ * `ssl_max_protocol_version`, matching libpq's
1551
+ * `ssl_min_protocol_version must be <= ssl_max_protocol_version` diagnostic.
1552
+ * Both arguments must already be canonicalised by
1553
+ * {@link normalizeTlsProtocolVersion}.
1554
+ */
1555
+ const assertTlsProtocolRange = (min, max) => {
1556
+ if (min === void 0 || max === void 0) return;
1557
+ if (TLS_PROTOCOL_VERSIONS.indexOf(min) > TLS_PROTOCOL_VERSIONS.indexOf(max)) throw new Error(`ssl_min_protocol_version must be <= ssl_max_protocol_version`);
1558
+ };
1559
+ const normalizeChannelBinding = (raw) => {
1560
+ if (raw === null || raw === "") return void 0;
1561
+ const value = raw.toLowerCase();
1562
+ switch (value) {
1563
+ case "disable":
1564
+ case "prefer":
1565
+ case "require": return value;
1566
+ default: throw new Error(`invalid channel_binding value: "${raw}"`);
1567
+ }
1568
+ };
1569
+ /**
1570
+ * Validate libpq's `gssencmode` (GSSAPI transport encryption).
1571
+ *
1572
+ * This client has NO GSSAPI support: GSS-API `gss_wrap`/`gss_unwrap` would
1573
+ * require a native Kerberos addon (e.g. the `kerberos` npm), which the
1574
+ * embedded psql deliberately avoids (pure-TS, zero native bindings — the
1575
+ * same reason the line editor is hand-rolled). `node-postgres` doesn't
1576
+ * support it either. So:
1577
+ * - `disable` / `prefer` — accepted and ignored: neither needs GSS
1578
+ * (`prefer` means "try GSS, else fall back", and falling back to the
1579
+ * non-GSS path is exactly what we always do).
1580
+ * - `require` — rejected with a clear diagnostic; we cannot satisfy it.
1581
+ * - anything else — `invalid gssencmode value`.
1582
+ * We recognise the parameter (rather than rejecting it as an unknown key)
1583
+ * so the many tools that always append `gssencmode=...` to a URI keep
1584
+ * working against Neon.
1585
+ */
1586
+ const validateGssEncMode = (raw) => {
1587
+ if (raw === null || raw === "") return;
1588
+ const value = raw.toLowerCase();
1589
+ if (value === "disable" || value === "prefer") return;
1590
+ if (value === "require") throw new Error("gssencmode=require is not supported: this client has no GSSAPI support");
1591
+ throw new Error(`invalid gssencmode value: "${raw}"`);
1592
+ };
1593
+ /**
1594
+ * Parse libpq's `sslcertmode` value (`disable` / `allow` / `require`).
1595
+ * Empty / unset returns `undefined` so the wire-layer default (`allow`)
1596
+ * applies. A malformed value throws libpq's
1597
+ * `invalid sslcertmode value: "<raw>"` diagnostic.
1598
+ */
1599
+ const normalizeSslCertMode = (raw) => {
1600
+ if (raw === null || raw === "") return void 0;
1601
+ const value = raw.toLowerCase();
1602
+ switch (value) {
1603
+ case "disable":
1604
+ case "allow":
1605
+ case "require": return value;
1606
+ default: throw new Error(`invalid sslcertmode value: "${raw}"`);
1607
+ }
1608
+ };
1609
+ /**
1610
+ * Parse libpq's `sslnegotiation` value (`postgres` / `direct`). Empty / unset
1611
+ * returns `undefined` so the wire-layer default (`postgres`, the classic
1612
+ * SSLRequest flow) applies. A malformed value throws libpq's
1613
+ * `invalid sslnegotiation value: "<raw>"` diagnostic.
1614
+ */
1615
+ const normalizeSslNegotiation = (raw) => {
1616
+ if (raw === null || raw === "") return void 0;
1617
+ const value = raw.toLowerCase();
1618
+ switch (value) {
1619
+ case "postgres":
1620
+ case "direct": return value;
1621
+ default: throw new Error(`invalid sslnegotiation value: "${raw}"`);
1622
+ }
1623
+ };
1624
+ /**
1625
+ * libpq constraint: `sslnegotiation=direct` may only be used with an encrypted
1626
+ * sslmode (`require` / `verify-ca` / `verify-full`). Direct SSL starts the TLS
1627
+ * handshake immediately with no plaintext fallback, so a "weak" mode that could
1628
+ * end up unencrypted (`disable` / `allow` / `prefer`) is rejected with libpq's
1629
+ * exact `pqConnectOptions2` wording. No-op unless `sslnegotiation` is `direct`.
1630
+ */
1631
+ const assertSslNegotiationModeCompatible = (ssl, sslnegotiation) => {
1632
+ if (sslnegotiation !== "direct") return;
1633
+ if (ssl === "require" || ssl === "verify-ca" || ssl === "verify-full") return;
1634
+ throw new Error(`weak sslmode "${ssl}" may not be used with sslnegotiation=direct`);
1635
+ };
1636
+ const VALID_REQUIRE_AUTH_METHODS = /* @__PURE__ */ new Set([
1637
+ "password",
1638
+ "md5",
1639
+ "gss",
1640
+ "sspi",
1641
+ "scram-sha-256",
1642
+ "creds",
1643
+ "none"
1644
+ ]);
1645
+ /**
1646
+ * Parse libpq's `require_auth` value: a comma-separated list of method
1647
+ * names where each entry may be prefixed with `!` to negate. Mixing
1648
+ * positive and negative entries is forbidden (libpq matches this).
1649
+ *
1650
+ * Returns `undefined` for empty input so the wire-layer default applies.
1651
+ * Throws on invalid syntax with libpq-parity wording, surfaced via the
1652
+ * outer `psql: error: ...` channel.
1653
+ */
1654
+ const normalizeRequireAuth = (raw) => {
1655
+ if (raw === null || raw === "") return void 0;
1656
+ const tokens = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1657
+ if (tokens.length === 0) return void 0;
1658
+ const methods = /* @__PURE__ */ new Set();
1659
+ let polarity = null;
1660
+ for (const token of tokens) {
1661
+ const isNeg = token.startsWith("!");
1662
+ const name = (isNeg ? token.slice(1) : token).toLowerCase();
1663
+ if (!VALID_REQUIRE_AUTH_METHODS.has(name)) throw new Error(`invalid require_auth method: "${token}"`);
1664
+ if (polarity === null) polarity = isNeg;
1665
+ else if (polarity !== isNeg) throw new Error("require_auth methods cannot mix positive and negative entries");
1666
+ methods.add(name);
1667
+ }
1668
+ return {
1669
+ methods,
1670
+ negated: polarity ?? false
1671
+ };
1672
+ };
1673
+ /**
1674
+ * Accept the libpq-spec set for `target_session_attrs`. Aliases `read-write`/
1675
+ * `primary` and `read-only`/`standby` are kept distinct because the wire
1676
+ * layer treats the canonical four values identically — we only normalise
1677
+ * unknown / empty inputs to `undefined` so the wire-layer default ('any')
1678
+ * applies. Throws on unrecognised values, matching libpq behaviour.
1679
+ */
1680
+ const normalizeTargetSessionAttrs = (raw) => {
1681
+ if (raw === null || raw === "") return void 0;
1682
+ const value = raw.toLowerCase();
1683
+ switch (value) {
1684
+ case "any":
1685
+ case "read-write":
1686
+ case "read-only":
1687
+ case "primary":
1688
+ case "standby":
1689
+ case "prefer-standby": return value;
1690
+ default: throw new Error(`invalid value for "target_session_attrs": "${raw}"`);
1691
+ }
1692
+ };
1693
+ /**
1694
+ * Accept the libpq-spec set for `load_balance_hosts`:
1695
+ * - `disable` (default) — preserve list order
1696
+ * - `random` — shuffle before iteration
1697
+ *
1698
+ * Unknown values throw; empty / unset returns `undefined` so the wire layer
1699
+ * default ('disable') applies.
1700
+ */
1701
+ const normalizeLoadBalanceHosts = (raw) => {
1702
+ if (raw === null || raw === "") return void 0;
1703
+ const value = raw.toLowerCase();
1704
+ if (value === "disable" || value === "random") return value;
1705
+ throw new Error(`invalid value for "load_balance_hosts": "${raw}"`);
1706
+ };
1707
+ /**
1708
+ * libpq accepts a wide set of "truthy" values for `replication`:
1709
+ * - `true` / `on` / `yes` / `1` → physical replication (mapped to `true`)
1710
+ * - `false` / `off` / `no` / `0` → not a walsender (no replication mode)
1711
+ * - `database` → logical replication on that DB
1712
+ *
1713
+ * Returns `undefined` when no value was supplied or when it explicitly
1714
+ * disables replication; throws for unrecognised input (matching libpq's
1715
+ * `"invalid <…> value"` semantics so users see a clear error rather than
1716
+ * silently sending an unexpected startup-message parameter).
1717
+ */
1718
+ const normalizeReplication = (raw) => {
1719
+ if (raw === null || raw === "") return void 0;
1720
+ const value = raw.toLowerCase();
1721
+ if (value === "database") return "database";
1722
+ if (value === "true" || value === "on" || value === "yes" || value === "1") return "true";
1723
+ if (value === "false" || value === "off" || value === "no" || value === "0") return;
1724
+ throw new Error(`invalid value for "replication": "${raw}"`);
1725
+ };
1726
+ /**
1727
+ * Service name extracted from a connection URI's `?service=` query
1728
+ * parameter, if any. Surfaced alongside `parseConnectionUriPartial` so the
1729
+ * caller can route the lookup into `applyStartupArgs` without re-parsing
1730
+ * the URI.
1731
+ */
1732
+ const parseConnectionUriService = (uri) => {
1733
+ const value = tokenizeConnectionUri(uri).query.get("service");
1734
+ return value === void 0 || value === "" ? void 0 : value;
1735
+ };
1736
+ /**
1737
+ * Parse a URI into a `Partial<ConnectOptions>` containing only the fields
1738
+ * the URI explicitly supplied. Returned shape:
1739
+ *
1740
+ * - missing fields are absent (no `undefined` placeholders)
1741
+ * - `host`/`port` are populated only when the URI authority or `?host=`
1742
+ * specified them
1743
+ * - `user`/`password`/`database`/`ssl`/`channelBinding`/... follow the
1744
+ * same rule — present iff explicitly set
1745
+ *
1746
+ * This is the building block for the layered merge in `applyStartupArgs`.
1747
+ * The full-defaults variant `parseConnectionUri` remains the right choice
1748
+ * for callers that want a complete `ConnectOptions` (e.g. the existing
1749
+ * `-d URI` path); it's kept untouched for back-compat.
1750
+ */
1751
+ const parseConnectionUriPartial = (uri) => {
1752
+ const raw = tokenizeConnectionUri(uri);
1753
+ const queryUser = raw.query.get("user");
1754
+ const queryPassword = raw.query.get("password");
1755
+ const queryPort = raw.query.get("port");
1756
+ const queryDbname = raw.query.get("dbname");
1757
+ const queryHost = raw.query.get("host");
1758
+ const hostsTuples = computeHostsTuples({
1759
+ rawHost: raw.host,
1760
+ rawPort: raw.port,
1761
+ rawAuthorityHosts: raw.hosts,
1762
+ queryHost,
1763
+ queryPort
1764
+ });
1765
+ const portInUri = raw.port !== void 0 && raw.port !== "" || queryPort !== void 0 && queryPort !== "" || (raw.hosts?.some((t) => t.port !== void 0 && t.port !== "") ?? false);
1766
+ const out = {};
1767
+ if (hostsTuples.length > 0) {
1768
+ if (hostsTuples[0].host !== "") out.host = hostsTuples[0].host;
1769
+ if (portInUri && hostsTuples[0].port !== 0) out.port = hostsTuples[0].port;
1770
+ if (hostsTuples.length > 1) out.hosts = hostsTuples.map((t) => ({
1771
+ host: t.host,
1772
+ port: t.port
1773
+ }));
1774
+ }
1775
+ const userExplicit = queryUser !== void 0 && queryUser !== "" ? queryUser : raw.user !== void 0 && raw.user !== "" ? raw.user : void 0;
1776
+ if (userExplicit !== void 0) out.user = userExplicit;
1777
+ const password = queryPassword ?? raw.password;
1778
+ if (password !== void 0) out.password = password;
1779
+ const database = queryDbname ?? raw.database;
1780
+ if (database !== void 0) out.database = database;
1781
+ const sslRaw = raw.query.get("sslmode");
1782
+ if (sslRaw !== void 0 && sslRaw !== "") out.ssl = normalizeSslMode(sslRaw);
1783
+ const cb = normalizeChannelBinding(raw.query.get("channel_binding") ?? null);
1784
+ if (cb !== void 0) out.channelBinding = cb;
1785
+ const ra = normalizeRequireAuth(raw.query.get("require_auth") ?? null);
1786
+ if (ra !== void 0) out.requireAuth = ra;
1787
+ const options = raw.query.get("options");
1788
+ if (options !== void 0 && options !== "") out.options = options;
1789
+ const appName = raw.query.get("application_name");
1790
+ if (appName !== void 0 && appName !== "") out.applicationName = appName;
1791
+ const replication = normalizeReplication(raw.query.get("replication") ?? null);
1792
+ if (replication !== void 0) out.replication = replication;
1793
+ const targetSessionAttrs = normalizeTargetSessionAttrs(raw.query.get("target_session_attrs") ?? null);
1794
+ if (targetSessionAttrs !== void 0) out.targetSessionAttrs = targetSessionAttrs;
1795
+ const loadBalanceHosts = normalizeLoadBalanceHosts(raw.query.get("load_balance_hosts") ?? null);
1796
+ if (loadBalanceHosts !== void 0) out.loadBalanceHosts = loadBalanceHosts;
1797
+ const sslcert = nonEmpty(raw.query.get("sslcert"));
1798
+ if (sslcert !== void 0) out.sslcert = sslcert;
1799
+ const sslkey = nonEmpty(raw.query.get("sslkey"));
1800
+ if (sslkey !== void 0) out.sslkey = sslkey;
1801
+ const sslcertmode = normalizeSslCertMode(raw.query.get("sslcertmode") ?? null);
1802
+ if (sslcertmode !== void 0) out.sslcertmode = sslcertmode;
1803
+ const sslnegotiation = normalizeSslNegotiation(raw.query.get("sslnegotiation") ?? null);
1804
+ if (sslnegotiation !== void 0) out.sslnegotiation = sslnegotiation;
1805
+ const sslrootcert = nonEmpty(raw.query.get("sslrootcert"));
1806
+ if (sslrootcert !== void 0) out.sslrootcert = sslrootcert;
1807
+ const sslcrl = nonEmpty(raw.query.get("sslcrl"));
1808
+ if (sslcrl !== void 0) out.sslcrl = sslcrl;
1809
+ const sslcrldir = nonEmpty(raw.query.get("sslcrldir"));
1810
+ if (sslcrldir !== void 0) out.sslcrldir = sslcrldir;
1811
+ const sslkeylogfile = nonEmpty(raw.query.get("sslkeylogfile"));
1812
+ if (sslkeylogfile !== void 0) out.sslkeylogfile = sslkeylogfile;
1813
+ const sslsni = parseLibpqBool(nonEmpty(raw.query.get("sslsni")));
1814
+ if (sslsni !== void 0) out.sslsni = sslsni;
1815
+ const keepalives = parseLibpqBool(nonEmpty(raw.query.get("keepalives")));
1816
+ if (keepalives !== void 0) out.keepalives = keepalives;
1817
+ const keepalivesIdle = parseKeepalivesIdle(nonEmpty(raw.query.get("keepalives_idle")));
1818
+ if (keepalivesIdle !== void 0) out.keepalivesIdle = keepalivesIdle;
1819
+ const requirepeer = nonEmpty(raw.query.get("requirepeer"));
1820
+ if (requirepeer !== void 0) out.requirepeer = requirepeer;
1821
+ const hostaddr = nonEmpty(raw.query.get("hostaddr"));
1822
+ if (hostaddr !== void 0) out.hostaddr = hostaddr;
1823
+ const sslMin = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_min_protocol_version")), "ssl_min_protocol_version");
1824
+ if (sslMin !== void 0) out.sslMinProtocolVersion = sslMin;
1825
+ const sslMax = normalizeTlsProtocolVersion(nonEmpty(raw.query.get("ssl_max_protocol_version")), "ssl_max_protocol_version");
1826
+ if (sslMax !== void 0) out.sslMaxProtocolVersion = sslMax;
1827
+ assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
1828
+ assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
1829
+ const connectTimeoutSec = raw.query.get("connect_timeout");
1830
+ if (connectTimeoutSec !== void 0 && connectTimeoutSec !== "") {
1831
+ const t = Number.parseInt(connectTimeoutSec, 10);
1832
+ if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1833
+ }
1834
+ const clientEncoding = raw.query.get("client_encoding");
1835
+ if (clientEncoding !== void 0 && clientEncoding !== "") out.clientEncoding = clientEncoding;
1836
+ return out;
1837
+ };
1838
+ const PG_ENV_FIELD_MAP = {
1839
+ PGHOST: "host",
1840
+ PGHOSTADDR: "hostaddr",
1841
+ PGPORT: "port",
1842
+ PGUSER: "user",
1843
+ PGDATABASE: "database",
1844
+ PGPASSWORD: "password",
1845
+ PGAPPNAME: "applicationName",
1846
+ PGOPTIONS: "options",
1847
+ PGCLIENTENCODING: "clientEncoding",
1848
+ PGSSLMODE: "ssl",
1849
+ PGSSLROOTCERT: "sslrootcert",
1850
+ PGSSLCERT: "sslcert",
1851
+ PGSSLKEY: "sslkey",
1852
+ PGSSLCERTMODE: "sslcertmode",
1853
+ PGSSLNEGOTIATION: "sslnegotiation",
1854
+ PGSSLCRL: "sslcrl",
1855
+ PGSSLCRLDIR: "sslcrldir",
1856
+ PGSSLKEYLOGFILE: "sslkeylogfile",
1857
+ PGCHANNELBINDING: "channelBinding"
1858
+ };
1859
+ /**
1860
+ * Resolve the PG* env vars into a `Partial<ConnectOptions>`. Only set keys
1861
+ * end up in the result; unset / empty env vars are skipped so the caller
1862
+ * can layer this between URI overrides and pgpass / service / libpq
1863
+ * defaults without clobbering anything.
1864
+ *
1865
+ * Validation: any malformed value (e.g. `PGPORT=abc`) is silently dropped.
1866
+ * libpq behaves the same — the connection then fails later with a clearer
1867
+ * "could not parse" message, but the env-var lookup itself does not throw.
1868
+ *
1869
+ * Notes:
1870
+ * - `PGHOSTADDR` maps to {@link ConnectOptions.hostaddr}: the wire layer
1871
+ * dials this fixed IP while `PGHOST` still drives TLS SNI / cert
1872
+ * verification.
1873
+ * - `PGCONNECT_TIMEOUT` is in seconds; we convert to milliseconds.
1874
+ * - `PGCHANNELBINDING` accepts disable/prefer/require.
1875
+ * - `PGSERVICE` is consumed by the caller (it drives the
1876
+ * pg_service.conf lookup) and is NOT a direct ConnectOptions field.
1877
+ * - `PGSERVICEFILE`, `PGSYSCONFDIR`, `PGPASSFILE` are likewise consumed
1878
+ * by the loaders, not surfaced here.
1879
+ */
1880
+ const envConnectionDefaults = (env) => {
1881
+ const out = {};
1882
+ const get = (k) => {
1883
+ const v = env[k];
1884
+ return v !== void 0 && v !== "" ? v : void 0;
1885
+ };
1886
+ for (const [envName, field] of Object.entries(PG_ENV_FIELD_MAP)) {
1887
+ const value = get(envName);
1888
+ if (value === void 0) continue;
1889
+ applyEnvValue(out, field, value);
1890
+ }
1891
+ const timeoutRaw = get("PGCONNECT_TIMEOUT");
1892
+ if (timeoutRaw !== void 0) {
1893
+ const t = Number.parseInt(timeoutRaw, 10);
1894
+ if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
1895
+ }
1896
+ validateGssEncMode(get("PGGSSENCMODE") ?? null);
1897
+ return out;
1898
+ };
1899
+ const applyEnvValue = (out, field, value) => {
1900
+ switch (field) {
1901
+ case "host":
1902
+ out.host = value;
1903
+ return;
1904
+ case "port": {
1905
+ const p = Number.parseInt(value, 10);
1906
+ if (Number.isFinite(p) && p > 0 && p <= 65535) out.port = p;
1907
+ return;
1908
+ }
1909
+ case "user":
1910
+ out.user = value;
1911
+ return;
1912
+ case "database":
1913
+ out.database = value;
1914
+ return;
1915
+ case "password":
1916
+ out.password = value;
1917
+ return;
1918
+ case "applicationName":
1919
+ out.applicationName = value;
1920
+ return;
1921
+ case "options":
1922
+ out.options = value;
1923
+ return;
1924
+ case "clientEncoding":
1925
+ out.clientEncoding = value;
1926
+ return;
1927
+ case "ssl":
1928
+ out.ssl = normalizeSslMode(value);
1929
+ return;
1930
+ case "sslrootcert":
1931
+ out.sslrootcert = value;
1932
+ return;
1933
+ case "sslcert":
1934
+ out.sslcert = value;
1935
+ return;
1936
+ case "sslkey":
1937
+ out.sslkey = value;
1938
+ return;
1939
+ case "sslcertmode": {
1940
+ const cm = normalizeSslCertMode(value);
1941
+ if (cm !== void 0) out.sslcertmode = cm;
1942
+ return;
1943
+ }
1944
+ case "sslnegotiation": {
1945
+ const sn = normalizeSslNegotiation(value);
1946
+ if (sn !== void 0) out.sslnegotiation = sn;
1947
+ return;
1948
+ }
1949
+ case "sslcrl":
1950
+ out.sslcrl = value;
1951
+ return;
1952
+ case "sslcrldir":
1953
+ out.sslcrldir = value;
1954
+ return;
1955
+ case "sslkeylogfile":
1956
+ out.sslkeylogfile = value;
1957
+ return;
1958
+ case "hostaddr":
1959
+ out.hostaddr = value;
1960
+ return;
1961
+ case "channelBinding": {
1962
+ const cb = normalizeChannelBinding(value);
1963
+ if (cb !== void 0) out.channelBinding = cb;
1964
+ return;
1965
+ }
1966
+ default: return;
1967
+ }
1968
+ };
1969
+ /**
1970
+ * libpq compiled-in defaults. The lowest-priority layer in the merge chain.
1971
+ *
1972
+ * - host: 'localhost'
1973
+ * - port: 5432
1974
+ * - user: $USER ?? '' (the wire layer surfaces a clear error if the user
1975
+ * is still empty at connect time)
1976
+ * - database: deferred — libpq defaults dbname to the user; we wire that
1977
+ * in `mergeConnectOptions` after layering so a `PGUSER` env can flow
1978
+ * into `database` when the user didn't specify one.
1979
+ * - ssl: 'prefer'
1980
+ * - applicationName: 'psql' — matches upstream so `pg_stat_activity` shows
1981
+ * the value users expect.
1982
+ * - sslcert / sslkey: libpq auto-loads the default client cert/key at
1983
+ * `~/.postgresql/postgresql.crt` / `.key` when neither is configured AND
1984
+ * the file exists. We seed these as the lowest-priority defaults via
1985
+ * {@link defaultClientCertDefaults}; any explicit URI / env / conninfo
1986
+ * value overrides them. A non-existent default file is simply not set
1987
+ * (no error), matching libpq — only an explicit path that's missing
1988
+ * surfaces an error (at TLS-load time, in the wire layer).
1989
+ */
1990
+ const libpqConnectionDefaults = (env) => ({
1991
+ host: "localhost",
1992
+ port: 5432,
1993
+ user: env.USER ?? "",
1994
+ database: "",
1995
+ ssl: "prefer",
1996
+ applicationName: "psql",
1997
+ ...defaultClientCertDefaults(env)
1998
+ });
1999
+ /**
2000
+ * libpq default client-certificate discovery. When the user has NOT set
2001
+ * `sslcert` / `sslkey` (explicit paths and `PGSSLCERT` / `PGSSLKEY` are
2002
+ * higher-priority layers), libpq falls back to `~/.postgresql/postgresql.crt`
2003
+ * and `~/.postgresql/postgresql.key` — but only if those files actually
2004
+ * exist. We mirror that here so a present default cert satisfies e.g.
2005
+ * `sslcertmode=require`.
2006
+ *
2007
+ * The home directory is taken from `env.HOME` (falling back to
2008
+ * `os.homedir()`), the same convention as the pgpass / pgservice loaders;
2009
+ * passing a synthetic `HOME` keeps this hermetic in tests.
2010
+ *
2011
+ * Exported for unit testing.
2012
+ */
2013
+ const defaultClientCertDefaults = (env) => {
2014
+ const home = env.HOME ?? os.homedir();
2015
+ if (home === void 0 || home === "") return {};
2016
+ const out = {};
2017
+ const certPath = path.join(home, ".postgresql", "postgresql.crt");
2018
+ if (existsSync(certPath)) out.sslcert = certPath;
2019
+ const keyPath = path.join(home, ".postgresql", "postgresql.key");
2020
+ if (existsSync(keyPath)) out.sslkey = keyPath;
2021
+ return out;
2022
+ };
2023
+ /**
2024
+ * Translate a `pg_service.conf` entry into a `Partial<ConnectOptions>`.
2025
+ * Unknown keys are silently dropped — the service file format admits
2026
+ * arbitrary keys but only the libpq-spec subset maps to ConnectOptions.
2027
+ *
2028
+ * Numeric / enum validation mirrors `parseConninfo` so an out-of-range
2029
+ * port or bogus sslmode in the service file fails the same way (silently
2030
+ * dropped here, since libpq itself only warns on invalid service values).
2031
+ */
2032
+ const serviceEntryToConnectOptions = (entry) => {
2033
+ const out = {};
2034
+ for (const [k, v] of Object.entries(entry)) switch (k.toLowerCase()) {
2035
+ case "host":
2036
+ if (v !== "") out.host = v;
2037
+ break;
2038
+ case "port": {
2039
+ const p = Number.parseInt(v, 10);
2040
+ if (Number.isFinite(p) && p > 0 && p <= 65535) out.port = p;
2041
+ break;
2042
+ }
2043
+ case "user":
2044
+ if (v !== "") out.user = v;
2045
+ break;
2046
+ case "dbname":
2047
+ if (v !== "") out.database = v;
2048
+ break;
2049
+ case "password":
2050
+ out.password = v;
2051
+ break;
2052
+ case "application_name":
2053
+ if (v !== "") out.applicationName = v;
2054
+ break;
2055
+ case "sslmode":
2056
+ if (v !== "") out.ssl = normalizeSslMode(v);
2057
+ break;
2058
+ case "channel_binding": {
2059
+ const cb = normalizeChannelBinding(v);
2060
+ if (cb !== void 0) out.channelBinding = cb;
2061
+ break;
2062
+ }
2063
+ case "require_auth": {
2064
+ const ra = normalizeRequireAuth(v);
2065
+ if (ra !== void 0) out.requireAuth = ra;
2066
+ break;
2067
+ }
2068
+ case "options":
2069
+ if (v !== "") out.options = v;
2070
+ break;
2071
+ case "client_encoding":
2072
+ if (v !== "") out.clientEncoding = v;
2073
+ break;
2074
+ case "sslcert":
2075
+ if (v !== "") out.sslcert = v;
2076
+ break;
2077
+ case "sslkey":
2078
+ if (v !== "") out.sslkey = v;
2079
+ break;
2080
+ case "sslcertmode": {
2081
+ const cm = normalizeSslCertMode(v);
2082
+ if (cm !== void 0) out.sslcertmode = cm;
2083
+ break;
2084
+ }
2085
+ case "sslnegotiation": {
2086
+ const sn = normalizeSslNegotiation(v);
2087
+ if (sn !== void 0) out.sslnegotiation = sn;
2088
+ break;
2089
+ }
2090
+ case "sslrootcert":
2091
+ if (v !== "") out.sslrootcert = v;
2092
+ break;
2093
+ case "sslcrl":
2094
+ if (v !== "") out.sslcrl = v;
2095
+ break;
2096
+ case "sslcrldir":
2097
+ if (v !== "") out.sslcrldir = v;
2098
+ break;
2099
+ case "sslkeylogfile":
2100
+ if (v !== "") out.sslkeylogfile = v;
2101
+ break;
2102
+ case "sslsni": {
2103
+ const b = parseLibpqBool(v);
2104
+ if (b !== void 0) out.sslsni = b;
2105
+ break;
2106
+ }
2107
+ case "keepalives": {
2108
+ const b = parseLibpqBool(v);
2109
+ if (b !== void 0) out.keepalives = b;
2110
+ break;
2111
+ }
2112
+ case "keepalives_idle": {
2113
+ const n = parseKeepalivesIdle(v);
2114
+ if (n !== void 0) out.keepalivesIdle = n;
2115
+ break;
2116
+ }
2117
+ case "requirepeer":
2118
+ if (v !== "") out.requirepeer = v;
2119
+ break;
2120
+ case "hostaddr":
2121
+ if (v !== "") out.hostaddr = v;
2122
+ break;
2123
+ case "ssl_min_protocol_version": {
2124
+ const pv = normalizeTlsProtocolVersion(v === "" ? void 0 : v, "ssl_min_protocol_version");
2125
+ if (pv !== void 0) out.sslMinProtocolVersion = pv;
2126
+ break;
2127
+ }
2128
+ case "ssl_max_protocol_version": {
2129
+ const pv = normalizeTlsProtocolVersion(v === "" ? void 0 : v, "ssl_max_protocol_version");
2130
+ if (pv !== void 0) out.sslMaxProtocolVersion = pv;
2131
+ break;
2132
+ }
2133
+ case "connect_timeout": {
2134
+ const t = Number.parseInt(v, 10);
2135
+ if (Number.isFinite(t) && t >= 0) out.connectTimeoutMs = t * 1e3;
2136
+ break;
2137
+ }
2138
+ }
2139
+ assertTlsProtocolRange(out.sslMinProtocolVersion, out.sslMaxProtocolVersion);
2140
+ assertTlsMaxProtocolSupported(out.sslMaxProtocolVersion);
2141
+ return out;
2142
+ };
2143
+ /**
2144
+ * Merge layered partial ConnectOptions into a complete ConnectOptions.
2145
+ *
2146
+ * Layers are listed in PRIORITY order (highest first). For each output
2147
+ * field, the first layer that supplies a value wins. The implementation
2148
+ * walks the layers in reverse so the spread-into semantics match.
2149
+ *
2150
+ * `database` has a libpq-specific fallback: if every layer omits it, the
2151
+ * default is the resolved `user` (so `psql -U alice` connects to a
2152
+ * database named `alice`). We apply this AFTER all layers have run.
2153
+ */
2154
+ const mergeConnectOptions = (layers, defaults) => {
2155
+ let out = { ...defaults };
2156
+ for (let i = layers.length - 1; i >= 0; i--) {
2157
+ const layer = layers[i];
2158
+ out = {
2159
+ ...out,
2160
+ ...layer
2161
+ };
2162
+ }
2163
+ if (out.database === "") out.database = out.user;
2164
+ if (out.sslrootcert === "system" && out.ssl !== "verify-full") out.ssl = "verify-full";
2165
+ assertSslNegotiationModeCompatible(out.ssl, out.sslnegotiation);
2166
+ return out;
2167
+ };
2168
+ //#endregion
2169
+ export { mergeConnectOptions as a, parseConnectionUriService as c, serviceEntryToConnectOptions as d, CLIENT_VERSION as f, parseStartupArgs as h, looksLikeConnectionString as i, parseConninfo as l, formatParseError as m, envConnectionDefaults as n, parseConnectionUri as o, applyStartupArgs as p, libpqConnectionDefaults as r, parseConnectionUriPartial as s, defaultClientCertDefaults as t, runPsql as u };