neonctl 2.27.1 → 2.29.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 (136) hide show
  1. package/README.md +35 -3
  2. package/dist/analytics.js +52 -34
  3. package/dist/api.js +643 -13
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +8 -1
  6. package/dist/commands/auth.js +64 -51
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +160 -150
  9. package/dist/commands/bucket.js +183 -146
  10. package/dist/commands/checkout.js +51 -51
  11. package/dist/commands/config.js +228 -82
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +100 -101
  14. package/dist/commands/databases.js +29 -26
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +101 -104
  19. package/dist/commands/index.js +27 -25
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +232 -182
  23. package/dist/commands/neon_auth.js +385 -370
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +103 -101
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +27 -24
  29. package/dist/commands/schema_diff.js +25 -26
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +40 -0
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +37 -14
  37. package/dist/current_branch_fast_path.js +55 -0
  38. package/dist/dev/env.js +33 -33
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +68 -5
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +110 -107
  48. package/dist/log.js +2 -2
  49. package/dist/parameters.gen.js +14 -14
  50. package/dist/pkg.js +5 -5
  51. package/dist/psql/cli.js +4 -2
  52. package/dist/psql/command/cmd_cond.js +61 -61
  53. package/dist/psql/command/cmd_connect.js +159 -154
  54. package/dist/psql/command/cmd_copy.js +107 -97
  55. package/dist/psql/command/cmd_describe.js +368 -363
  56. package/dist/psql/command/cmd_format.js +276 -263
  57. package/dist/psql/command/cmd_io.js +269 -263
  58. package/dist/psql/command/cmd_lo.js +74 -66
  59. package/dist/psql/command/cmd_meta.js +148 -148
  60. package/dist/psql/command/cmd_misc.js +17 -17
  61. package/dist/psql/command/cmd_pipeline.js +142 -135
  62. package/dist/psql/command/cmd_restrict.js +25 -25
  63. package/dist/psql/command/cmd_show.js +183 -168
  64. package/dist/psql/command/dispatch.js +26 -26
  65. package/dist/psql/command/shared.js +14 -14
  66. package/dist/psql/complete/filenames.js +16 -16
  67. package/dist/psql/complete/index.js +4 -4
  68. package/dist/psql/complete/matcher.js +33 -32
  69. package/dist/psql/complete/psqlVars.js +173 -173
  70. package/dist/psql/complete/queries.js +5 -3
  71. package/dist/psql/complete/rules.js +900 -863
  72. package/dist/psql/core/common.js +136 -133
  73. package/dist/psql/core/help.js +343 -343
  74. package/dist/psql/core/mainloop.js +160 -153
  75. package/dist/psql/core/prompt.js +126 -123
  76. package/dist/psql/core/settings.js +111 -111
  77. package/dist/psql/core/sqlHelp.js +150 -150
  78. package/dist/psql/core/startup.js +211 -205
  79. package/dist/psql/core/syncVars.js +14 -14
  80. package/dist/psql/core/variables.js +24 -24
  81. package/dist/psql/describe/formatters.js +302 -289
  82. package/dist/psql/describe/processNamePattern.js +28 -28
  83. package/dist/psql/describe/queries.js +656 -651
  84. package/dist/psql/index.js +436 -411
  85. package/dist/psql/io/history.js +36 -36
  86. package/dist/psql/io/input.js +15 -15
  87. package/dist/psql/io/lineEditor/buffer.js +27 -25
  88. package/dist/psql/io/lineEditor/complete.js +15 -15
  89. package/dist/psql/io/lineEditor/filename.js +22 -22
  90. package/dist/psql/io/lineEditor/index.js +65 -62
  91. package/dist/psql/io/lineEditor/keymap.js +325 -318
  92. package/dist/psql/io/lineEditor/vt100.js +60 -60
  93. package/dist/psql/io/pgpass.js +18 -18
  94. package/dist/psql/io/pgservice.js +14 -14
  95. package/dist/psql/io/psqlrc.js +46 -46
  96. package/dist/psql/print/aligned.js +175 -166
  97. package/dist/psql/print/asciidoc.js +51 -51
  98. package/dist/psql/print/crosstab.js +34 -31
  99. package/dist/psql/print/csv.js +25 -22
  100. package/dist/psql/print/html.js +54 -54
  101. package/dist/psql/print/json.js +12 -12
  102. package/dist/psql/print/latex.js +118 -118
  103. package/dist/psql/print/pager.js +28 -26
  104. package/dist/psql/print/troff.js +48 -48
  105. package/dist/psql/print/unaligned.js +15 -14
  106. package/dist/psql/print/units.js +17 -17
  107. package/dist/psql/scanner/slash.js +48 -46
  108. package/dist/psql/scanner/sql.js +88 -84
  109. package/dist/psql/scanner/stringutils.js +21 -17
  110. package/dist/psql/types/index.js +7 -7
  111. package/dist/psql/types/scanner.js +8 -8
  112. package/dist/psql/wire/connection.js +341 -327
  113. package/dist/psql/wire/copy.js +7 -7
  114. package/dist/psql/wire/pipeline.js +26 -24
  115. package/dist/psql/wire/protocol.js +102 -102
  116. package/dist/psql/wire/sasl.js +62 -62
  117. package/dist/psql/wire/tls.js +79 -73
  118. package/dist/storage_api.js +22 -23
  119. package/dist/test_utils/fixtures.js +74 -41
  120. package/dist/test_utils/oauth_server.js +5 -5
  121. package/dist/utils/api_enums.js +33 -0
  122. package/dist/utils/branch_notice.js +5 -5
  123. package/dist/utils/branch_picker.js +26 -26
  124. package/dist/utils/compute_units.js +4 -4
  125. package/dist/utils/enrichers.js +28 -16
  126. package/dist/utils/esbuild.js +28 -28
  127. package/dist/utils/formats.js +1 -1
  128. package/dist/utils/middlewares.js +3 -3
  129. package/dist/utils/package_manager.js +68 -0
  130. package/dist/utils/point_in_time.js +12 -12
  131. package/dist/utils/psql.js +30 -30
  132. package/dist/utils/string.js +2 -2
  133. package/dist/utils/ui.js +9 -9
  134. package/dist/utils/zip.js +1 -1
  135. package/dist/writer.js +17 -17
  136. package/package.json +10 -12
@@ -29,19 +29,19 @@
29
29
  * - Encoding / multibyte handling beyond UTF-8 (handled implicitly by JS).
30
30
  * - `\watch` continuous-execution mode.
31
31
  */
32
- import * as readline from 'node:readline';
33
- import { initialScanState } from '../types/scanner.js';
34
- import { scanSql } from '../scanner/sql.js';
35
- import { scanSlashArgs } from '../scanner/slash.js';
36
- import { renderPromptByName } from './prompt.js';
37
- import { captureLastError, pickOut, refreshErrorVars, renderResultSet, sendQuery, writeQueryError, } from './common.js';
38
- import { formatDurationMs } from '../print/units.js';
39
- import { COND_COMMAND_NAMES, attachCondStack, cmdElif, cmdElse, cmdEndif, cmdIf, } from '../command/cmd_cond.js';
40
- import { consumeNext as consumeQueuedInput } from '../command/inputQueue.js';
41
- import { consumeBindState, getPipelineState } from '../command/cmd_pipeline.js';
42
- import { appendHistory, defaultHistoryPath, loadHistory, resolveHistSize, truncateHistory, } from '../io/history.js';
43
- import { LineEditor } from '../io/lineEditor/index.js';
44
- import { psqlCompleter } from '../complete/index.js';
32
+ import * as readline from "node:readline";
33
+ import { attachCondStack, COND_COMMAND_NAMES, cmdElif, cmdElse, cmdEndif, cmdIf, } from "../command/cmd_cond.js";
34
+ import { consumeBindState, getPipelineState } from "../command/cmd_pipeline.js";
35
+ import { consumeNext as consumeQueuedInput } from "../command/inputQueue.js";
36
+ import { psqlCompleter } from "../complete/index.js";
37
+ import { appendHistory, defaultHistoryPath, loadHistory, resolveHistSize, truncateHistory, } from "../io/history.js";
38
+ import { LineEditor } from "../io/lineEditor/index.js";
39
+ import { formatDurationMs } from "../print/units.js";
40
+ import { scanSlashArgs } from "../scanner/slash.js";
41
+ import { scanSql } from "../scanner/sql.js";
42
+ import { initialScanState } from "../types/scanner.js";
43
+ import { captureLastError, pickOut, refreshErrorVars, renderResultSet, sendQuery, writeQueryError, } from "./common.js";
44
+ import { renderPromptByName } from "./prompt.js";
45
45
  // ---------------------------------------------------------------------------
46
46
  // Exit codes — mirror psql's `EXIT_*` constants.
47
47
  // ---------------------------------------------------------------------------
@@ -54,10 +54,10 @@ export const EXIT_USER = 3;
54
54
  // registry lookup, because they must run even inside an inactive branch.
55
55
  // ---------------------------------------------------------------------------
56
56
  const COND_COMMANDS = new Map([
57
- ['if', cmdIf],
58
- ['elif', cmdElif],
59
- ['else', cmdElse],
60
- ['endif', cmdEndif],
57
+ ["if", cmdIf],
58
+ ["elif", cmdElif],
59
+ ["else", cmdElse],
60
+ ["endif", cmdEndif],
61
61
  ]);
62
62
  const makeStreamLineReader = (input, out) => {
63
63
  const rl = readline.createInterface({
@@ -79,7 +79,7 @@ const makeStreamLineReader = (input, out) => {
79
79
  const lineQueue = [];
80
80
  let waiter = null;
81
81
  let ended = false;
82
- rl.on('line', (line) => {
82
+ rl.on("line", (line) => {
83
83
  if (waiter) {
84
84
  const w = waiter;
85
85
  waiter = null;
@@ -89,7 +89,7 @@ const makeStreamLineReader = (input, out) => {
89
89
  lineQueue.push(line);
90
90
  }
91
91
  });
92
- rl.on('close', () => {
92
+ rl.on("close", () => {
93
93
  ended = true;
94
94
  if (waiter) {
95
95
  const w = waiter;
@@ -127,10 +127,10 @@ const makeStreamLineReader = (input, out) => {
127
127
  */
128
128
  const parseBoolVar = (raw) => {
129
129
  const v = raw.toLowerCase().trim();
130
- if (v === '' || v === 'on' || v === 'true' || v === 'yes' || v === '1') {
130
+ if (v === "" || v === "on" || v === "true" || v === "yes" || v === "1") {
131
131
  return true;
132
132
  }
133
- if (v === 'off' || v === 'false' || v === 'no' || v === '0') {
133
+ if (v === "off" || v === "false" || v === "no" || v === "0") {
134
134
  return false;
135
135
  }
136
136
  return null;
@@ -138,14 +138,14 @@ const parseBoolVar = (raw) => {
138
138
  /** Translate a psql VI_MODE var value into the LineEditor mode. */
139
139
  const viModeOption = (raw) => {
140
140
  if (raw === undefined)
141
- return 'emacs';
142
- return parseBoolVar(raw) === true ? 'vi' : 'emacs';
141
+ return "emacs";
142
+ return parseBoolVar(raw) === true ? "vi" : "emacs";
143
143
  };
144
144
  const makeEditorLineReader = async (ctx, opts = {}) => {
145
145
  const env = process.env;
146
146
  const histPath = defaultHistoryPath(env);
147
147
  const histSize = resolveHistSize(env);
148
- const histControl = ctx.settings.vars.get('HISTCONTROL') ??
148
+ const histControl = ctx.settings.vars.get("HISTCONTROL") ??
149
149
  ctx.settings.histControl;
150
150
  let history = [];
151
151
  try {
@@ -158,7 +158,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
158
158
  // VI_MODE: upstream readline's `set editing-mode {emacs|vi}`. We read once
159
159
  // here for the initial mode, and below we install a VarStore hook so a
160
160
  // subsequent `\set VI_MODE on` switches the editor at the next prompt.
161
- const initialMode = viModeOption(ctx.settings.vars.get('VI_MODE'));
161
+ const initialMode = viModeOption(ctx.settings.vars.get("VI_MODE"));
162
162
  const editor = new LineEditor({
163
163
  stdin: ctx.stdin,
164
164
  stdout: ctx.stdout,
@@ -174,9 +174,9 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
174
174
  // and on success forward to `editor.setMode` (which defers the switch to
175
175
  // the next readLine boundary). Replay on registration is fine — the hook
176
176
  // is idempotent for a no-op `null`/unchanged value.
177
- ctx.settings.vars.addHook('VI_MODE', (newValue) => {
177
+ ctx.settings.vars.addHook("VI_MODE", (newValue) => {
178
178
  if (newValue === null) {
179
- editor.setMode('emacs');
179
+ editor.setMode("emacs");
180
180
  return true;
181
181
  }
182
182
  const parsed = parseBoolVar(newValue);
@@ -184,7 +184,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
184
184
  ctx.stderr.write(`\\set: VI_MODE: invalid value "${newValue}"; valid values: on, off\n`);
185
185
  return false;
186
186
  }
187
- editor.setMode(parsed ? 'vi' : 'emacs');
187
+ editor.setMode(parsed ? "vi" : "emacs");
188
188
  return true;
189
189
  });
190
190
  return {
@@ -195,7 +195,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
195
195
  return r;
196
196
  },
197
197
  pushHistory: (line) => {
198
- const trimmed = line.replace(/\n+$/, '');
198
+ const trimmed = line.replace(/\n+$/, "");
199
199
  if (trimmed.length === 0)
200
200
  return;
201
201
  editor.pushHistory(trimmed);
@@ -231,8 +231,8 @@ const isQuitKeyword = (line) => {
231
231
  const trimmed = line.trim();
232
232
  if (trimmed.length === 0)
233
233
  return false;
234
- const stripped = trimmed.replace(/;+\s*$/u, '').trimEnd();
235
- return stripped === 'exit' || stripped === 'quit';
234
+ const stripped = trimmed.replace(/;+\s*$/u, "").trimEnd();
235
+ return stripped === "exit" || stripped === "quit";
236
236
  };
237
237
  /**
238
238
  * Recognize the bare `help` keyword the same way upstream does: at the start
@@ -243,27 +243,27 @@ const isHelpKeyword = (line) => {
243
243
  const trimmed = line.trim();
244
244
  if (trimmed.length === 0)
245
245
  return false;
246
- const stripped = trimmed.replace(/;+\s*$/u, '').trimEnd();
247
- return stripped === 'help';
246
+ const stripped = trimmed.replace(/;+\s*$/u, "").trimEnd();
247
+ return stripped === "help";
248
248
  };
249
- const HELP_TEXT = 'You are using psql-ts, the embedded TypeScript psql in neonctl.\n' +
250
- 'Type: \\copyright for distribution terms\n' +
251
- ' \\h for help with SQL commands\n' +
252
- ' \\? for help with psql commands\n' +
253
- ' \\g or terminate with semicolon to execute query\n' +
254
- ' \\q to quit\n';
249
+ const HELP_TEXT = "You are using psql-ts, the embedded TypeScript psql in neonctl.\n" +
250
+ "Type: \\copyright for distribution terms\n" +
251
+ " \\h for help with SQL commands\n" +
252
+ " \\? for help with psql commands\n" +
253
+ " \\g or terminate with semicolon to execute query\n" +
254
+ " \\q to quit\n";
255
255
  const makeLineReader = async (ctx, opts = {}) => {
256
- const debug = process.env.NEONCTL_PSQL_DEBUG === '1';
256
+ const debug = process.env.NEONCTL_PSQL_DEBUG === "1";
257
257
  if (ctx.settings.notty) {
258
258
  if (debug) {
259
- ctx.stderr.write('[psql-debug] notty=true; using stream reader (no line editor / no Tab completion)\n');
259
+ ctx.stderr.write("[psql-debug] notty=true; using stream reader (no line editor / no Tab completion)\n");
260
260
  }
261
261
  return makeStreamLineReader(ctx.stdin, ctx.stdout);
262
262
  }
263
263
  try {
264
264
  const r = await makeEditorLineReader(ctx, opts);
265
265
  if (debug) {
266
- ctx.stderr.write('[psql-debug] LineEditor engaged (raw mode, Tab completion active)\n');
266
+ ctx.stderr.write("[psql-debug] LineEditor engaged (raw mode, Tab completion active)\n");
267
267
  }
268
268
  return r;
269
269
  }
@@ -277,22 +277,22 @@ const makeLineReader = async (ctx, opts = {}) => {
277
277
  const transactionState = (ctx) => {
278
278
  const db = ctx.settings.db;
279
279
  if (!db)
280
- return 'idle';
280
+ return "idle";
281
281
  const status = db.txStatus;
282
282
  switch (status) {
283
- case 'I':
284
- case 'idle':
285
- return 'idle';
286
- case 'T':
287
- case 'in-block':
288
- return 'in-block';
289
- case 'E':
290
- case 'failed':
291
- return 'failed';
292
- case 'unknown':
293
- return 'unknown';
283
+ case "I":
284
+ case "idle":
285
+ return "idle";
286
+ case "T":
287
+ case "in-block":
288
+ return "in-block";
289
+ case "E":
290
+ case "failed":
291
+ return "failed";
292
+ case "unknown":
293
+ return "unknown";
294
294
  default:
295
- return 'idle';
295
+ return "idle";
296
296
  }
297
297
  };
298
298
  // ---------------------------------------------------------------------------
@@ -321,7 +321,7 @@ const makeBackslashContext = (ctx, cmdName, rawArgs, queryBuf) => {
321
321
  cmdName,
322
322
  queryBuf,
323
323
  rawArgs,
324
- nextArg(mode = 'normal') {
324
+ nextArg(mode = "normal") {
325
325
  const args = argsFor(mode);
326
326
  const idx = cursors.get(mode) ?? 0;
327
327
  if (idx >= args.length)
@@ -351,7 +351,7 @@ const buildPromptContext = (ctx, promptStatus, lineNumber) => ({
351
351
  promptStatus,
352
352
  lineNumber,
353
353
  inTransaction: transactionState(ctx),
354
- pipelineState: 'off',
354
+ pipelineState: "off",
355
355
  });
356
356
  // ---------------------------------------------------------------------------
357
357
  // Conditional-command dispatch. Returns true if the command was a cond
@@ -370,7 +370,7 @@ const dispatchCondCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
370
370
  // these: commands using cmd_io's `errResult` (and inline writers) set it
371
371
  // to `true`; cond commands (which only stash `lastErrorResult.message`)
372
372
  // leave it unset so the mainloop surfaces the message.
373
- if (result.status === 'error' &&
373
+ if (result.status === "error" &&
374
374
  !result.errorWritten &&
375
375
  ctx.settings.lastErrorResult?.message) {
376
376
  writeError(ctx, ctx.settings.lastErrorResult.message);
@@ -389,7 +389,7 @@ const dispatchRegisteredCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
389
389
  // layer doesn't add a second one. (Other dispatch paths set
390
390
  // `lastErrorResult.message`; this one does not, so the duplicate
391
391
  // guard below would skip anyway — flag it explicitly for symmetry.)
392
- return { status: 'error', errorWritten: true };
392
+ return { status: "error", errorWritten: true };
393
393
  }
394
394
  const bctx = makeBackslashContext(ctx, cmdName, rawArgs, queryBuf);
395
395
  attachCondStack(bctx, ctx.cond);
@@ -400,7 +400,7 @@ const dispatchRegisteredCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
400
400
  // would emit a stray `psql: ERROR: <msg>` line between the LINE/`^`
401
401
  // block and the `\errverbose` re-render, breaking the strict ordering
402
402
  // check in the conformance regex.
403
- if (result.status === 'error' &&
403
+ if (result.status === "error" &&
404
404
  !result.errorWritten &&
405
405
  ctx.settings.lastErrorResult?.message) {
406
406
  writeError(ctx, ctx.settings.lastErrorResult.message);
@@ -426,9 +426,9 @@ const refreshConnectionVars = (ctx) => {
426
426
  const db = ctx.settings.db;
427
427
  if (!db)
428
428
  return;
429
- const enc = db.parameterStatus('client_encoding');
430
- if (enc !== undefined && ctx.settings.vars.get('ENCODING') !== enc) {
431
- ctx.settings.vars.set('ENCODING', enc);
429
+ const enc = db.parameterStatus("client_encoding");
430
+ if (enc !== undefined && ctx.settings.vars.get("ENCODING") !== enc) {
431
+ ctx.settings.vars.set("ENCODING", enc);
432
432
  }
433
433
  };
434
434
  const dispatchSendQuery = async (ctx, sql) => {
@@ -457,15 +457,15 @@ const dispatchSendQuery = async (ctx, sql) => {
457
457
  const trimmed = sql.trimStart();
458
458
  if (/^COPY\b/i.test(trimmed) &&
459
459
  /\b(FROM\s+STDIN|TO\s+STDOUT)\b/i.test(trimmed)) {
460
- ctx.stderr.write('psql: error: COPY in a pipeline is not supported, aborting connection\n');
460
+ ctx.stderr.write("psql: error: COPY in a pipeline is not supported, aborting connection\n");
461
461
  // Hard-abort the underlying socket so isClosed() flips true and the
462
462
  // mainloop's post-dispatch `checkConnectionLost` ends the loop.
463
463
  try {
464
464
  const db = ctx.settings.db;
465
- if (typeof db.abortForCopyInPipeline === 'function') {
465
+ if (typeof db.abortForCopyInPipeline === "function") {
466
466
  db.abortForCopyInPipeline();
467
467
  }
468
- else if (typeof db.close === 'function') {
468
+ else if (typeof db.close === "function") {
469
469
  await db.close();
470
470
  }
471
471
  }
@@ -478,23 +478,23 @@ const dispatchSendQuery = async (ctx, sql) => {
478
478
  // Pipeline-mode `;`-queries: empty parameter list, anonymous prepared
479
479
  // statement, anonymous portal. The result will surface later through
480
480
  // `\endpipeline` / `\getresults`.
481
- await ps.session.parse('', sql, []);
482
- await ps.session.bind('', bind?.values ?? []);
481
+ await ps.session.parse("", sql, []);
482
+ await ps.session.bind("", bind?.values ?? []);
483
483
  const exec = (async () => {
484
- await ps.session.execute('', 0);
484
+ await ps.session.execute("", 0);
485
485
  return undefined;
486
486
  })();
487
487
  ps.pending.push(exec);
488
488
  // The enqueue succeeded; the actual result will flush at
489
489
  // `\endpipeline` time. Mark the diagnostic vars as success-now so
490
490
  // intervening `\echo :ERROR` sees "false" between pipeline appends.
491
- refreshErrorVars(ctx.settings, { kind: 'success', rowCount: null });
491
+ refreshErrorVars(ctx.settings, { kind: "success", rowCount: null });
492
492
  return true;
493
493
  }
494
494
  catch (err) {
495
495
  const message = captureLastError(ctx.settings, err, sql);
496
496
  writeQueryError(ctx, message);
497
- refreshErrorVars(ctx.settings, { kind: 'error' });
497
+ refreshErrorVars(ctx.settings, { kind: "error" });
498
498
  return false;
499
499
  }
500
500
  }
@@ -523,10 +523,10 @@ const dispatchSendQuery = async (ctx, sql) => {
523
523
  finally {
524
524
  refreshConnectionVars(ctx);
525
525
  refreshErrorVars(ctx.settings, hadError
526
- ? { kind: 'error' }
527
- : { kind: 'success', rowCount: lastRowCount });
526
+ ? { kind: "error" }
527
+ : { kind: "success", rowCount: lastRowCount });
528
528
  if (ctx.settings.timing) {
529
- ctx.stdout.write('\n' + formatDurationMs(Date.now() - started) + '\n');
529
+ ctx.stdout.write("\n" + formatDurationMs(Date.now() - started) + "\n");
530
530
  }
531
531
  }
532
532
  }
@@ -581,18 +581,18 @@ const installNotificationHandler = (ctx, reader) => {
581
581
  * returned string directly.
582
582
  */
583
583
  const formatNotice = (notice, verbosity, showContext) => {
584
- const severity = notice.severity || 'NOTICE';
585
- const message = notice.message || '';
584
+ const severity = notice.severity || "NOTICE";
585
+ const message = notice.message || "";
586
586
  const lines = [];
587
- if (verbosity === 'verbose' || verbosity === 'sqlstate') {
588
- const sqlstate = notice.code ?? 'XX000';
587
+ if (verbosity === "verbose" || verbosity === "sqlstate") {
588
+ const sqlstate = notice.code ?? "XX000";
589
589
  lines.push(`${severity}: ${sqlstate}: ${message}`);
590
590
  }
591
591
  else {
592
592
  lines.push(`${severity}: ${message}`);
593
593
  }
594
- if (verbosity === 'terse' || verbosity === 'sqlstate') {
595
- return lines.join('\n') + '\n';
594
+ if (verbosity === "terse" || verbosity === "sqlstate") {
595
+ return lines.join("\n") + "\n";
596
596
  }
597
597
  if (notice.detail)
598
598
  lines.push(`DETAIL: ${notice.detail}`);
@@ -603,19 +603,19 @@ const formatNotice = (notice, verbosity, showContext) => {
603
603
  // - `default` shows CONTEXT only when SHOW_CONTEXT is `always` for
604
604
  // non-error severities (NOTICE / WARNING / INFO / LOG / DEBUG), or
605
605
  // when SHOW_CONTEXT is `errors`/`always` for ERROR-level entries.
606
- const isError = severity === 'ERROR' || severity === 'FATAL' || severity === 'PANIC';
607
- const includeContext = verbosity === 'verbose' ||
608
- showContext === 'always' ||
609
- (showContext === 'errors' && isError);
606
+ const isError = severity === "ERROR" || severity === "FATAL" || severity === "PANIC";
607
+ const includeContext = verbosity === "verbose" ||
608
+ showContext === "always" ||
609
+ (showContext === "errors" && isError);
610
610
  if (includeContext && notice.where) {
611
611
  lines.push(`CONTEXT: ${notice.where}`);
612
612
  }
613
- if (verbosity === 'verbose' && (notice.routine || notice.file)) {
614
- const location = (notice.routine ?? '') +
615
- (notice.file ? `, ${notice.file}:${notice.line ?? ''}` : '');
613
+ if (verbosity === "verbose" && (notice.routine || notice.file)) {
614
+ const location = (notice.routine ?? "") +
615
+ (notice.file ? `, ${notice.file}:${notice.line ?? ""}` : "");
616
616
  lines.push(`LOCATION: ${location}`);
617
617
  }
618
- return lines.join('\n') + '\n';
618
+ return lines.join("\n") + "\n";
619
619
  };
620
620
  /**
621
621
  * Subscribe to NoticeResponse on the active connection, rendering each to
@@ -637,7 +637,7 @@ const installNoticeHandler = (ctx, reader) => {
637
637
  // the NOTICE lands AT the result boundary, not before. Emitting here too
638
638
  // would duplicate every notice — once when the wire layer parses it,
639
639
  // once when the drain walks `rs.notices`.
640
- if (ctx.settings.sendMode === 'extended-pipeline')
640
+ if (ctx.settings.sendMode === "extended-pipeline")
641
641
  return;
642
642
  const text = formatNotice(notice, ctx.settings.verbosity, ctx.settings.showContext);
643
643
  // Notices go to stderr (libpq default). The LineEditor's prompt-redraw
@@ -662,8 +662,8 @@ const installSigint = (ctx, state) => {
662
662
  }
663
663
  state.resetBuf();
664
664
  };
665
- process.on('SIGINT', handler);
666
- return () => process.off('SIGINT', handler);
665
+ process.on("SIGINT", handler);
666
+ return () => process.off("SIGINT", handler);
667
667
  };
668
668
  // ---------------------------------------------------------------------------
669
669
  // The main entry point.
@@ -674,7 +674,7 @@ export const runMainLoop = async (ctx) => {
674
674
  // on every Tab. The mainloop reassigns this variable across statements
675
675
  // (resetBuf, after dispatch); the closure stays valid because it captures
676
676
  // the binding, not a snapshot.
677
- let queryBuf = '';
677
+ let queryBuf = "";
678
678
  const reader = await makeLineReader(ctx, { getQueryBuf: () => queryBuf });
679
679
  let scanState = initialScanState();
680
680
  let stmtLineNumber = 1;
@@ -689,7 +689,7 @@ export const runMainLoop = async (ctx) => {
689
689
  // in lock-step with cond.push / cond.pop / cond.setSavedQueryBufLen.
690
690
  const condScanStateStack = [];
691
691
  const resetBuf = () => {
692
- queryBuf = '';
692
+ queryBuf = "";
693
693
  scanState = initialScanState();
694
694
  stmtLineNumber = 1;
695
695
  };
@@ -699,7 +699,7 @@ export const runMainLoop = async (ctx) => {
699
699
  // we halt the loop instead so we don't spam ERROR lines for every one.
700
700
  const checkConnectionLost = () => {
701
701
  if (ctx.settings.db?.isClosed()) {
702
- ctx.stderr.write('psql: error: connection to server was lost\n');
702
+ ctx.stderr.write("psql: error: connection to server was lost\n");
703
703
  successResult = EXIT_BADCONN;
704
704
  exitRequested = true;
705
705
  return true;
@@ -726,12 +726,12 @@ export const runMainLoop = async (ctx) => {
726
726
  // with stdout. For TTY input the LineEditor renders the prompt itself.
727
727
  const computePrompt = (status) => {
728
728
  if (ctx.settings.notty)
729
- return '';
730
- const name = queryBuf.trim().length === 0 || status === 'ready'
731
- ? 'PROMPT1'
732
- : status === 'copy'
733
- ? 'PROMPT3'
734
- : 'PROMPT2';
729
+ return "";
730
+ const name = queryBuf.trim().length === 0 || status === "ready"
731
+ ? "PROMPT1"
732
+ : status === "copy"
733
+ ? "PROMPT3"
734
+ : "PROMPT2";
735
735
  const promptCtx = buildPromptContext(ctx, status, stmtLineNumber);
736
736
  return renderPromptByName(name, promptCtx);
737
737
  };
@@ -749,8 +749,8 @@ export const runMainLoop = async (ctx) => {
749
749
  const spec = ctx.registry.lookup(cmdName);
750
750
  if (!spec)
751
751
  return undefined;
752
- if (spec.argMode === 'whole-line')
753
- return 'whole-line';
752
+ if (spec.argMode === "whole-line")
753
+ return "whole-line";
754
754
  // Backslash registry currently only distinguishes whole-line vs the
755
755
  // default `lex` mode. Filepipe is signalled per-call via
756
756
  // `nextArg('filepipe')` inside cmd implementations rather than the
@@ -758,15 +758,15 @@ export const runMainLoop = async (ctx) => {
758
758
  // upstream declares as `OT_FILEPIPE` (`\w` and `\o`). Without this,
759
759
  // `\w |/no/such/file \else` would split off `\else` as a separate
760
760
  // command instead of capturing it as the file's whole-line arg.
761
- if (cmdName === 'w' || cmdName === 'o')
762
- return 'filepipe';
761
+ if (cmdName === "w" || cmdName === "o")
762
+ return "filepipe";
763
763
  return undefined;
764
764
  };
765
765
  /**
766
766
  * Strip block / line comments cheaply before scanning so a COPY-shaped
767
767
  * comment doesn't trigger pre-buffering or sink wiring.
768
768
  */
769
- const stripSqlComments = (sql) => sql.replace(/\/\*[\s\S]*?\*\//g, '').replace(/--[^\n]*/g, '');
769
+ const stripSqlComments = (sql) => sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--[^\n]*/g, "");
770
770
  /**
771
771
  * Count the number of `COPY ... FROM STDIN` segments in `sql`. Upstream
772
772
  * `handleCopyIn` in copy.c is invoked for each one that hits the wire as
@@ -808,10 +808,10 @@ export const runMainLoop = async (ctx) => {
808
808
  const readCopyDataBlock = async () => {
809
809
  const lines = [];
810
810
  for (;;) {
811
- const line = await reader.readLine('');
811
+ const line = await reader.readLine("");
812
812
  if (line === null)
813
813
  break;
814
- if (line.replace(/\s+$/, '') === '\\.')
814
+ if (line.replace(/\s+$/, "") === "\\.")
815
815
  break;
816
816
  lines.push(line);
817
817
  // Upstream `handleCopyIn` in copy.c reads COPY data lines straight
@@ -824,8 +824,8 @@ export const runMainLoop = async (ctx) => {
824
824
  }
825
825
  // Each line plus a trailing newline — matches the byte stream COPY
826
826
  // expects on its input side.
827
- const text = lines.length === 0 ? '' : lines.join('\n') + '\n';
828
- return Buffer.from(text, 'utf8');
827
+ const text = lines.length === 0 ? "" : lines.join("\n") + "\n";
828
+ return Buffer.from(text, "utf8");
829
829
  };
830
830
  /**
831
831
  * Process the assembled queryBuf+line through scanSql, dispatching the
@@ -842,11 +842,11 @@ export const runMainLoop = async (ctx) => {
842
842
  singleline: ctx.settings.singleline,
843
843
  });
844
844
  scanState = result.nextState;
845
- if (result.kind === 'semicolon') {
845
+ if (result.kind === "semicolon") {
846
846
  // Use the substituted `result.sql` so `:NAME` references already
847
847
  // resolved at scan time make it into the executed SQL.
848
848
  const sqlText = queryBuf + result.sql;
849
- queryBuf = '';
849
+ queryBuf = "";
850
850
  working = working.slice(result.consumed);
851
851
  scanState = initialScanState();
852
852
  stmtLineNumber = 1;
@@ -861,14 +861,16 @@ export const runMainLoop = async (ctx) => {
861
861
  // layer before dispatch. Mirrors upstream `handleCopyIn` in
862
862
  // copy.c — except we pump the bytes up-front instead of via a
863
863
  // callback into the REPL when CopyInResponse arrives.
864
- const copyCount = ctx.settings.db ? countCopyFromStdin(sqlText) : 0;
864
+ const copyCount = ctx.settings.db
865
+ ? countCopyFromStdin(sqlText)
866
+ : 0;
865
867
  const wantsCopyOut = ctx.settings.db !== undefined && hasCopyToStdout(sqlText);
866
868
  if (copyCount > 0 && ctx.settings.db) {
867
869
  // The Connection type doesn't expose `queueCopyInData` (kept
868
870
  // off the frozen interface), but the concrete PgConnection
869
871
  // does. We duck-type the method to avoid coupling here.
870
872
  const conn = ctx.settings.db;
871
- if (typeof conn.queueCopyInData === 'function') {
873
+ if (typeof conn.queueCopyInData === "function") {
872
874
  // Drop any leftover buffers from a previous (failed) batch so
873
875
  // we don't accidentally re-use stale data.
874
876
  conn.clearCopyInDataQueue?.();
@@ -917,7 +919,7 @@ export const runMainLoop = async (ctx) => {
917
919
  }
918
920
  continue;
919
921
  }
920
- if (result.kind === 'backslash') {
922
+ if (result.kind === "backslash") {
921
923
  // Fold buffered SQL accumulated before the backslash into queryBuf.
922
924
  // `result.sql` carries the (possibly empty) text that preceded the
923
925
  // backslash in this scan pass — empty when the backslash was at the
@@ -955,7 +957,7 @@ export const runMainLoop = async (ctx) => {
955
957
  // small immutable objects.
956
958
  const scanStateBefore = { ...scanState };
957
959
  const r = await dispatchCondCommand(ctx, cmdName, result.rest, queryBuf);
958
- if (r.handled && r.result?.status === 'exit') {
960
+ if (r.handled && r.result?.status === "exit") {
959
961
  exitRequested = true;
960
962
  return;
961
963
  }
@@ -986,24 +988,26 @@ export const runMainLoop = async (ctx) => {
986
988
  // inactive restoration (so the new branch
987
989
  // starts from a clean checkpoint).
988
990
  // `\endif` → pop the top entry.
989
- if (cmdName === 'if') {
991
+ if (cmdName === "if") {
990
992
  condScanStateStack.push(scanStateBefore);
991
993
  }
992
- else if (cmdName === 'elif' || cmdName === 'else') {
994
+ else if (cmdName === "elif" || cmdName === "else") {
993
995
  // Errors leave the top untouched (cond.setState not called on
994
996
  // the no-matching/double-else paths). Only re-anchor when the
995
997
  // command succeeded — `status: 'ok'` covers both the active
996
998
  // and truncated paths.
997
- if (condScanStateStack.length > 0 && r.result?.status === 'ok') {
998
- condScanStateStack[condScanStateStack.length - 1] = {
999
- ...scanState,
1000
- };
999
+ if (condScanStateStack.length > 0 &&
1000
+ r.result?.status === "ok") {
1001
+ condScanStateStack[condScanStateStack.length - 1] =
1002
+ {
1003
+ ...scanState,
1004
+ };
1001
1005
  }
1002
1006
  }
1003
- else if (cmdName === 'endif') {
1007
+ else if (cmdName === "endif") {
1004
1008
  // Pop only on success — `\endif` with no matching `\if`
1005
1009
  // returns an error and doesn't actually pop the cond frame.
1006
- if (r.result?.status === 'ok') {
1010
+ if (r.result?.status === "ok") {
1007
1011
  condScanStateStack.pop();
1008
1012
  }
1009
1013
  }
@@ -1014,7 +1018,7 @@ export const runMainLoop = async (ctx) => {
1014
1018
  // the terminal `lastWasError → EXIT_USER` escalation. Only
1015
1019
  // ON_ERROR_STOP can escalate cond failures.
1016
1020
  if (r.handled &&
1017
- r.result?.status === 'error' &&
1021
+ r.result?.status === "error" &&
1018
1022
  ctx.settings.onErrorStop) {
1019
1023
  successResult = EXIT_USER;
1020
1024
  exitRequested = true;
@@ -1050,12 +1054,12 @@ export const runMainLoop = async (ctx) => {
1050
1054
  continue;
1051
1055
  }
1052
1056
  const bres = await dispatchRegisteredCommand(ctx, cmdName, result.rest, queryBuf);
1053
- if (bres?.status === 'exit') {
1057
+ if (bres?.status === "exit") {
1054
1058
  exitRequested = true;
1055
1059
  return;
1056
1060
  }
1057
- if (bres?.status === 'reset-buf') {
1058
- queryBuf = bres.newBuf ?? '';
1061
+ if (bres?.status === "reset-buf") {
1062
+ queryBuf = bres.newBuf ?? "";
1059
1063
  scanState = initialScanState();
1060
1064
  stmtLineNumber = 1;
1061
1065
  // The SQL scanner intentionally stops the backslash boundary on
@@ -1083,10 +1087,11 @@ export const runMainLoop = async (ctx) => {
1083
1087
  // through-newline behaviour for the buffer-reset case — without
1084
1088
  // changing the scanner's semantics for the inline-slash + multi-
1085
1089
  // line shape that depends on the `\n` surviving.
1086
- if (working.startsWith('\r\n')) {
1090
+ if (working.startsWith("\r\n")) {
1087
1091
  working = working.slice(2);
1088
1092
  }
1089
- else if (working.startsWith('\n') || working.startsWith('\r')) {
1093
+ else if (working.startsWith("\n") ||
1094
+ working.startsWith("\r")) {
1090
1095
  working = working.slice(1);
1091
1096
  }
1092
1097
  }
@@ -1101,11 +1106,12 @@ export const runMainLoop = async (ctx) => {
1101
1106
  // would assemble the SELECT's queryBuf as `\n` + `SELECT...` —
1102
1107
  // shifting the server's `LINE N` count by one and contaminating
1103
1108
  // the `STATEMENT: ...` echo emitted on error.
1104
- if (bres?.status !== 'reset-buf' && slashOnlyLine) {
1105
- if (working.startsWith('\r\n')) {
1109
+ if (bres?.status !== "reset-buf" && slashOnlyLine) {
1110
+ if (working.startsWith("\r\n")) {
1106
1111
  working = working.slice(2);
1107
1112
  }
1108
- else if (working.startsWith('\n') || working.startsWith('\r')) {
1113
+ else if (working.startsWith("\n") ||
1114
+ working.startsWith("\r")) {
1109
1115
  working = working.slice(1);
1110
1116
  }
1111
1117
  }
@@ -1124,8 +1130,8 @@ export const runMainLoop = async (ctx) => {
1124
1130
  // newline. Without this, `\bind_named NAME 1 2 \gset pref02_ \echo X`
1125
1131
  // would still execute `\echo X` after the pipeline-mode `\gset`
1126
1132
  // rejection — vanilla suppresses it.
1127
- if (bres?.status === 'error') {
1128
- queryBuf = '';
1133
+ if (bres?.status === "error") {
1134
+ queryBuf = "";
1129
1135
  scanState = initialScanState();
1130
1136
  stmtLineNumber = 1;
1131
1137
  // Discard any trailing content on the SAME physical line — but NOT
@@ -1139,21 +1145,22 @@ export const runMainLoop = async (ctx) => {
1139
1145
  // discard). Without this branch a stack of `\gdesc\n\gdesc\n…`
1140
1146
  // lines collapses to a single dispatched `\gdesc` because the
1141
1147
  // first discard ate the second line.
1142
- if (working.startsWith('\r\n')) {
1148
+ if (working.startsWith("\r\n")) {
1143
1149
  working = working.slice(2);
1144
1150
  }
1145
- else if (working.startsWith('\n') || working.startsWith('\r')) {
1151
+ else if (working.startsWith("\n") ||
1152
+ working.startsWith("\r")) {
1146
1153
  working = working.slice(1);
1147
1154
  }
1148
1155
  else {
1149
- const nlIdx = working.indexOf('\n');
1150
- working = nlIdx === -1 ? '' : working.slice(nlIdx + 1);
1156
+ const nlIdx = working.indexOf("\n");
1157
+ working = nlIdx === -1 ? "" : working.slice(nlIdx + 1);
1151
1158
  }
1152
1159
  }
1153
1160
  // Backslash commands like \connect can also tear down the connection.
1154
1161
  if (checkConnectionLost())
1155
1162
  return;
1156
- if (bres?.status === 'error' && ctx.settings.onErrorStop) {
1163
+ if (bres?.status === "error" && ctx.settings.onErrorStop) {
1157
1164
  successResult = EXIT_USER;
1158
1165
  exitRequested = true;
1159
1166
  return;
@@ -1168,7 +1175,7 @@ export const runMainLoop = async (ctx) => {
1168
1175
  // current buffer; the line-reader feeds whole lines so this is
1169
1176
  // effectively unreachable in interactive use.)
1170
1177
  queryBuf += result.sql;
1171
- working = '';
1178
+ working = "";
1172
1179
  return;
1173
1180
  }
1174
1181
  };
@@ -1191,15 +1198,15 @@ export const runMainLoop = async (ctx) => {
1191
1198
  // residue (e.g. a trailing `\n` left over after a `;` boundary) counts
1192
1199
  // as empty so the next prompt is PROMPT1 not PROMPT2.
1193
1200
  const status = queryBuf.trim().length === 0
1194
- ? 'ready'
1195
- : scanState.promptStatus === 'ready'
1196
- ? 'continue'
1201
+ ? "ready"
1202
+ : scanState.promptStatus === "ready"
1203
+ ? "continue"
1197
1204
  : scanState.promptStatus;
1198
1205
  const prompt = computePrompt(status);
1199
1206
  // 1. Pending input from \i: process as a single chunk and loop again.
1200
1207
  const queued = consumeQueuedInput();
1201
1208
  if (queued !== null) {
1202
- await processChunk(queued.endsWith('\n') ? queued : queued + '\n');
1209
+ await processChunk(queued.endsWith("\n") ? queued : queued + "\n");
1203
1210
  continue;
1204
1211
  }
1205
1212
  // 2. Read the next line from stdin / line editor.
@@ -1210,7 +1217,7 @@ export const runMainLoop = async (ctx) => {
1210
1217
  catch (err) {
1211
1218
  // SignalError (Ctrl-C on an interactive line) — drop the partial
1212
1219
  // buffer and re-prompt, matching upstream psql.
1213
- if (err.name === 'SignalError') {
1220
+ if (err.name === "SignalError") {
1214
1221
  resetBuf();
1215
1222
  continue;
1216
1223
  }
@@ -1251,8 +1258,8 @@ export const runMainLoop = async (ctx) => {
1251
1258
  // the scanner is mid-quote and the line is part of the assembled
1252
1259
  // statement. ECHO=queries echoes only completed queries — handled
1253
1260
  // separately by the exec path.
1254
- if (ctx.settings.echo === 'all') {
1255
- ctx.stdout.write(line + '\n');
1261
+ if (ctx.settings.echo === "all") {
1262
+ ctx.stdout.write(line + "\n");
1256
1263
  }
1257
1264
  // 2a. `exit`/`quit` keyword handling.
1258
1265
  //
@@ -1273,7 +1280,7 @@ export const runMainLoop = async (ctx) => {
1273
1280
  exitRequested = true;
1274
1281
  break;
1275
1282
  }
1276
- ctx.stdout.write('Use \\q to quit.\n');
1283
+ ctx.stdout.write("Use \\q to quit.\n");
1277
1284
  continue;
1278
1285
  }
1279
1286
  // 2b. `help` keyword handling, same shape.
@@ -1286,14 +1293,14 @@ export const runMainLoop = async (ctx) => {
1286
1293
  ctx.stdout.write(HELP_TEXT);
1287
1294
  }
1288
1295
  else {
1289
- ctx.stdout.write('Use \\? for help.\n');
1296
+ ctx.stdout.write("Use \\? for help.\n");
1290
1297
  }
1291
1298
  continue;
1292
1299
  }
1293
1300
  // 3. Push to history once we have a complete submitted line (only
1294
1301
  // when there's something non-blank to record).
1295
1302
  reader.pushHistory(line);
1296
- await processChunk(line + '\n');
1303
+ await processChunk(line + "\n");
1297
1304
  }
1298
1305
  // EOF: if there's a residual non-empty buffer in non-interactive mode,
1299
1306
  // dispatch it (mirroring upstream's tail-of-MainLoop block). For
@@ -1310,18 +1317,18 @@ export const runMainLoop = async (ctx) => {
1310
1317
  const ok = await dispatchSendQuery(ctx, queryBuf);
1311
1318
  sigintState.inQuery = false;
1312
1319
  if (ctx.settings.db?.isClosed()) {
1313
- ctx.stderr.write('psql: error: connection to server was lost\n');
1320
+ ctx.stderr.write("psql: error: connection to server was lost\n");
1314
1321
  successResult = EXIT_BADCONN;
1315
1322
  }
1316
1323
  else if (!ok && ctx.settings.onErrorStop) {
1317
1324
  successResult = EXIT_USER;
1318
1325
  }
1319
1326
  }
1320
- queryBuf = '';
1327
+ queryBuf = "";
1321
1328
  }
1322
1329
  // Warn about unbalanced \if blocks (psql's tail-of-MainLoop check).
1323
1330
  if (!exitRequested && ctx.cond.depth() > 0) {
1324
- writeError(ctx, 'reached EOF without finding closing \\endif(s)');
1331
+ writeError(ctx, "reached EOF without finding closing \\endif(s)");
1325
1332
  if (ctx.settings.onErrorStop && ctx.settings.notty) {
1326
1333
  successResult = EXIT_USER;
1327
1334
  }