neonctl 2.28.0 → 2.29.1

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 (135) hide show
  1. package/README.md +71 -71
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  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 +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  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 +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +34 -34
  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 +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -34,21 +34,20 @@
34
34
  * additionally honours a separate "query output" file set via `\o`;
35
35
  * that wiring lives in WP-15 and we leave the hook in place.
36
36
  */
37
- import { spawnSync } from 'node:child_process';
38
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
39
- import { tmpdir } from 'node:os';
40
- import { join } from 'node:path';
41
- import { readLine } from '../io/input.js';
42
- import { getHistory } from '../io/history.js';
43
- import { slashUsage } from '../core/help.js';
44
- import { helpSQL } from '../core/help.js';
45
- import { writeOut, writeErr, parseBool } from './shared.js';
37
+ import { spawnSync } from "node:child_process";
38
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
39
+ import { tmpdir } from "node:os";
40
+ import { join } from "node:path";
41
+ import { helpSQL, slashUsage } from "../core/help.js";
42
+ import { getHistory } from "../io/history.js";
43
+ import { readLine } from "../io/input.js";
44
+ import { parseBool, writeErr, writeOut } from "./shared.js";
46
45
  /** `\q` / `\quit` — exit the REPL. */
47
46
  export const cmdQuit = {
48
- name: 'q',
49
- aliases: ['quit'],
50
- helpKey: 'q',
51
- run: () => Promise.resolve({ status: 'exit' }),
47
+ name: "q",
48
+ aliases: ["quit"],
49
+ helpKey: "q",
50
+ run: () => Promise.resolve({ status: "exit" }),
52
51
  };
53
52
  /**
54
53
  * `\!` — shell escape. Whole-line mode: the entire rest of the line is the
@@ -64,18 +63,18 @@ export const cmdQuit = {
64
63
  * resilient against environments where `sh` is unavailable.
65
64
  */
66
65
  export const cmdShell = {
67
- name: '!',
68
- argMode: 'whole-line',
69
- helpKey: '!',
66
+ name: "!",
67
+ argMode: "whole-line",
68
+ helpKey: "!",
70
69
  run: (ctx) => {
71
70
  const line = ctx.restOfLine().trim();
72
71
  try {
73
72
  if (line.length === 0) {
74
- const shell = process.env.SHELL ?? '/bin/sh';
75
- spawnSync(shell, ['-i'], { stdio: 'inherit' });
73
+ const shell = process.env.SHELL ?? "/bin/sh";
74
+ spawnSync(shell, ["-i"], { stdio: "inherit" });
76
75
  }
77
76
  else {
78
- spawnSync('sh', ['-c', line], { stdio: 'inherit' });
77
+ spawnSync("sh", ["-c", line], { stdio: "inherit" });
79
78
  }
80
79
  }
81
80
  catch {
@@ -83,28 +82,28 @@ export const cmdShell = {
83
82
  // keep running even when the child won't start. Status stays `ok` so
84
83
  // a failing `\!` is purely informational.
85
84
  }
86
- return Promise.resolve({ status: 'ok' });
85
+ return Promise.resolve({ status: "ok" });
87
86
  },
88
87
  };
89
88
  /** `\cd [dir]` — change cwd. No arg falls back to `$HOME`. */
90
89
  export const cmdCd = {
91
- name: 'cd',
92
- helpKey: 'cd',
90
+ name: "cd",
91
+ helpKey: "cd",
93
92
  run: (ctx) => {
94
- const dir = ctx.nextArg('normal');
93
+ const dir = ctx.nextArg("normal");
95
94
  const target = dir && dir.length > 0 ? dir : (process.env.HOME ?? null);
96
95
  if (!target) {
97
96
  writeErr(`\\${ctx.cmdName}: could not determine home directory\n`);
98
- return Promise.resolve({ status: 'error' });
97
+ return Promise.resolve({ status: "error" });
99
98
  }
100
99
  try {
101
100
  process.chdir(target);
102
- return Promise.resolve({ status: 'ok' });
101
+ return Promise.resolve({ status: "ok" });
103
102
  }
104
103
  catch (err) {
105
104
  const msg = err instanceof Error ? err.message : String(err);
106
105
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
107
- return Promise.resolve({ status: 'error' });
106
+ return Promise.resolve({ status: "error" });
108
107
  }
109
108
  },
110
109
  };
@@ -130,14 +129,14 @@ const runEcho = (ctx, write) => {
130
129
  let i = 0;
131
130
  while (i < ctx.rawArgs.length && /\s/.test(ctx.rawArgs[i]))
132
131
  i++;
133
- return (ctx.rawArgs.slice(i, i + 2) === '-n' &&
132
+ return (ctx.rawArgs.slice(i, i + 2) === "-n" &&
134
133
  (i + 2 === ctx.rawArgs.length || /\s/.test(ctx.rawArgs[i + 2])));
135
134
  })();
136
135
  for (;;) {
137
- const arg = ctx.nextArg('normal');
136
+ const arg = ctx.nextArg("normal");
138
137
  if (arg === null)
139
138
  break;
140
- if (first && firstArgIsUnquotedDashN && arg === '-n') {
139
+ if (first && firstArgIsUnquotedDashN && arg === "-n") {
141
140
  noNewline = true;
142
141
  first = false;
143
142
  continue;
@@ -145,20 +144,20 @@ const runEcho = (ctx, write) => {
145
144
  first = false;
146
145
  parts.push(arg);
147
146
  }
148
- const out = parts.join(' ') + (noNewline ? '' : '\n');
147
+ const out = parts.join(" ") + (noNewline ? "" : "\n");
149
148
  write(out);
150
- return { status: 'ok' };
149
+ return { status: "ok" };
151
150
  };
152
151
  /** `\echo` — write args to stdout. */
153
152
  export const cmdEcho = {
154
- name: 'echo',
155
- helpKey: 'echo',
153
+ name: "echo",
154
+ helpKey: "echo",
156
155
  run: (ctx) => Promise.resolve(runEcho(ctx, writeOut)),
157
156
  };
158
157
  /** `\qecho` — write args to the query output (logfile if set, else stdout). */
159
158
  export const cmdQecho = {
160
- name: 'qecho',
161
- helpKey: 'qecho',
159
+ name: "qecho",
160
+ helpKey: "qecho",
162
161
  run: (ctx) => {
163
162
  const { logfile } = ctx.settings;
164
163
  const write = (s) => {
@@ -174,8 +173,8 @@ export const cmdQecho = {
174
173
  };
175
174
  /** `\warn` — write args to stderr. */
176
175
  export const cmdWarn = {
177
- name: 'warn',
178
- helpKey: 'warn',
176
+ name: "warn",
177
+ helpKey: "warn",
179
178
  run: (ctx) => Promise.resolve(runEcho(ctx, writeErr)),
180
179
  };
181
180
  /**
@@ -193,27 +192,27 @@ export const cmdWarn = {
193
192
  * the first is the prompt and the second the variable.
194
193
  */
195
194
  export const cmdPrompt = {
196
- name: 'prompt',
197
- helpKey: 'prompt',
195
+ name: "prompt",
196
+ helpKey: "prompt",
198
197
  run: async (ctx) => {
199
198
  const args = [];
200
199
  for (;;) {
201
- const a = ctx.nextArg('normal');
200
+ const a = ctx.nextArg("normal");
202
201
  if (a === null)
203
202
  break;
204
203
  args.push(a);
205
204
  }
206
205
  // A leading `-` selects the no-echo (password) read path.
207
206
  let echo = true;
208
- if (args.length > 0 && args[0] === '-') {
207
+ if (args.length > 0 && args[0] === "-") {
209
208
  echo = false;
210
209
  args.shift();
211
210
  }
212
211
  if (args.length === 0) {
213
212
  writeErr(`\\${ctx.cmdName}: missing required argument\n`);
214
- return { status: 'error' };
213
+ return { status: "error" };
215
214
  }
216
- let promptText = '';
215
+ let promptText = "";
217
216
  let varname;
218
217
  if (args.length === 1) {
219
218
  varname = args[0];
@@ -225,9 +224,9 @@ export const cmdPrompt = {
225
224
  const line = await readLine(promptText, { echo });
226
225
  if (!ctx.settings.vars.set(varname, line)) {
227
226
  writeErr(`\\${ctx.cmdName}: invalid variable name "${varname}"\n`);
228
- return { status: 'error' };
227
+ return { status: "error" };
229
228
  }
230
- return { status: 'ok' };
229
+ return { status: "ok" };
231
230
  },
232
231
  };
233
232
  /**
@@ -250,30 +249,30 @@ export const cmdPrompt = {
250
249
  * accepts or returns a wording string.
251
250
  */
252
251
  export const cmdSet = {
253
- name: 'set',
254
- helpKey: 'set',
252
+ name: "set",
253
+ helpKey: "set",
255
254
  run: (ctx) => {
256
- const name = ctx.nextArg('normal');
255
+ const name = ctx.nextArg("normal");
257
256
  if (name === null) {
258
257
  // List all vars sorted by name.
259
258
  const entries = [...ctx.settings.vars.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
260
259
  for (const [k, v] of entries) {
261
260
  writeOut(`${k} = '${v}'\n`);
262
261
  }
263
- return Promise.resolve({ status: 'ok' });
262
+ return Promise.resolve({ status: "ok" });
264
263
  }
265
264
  const values = [];
266
265
  for (;;) {
267
- const a = ctx.nextArg('normal');
266
+ const a = ctx.nextArg("normal");
268
267
  if (a === null)
269
268
  break;
270
269
  values.push(a);
271
270
  }
272
- const value = values.join('');
271
+ const value = values.join("");
273
272
  const result = ctx.settings.vars.trySet(name, value);
274
273
  if (!result.ok) {
275
274
  const prefix = psqlErrorPrefix(ctx.settings);
276
- if (result.reason === 'invalid-name') {
275
+ if (result.reason === "invalid-name") {
277
276
  writeErr(`${prefix}invalid variable name: "${name}"\n`);
278
277
  }
279
278
  else if (result.error !== undefined) {
@@ -289,9 +288,9 @@ export const cmdSet = {
289
288
  // built-in hooks take this path, but third-party callers might.
290
289
  writeErr(`${prefix}error while setting variable "${name}"\n`);
291
290
  }
292
- return Promise.resolve({ status: 'error' });
291
+ return Promise.resolve({ status: "error" });
293
292
  }
294
- return Promise.resolve({ status: 'ok' });
293
+ return Promise.resolve({ status: "ok" });
295
294
  },
296
295
  };
297
296
  /**
@@ -310,49 +309,49 @@ export const cmdSet = {
310
309
  * the regress harness, which passes `--quiet`) produces no output.
311
310
  */
312
311
  export const cmdReset = {
313
- name: 'r',
314
- aliases: ['reset'],
315
- helpKey: 'r',
312
+ name: "r",
313
+ aliases: ["reset"],
314
+ helpKey: "r",
316
315
  run: (ctx) => {
317
316
  if (!ctx.settings.quiet) {
318
- writeOut('Query buffer reset (cleared).\n');
317
+ writeOut("Query buffer reset (cleared).\n");
319
318
  }
320
- return Promise.resolve({ status: 'reset-buf', newBuf: '' });
319
+ return Promise.resolve({ status: "reset-buf", newBuf: "" });
321
320
  },
322
321
  };
323
322
  /** `\unset varname` — unset a psql variable. */
324
323
  export const cmdUnset = {
325
- name: 'unset',
326
- helpKey: 'unset',
324
+ name: "unset",
325
+ helpKey: "unset",
327
326
  run: (ctx) => {
328
- const name = ctx.nextArg('normal');
327
+ const name = ctx.nextArg("normal");
329
328
  if (name === null) {
330
329
  writeErr(`\\${ctx.cmdName}: missing required argument\n`);
331
- return Promise.resolve({ status: 'error' });
330
+ return Promise.resolve({ status: "error" });
332
331
  }
333
332
  ctx.settings.vars.unset(name);
334
- return Promise.resolve({ status: 'ok' });
333
+ return Promise.resolve({ status: "ok" });
335
334
  },
336
335
  };
337
336
  export const cmdGetenv = {
338
- name: 'getenv',
339
- helpKey: 'getenv',
337
+ name: "getenv",
338
+ helpKey: "getenv",
340
339
  run: (ctx) => {
341
- const varname = ctx.nextArg('normal');
342
- const envname = ctx.nextArg('normal');
340
+ const varname = ctx.nextArg("normal");
341
+ const envname = ctx.nextArg("normal");
343
342
  if (varname === null || envname === null) {
344
343
  writeErr(`\\${ctx.cmdName}: missing required argument\n`);
345
- return Promise.resolve({ status: 'error' });
344
+ return Promise.resolve({ status: "error" });
346
345
  }
347
346
  const value = process.env[envname];
348
347
  if (value === undefined) {
349
- return Promise.resolve({ status: 'ok' });
348
+ return Promise.resolve({ status: "ok" });
350
349
  }
351
350
  if (!ctx.settings.vars.set(varname, value)) {
352
351
  writeErr(`\\${ctx.cmdName}: invalid variable name "${varname}"\n`);
353
- return Promise.resolve({ status: 'error' });
352
+ return Promise.resolve({ status: "error" });
354
353
  }
355
- return Promise.resolve({ status: 'ok' });
354
+ return Promise.resolve({ status: "ok" });
356
355
  },
357
356
  };
358
357
  /**
@@ -362,17 +361,17 @@ export const cmdGetenv = {
362
361
  * rejects names containing `=`.
363
362
  */
364
363
  export const cmdSetenv = {
365
- name: 'setenv',
366
- helpKey: 'setenv',
364
+ name: "setenv",
365
+ helpKey: "setenv",
367
366
  run: (ctx) => {
368
- const envname = ctx.nextArg('normal');
367
+ const envname = ctx.nextArg("normal");
369
368
  if (envname === null) {
370
369
  writeErr(`\\${ctx.cmdName}: missing required argument\n`);
371
- return Promise.resolve({ status: 'error' });
370
+ return Promise.resolve({ status: "error" });
372
371
  }
373
- if (envname.includes('=')) {
372
+ if (envname.includes("=")) {
374
373
  writeErr(`\\${ctx.cmdName}: environment variable name must not contain "="\n`);
375
- return Promise.resolve({ status: 'error' });
374
+ return Promise.resolve({ status: "error" });
376
375
  }
377
376
  // Upstream `exec_command_setenv` reads BOTH the name AND the value with
378
377
  // OT_NORMAL — `:VAR` substitution applies to the value so
@@ -381,7 +380,7 @@ export const cmdSetenv = {
381
380
  // value.) The mainloop context maintains a per-mode cursor, so using
382
381
  // a single mode for both calls also keeps positional reads in sync —
383
382
  // each cursor advances exactly once per call.
384
- const value = ctx.nextArg('normal');
383
+ const value = ctx.nextArg("normal");
385
384
  if (value === null) {
386
385
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
387
386
  delete process.env[envname];
@@ -389,7 +388,7 @@ export const cmdSetenv = {
389
388
  else {
390
389
  process.env[envname] = value;
391
390
  }
392
- return Promise.resolve({ status: 'ok' });
391
+ return Promise.resolve({ status: "ok" });
393
392
  },
394
393
  };
395
394
  /**
@@ -409,11 +408,11 @@ export const cmdSetenv = {
409
408
  * concatenate the severity directly (`prefix + 'ERROR: msg'`).
410
409
  */
411
410
  export const psqlErrorPrefix = (settings, lineNumber) => {
412
- if (settings.curCmdSource === 'file' && settings.inputfile) {
413
- const lineSuffix = lineNumber !== undefined ? String(lineNumber) : '';
411
+ if (settings.curCmdSource === "file" && settings.inputfile) {
412
+ const lineSuffix = lineNumber !== undefined ? String(lineNumber) : "";
414
413
  return `psql:${settings.inputfile}:${lineSuffix}: `;
415
414
  }
416
- return '';
415
+ return "";
417
416
  };
418
417
  /**
419
418
  * Walk past leading whitespace + `--` line comments + slash-star block
@@ -535,9 +534,9 @@ export const renderLineAndCaret = (sqlText, position) => {
535
534
  // The server's offset is 1-based and points at the failing character.
536
535
  const idx = Math.min(rebasedPos - 1, trimmed.length);
537
536
  // Find the line containing `idx`.
538
- let lineStart = trimmed.lastIndexOf('\n', idx - 1);
537
+ let lineStart = trimmed.lastIndexOf("\n", idx - 1);
539
538
  lineStart = lineStart === -1 ? 0 : lineStart + 1;
540
- let lineEnd = trimmed.indexOf('\n', lineStart);
539
+ let lineEnd = trimmed.indexOf("\n", lineStart);
541
540
  if (lineEnd === -1)
542
541
  lineEnd = trimmed.length;
543
542
  const lineText = trimmed.slice(lineStart, lineEnd);
@@ -550,15 +549,15 @@ export const renderLineAndCaret = (sqlText, position) => {
550
549
  let col = idx - lineStart;
551
550
  // Snap past trailing whitespace when the position lands inside it —
552
551
  // see the function header for the rationale (trim-on-send delta).
553
- const lineTrimEndLen = lineText.replace(/[ \t\f\v]+$/u, '').length;
552
+ const lineTrimEndLen = lineText.replace(/[ \t\f\v]+$/u, "").length;
554
553
  if (col >= lineTrimEndLen && col < lineText.length) {
555
554
  col = lineText.length;
556
555
  }
557
- const caretIndent = ' '.repeat(Math.max(0, col));
556
+ const caretIndent = " ".repeat(Math.max(0, col));
558
557
  const prefix = `LINE ${String(lineNumber)}: `;
559
558
  return {
560
559
  line: `${prefix}${lineText}`,
561
- caret: `${' '.repeat(prefix.length)}${caretIndent}^`,
560
+ caret: `${" ".repeat(prefix.length)}${caretIndent}^`,
562
561
  };
563
562
  };
564
563
  /**
@@ -588,10 +587,10 @@ export const renderLineAndCaret = (sqlText, position) => {
588
587
  * appears when we have both originating SQL text and a 1-based position
589
588
  * pointing inside it.
590
589
  */
591
- export const formatErrorReport = (e, verbosity = 'default', showContext = 'errors') => {
592
- const severity = e.severity ?? 'ERROR';
593
- const sqlstate = e.code ?? e.sqlstate ?? 'XX000';
594
- const message = e.message ?? '';
590
+ export const formatErrorReport = (e, verbosity = "default", showContext = "errors") => {
591
+ const severity = e.severity ?? "ERROR";
592
+ const sqlstate = e.code ?? e.sqlstate ?? "XX000";
593
+ const message = e.message ?? "";
595
594
  const out = [];
596
595
  // `sqlstate` mode is the upstream "just give me the code" flavour:
597
596
  // emit `<severity>: <sqlstate>` with NO message body. `verbose` mode
@@ -601,14 +600,14 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
601
600
  // Reference: upstream `pg_log_pre_callback` / `PQresultErrorMessage`
602
601
  // with `verbosity = PQERRORS_SQLSTATE`, which formats just
603
602
  // `severity: sqlstate\n` and stops.
604
- if (verbosity === 'sqlstate') {
603
+ if (verbosity === "sqlstate") {
605
604
  out.push(`${severity}: ${sqlstate}`);
606
605
  return out;
607
606
  }
608
- if (verbosity === 'verbose') {
607
+ if (verbosity === "verbose") {
609
608
  out.push(`${severity}: ${sqlstate}: ${message}`);
610
609
  }
611
- else if (verbosity === 'terse') {
610
+ else if (verbosity === "terse") {
612
611
  // Terse suppresses LINE/caret/DETAIL/HINT/CONTEXT, but it merges the
613
612
  // server's `position` into the severity line as `at character N` —
614
613
  // matches libpq's `pqGetErrorNotice3` with `PQERRORS_TERSE` (and
@@ -641,12 +640,12 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
641
640
  // formatter as an error report, so 'errors' and 'always' both include
642
641
  // CONTEXT, while 'never' suppresses it. Verbose verbosity unconditionally
643
642
  // includes CONTEXT.
644
- const includeContext = verbosity === 'verbose' || showContext !== 'never';
643
+ const includeContext = verbosity === "verbose" || showContext !== "never";
645
644
  if (includeContext && e.where) {
646
645
  out.push(`CONTEXT: ${e.where}`);
647
646
  }
648
- if (verbosity === 'verbose' && (e.routine || e.file || e.line)) {
649
- const location = (e.routine ?? '') + (e.file ? `, ${e.file}:${e.line ?? ''}` : '');
647
+ if (verbosity === "verbose" && (e.routine || e.file || e.line)) {
648
+ const location = (e.routine ?? "") + (e.file ? `, ${e.file}:${e.line ?? ""}` : "");
650
649
  out.push(`LOCATION: ${location}`);
651
650
  }
652
651
  return out;
@@ -671,27 +670,27 @@ export const formatErrorReport = (e, verbosity = 'default', showContext = 'error
671
670
  * we have both the originating SQL text and a server-provided position.
672
671
  */
673
672
  export const cmdErrverbose = {
674
- name: 'errverbose',
675
- helpKey: 'errverbose',
673
+ name: "errverbose",
674
+ helpKey: "errverbose",
676
675
  run: (ctx) => {
677
676
  const e = ctx.settings.lastErrorResult;
678
677
  if (!e || (!e.message && !e.sqlstate && !e.code)) {
679
678
  // Upstream `exec_command_errverbose` writes the "no previous error"
680
679
  // notice to stdout (via `printf`); only the verbose re-render goes to
681
680
  // stderr (via `pg_log_error`).
682
- writeOut('There is no previous error.\n');
683
- return Promise.resolve({ status: 'ok' });
681
+ writeOut("There is no previous error.\n");
682
+ return Promise.resolve({ status: "ok" });
684
683
  }
685
684
  // `\errverbose` always emits the full verbose form regardless of the
686
685
  // currently active VERBOSITY setting. Output is prefixed with the same
687
686
  // `psql:[<file>:<n>]:` tag upstream's `pg_log_pre_callback` adds — only
688
687
  // on the leading severity line; subsequent layers (LINE / caret / DETAIL
689
688
  // / HINT / LOCATION) stay unprefixed to match libpq's `PQresultErrorMessage`.
690
- const lines = formatErrorReport(e, 'verbose', 'always');
689
+ const lines = formatErrorReport(e, "verbose", "always");
691
690
  const prefix = psqlErrorPrefix(ctx.settings);
692
691
  const prefixed = [prefix + lines[0], ...lines.slice(1)];
693
- writeErr(prefixed.join('\n') + '\n');
694
- return Promise.resolve({ status: 'ok' });
692
+ writeErr(prefixed.join("\n") + "\n");
693
+ return Promise.resolve({ status: "ok" });
695
694
  },
696
695
  };
697
696
  /**
@@ -700,10 +699,10 @@ export const cmdErrverbose = {
700
699
  * upstream errors "Boolean expected" (review: minor divergences).
701
700
  */
702
701
  export const cmdTiming = {
703
- name: 'timing',
704
- helpKey: 'timing',
702
+ name: "timing",
703
+ helpKey: "timing",
705
704
  run: (ctx) => {
706
- const arg = ctx.nextArg('normal');
705
+ const arg = ctx.nextArg("normal");
707
706
  let next;
708
707
  if (arg === null) {
709
708
  next = !ctx.settings.timing;
@@ -712,13 +711,13 @@ export const cmdTiming = {
712
711
  const parsed = parseBool(arg);
713
712
  if (parsed === null) {
714
713
  writeErr(`\\${ctx.cmdName}: unrecognized value "${arg}" for "\\timing": Boolean expected\n`);
715
- return Promise.resolve({ status: 'error' });
714
+ return Promise.resolve({ status: "error" });
716
715
  }
717
716
  next = parsed;
718
717
  }
719
718
  ctx.settings.timing = next;
720
- writeOut(`Timing is ${next ? 'on' : 'off'}.\n`);
721
- return Promise.resolve({ status: 'ok' });
719
+ writeOut(`Timing is ${next ? "on" : "off"}.\n`);
720
+ return Promise.resolve({ status: "ok" });
722
721
  },
723
722
  };
724
723
  /**
@@ -772,11 +771,11 @@ LICENSE file for distribution terms.
772
771
  * Neon notice is appended after it.
773
772
  */
774
773
  export const cmdCopyright = {
775
- name: 'copyright',
776
- helpKey: 'copyright',
774
+ name: "copyright",
775
+ helpKey: "copyright",
777
776
  run: () => {
778
777
  writeOut(COPYRIGHT_TEXT + NEON_NOTICE);
779
- return Promise.resolve({ status: 'ok' });
778
+ return Promise.resolve({ status: "ok" });
780
779
  },
781
780
  };
782
781
  /**
@@ -787,7 +786,7 @@ export const cmdCopyright = {
787
786
  */
788
787
  const screenWidth = () => {
789
788
  const cols = process.stdout.columns;
790
- return typeof cols === 'number' && cols > 0 ? cols : 80;
789
+ return typeof cols === "number" && cols > 0 ? cols : 80;
791
790
  };
792
791
  /**
793
792
  * `\h [TOPIC]` (alias `\help`) — show SQL command help.
@@ -798,15 +797,15 @@ const screenWidth = () => {
798
797
  * matches. Mirrors upstream `exec_command_help` in `command.c`.
799
798
  */
800
799
  export const cmdHelpSQL = {
801
- name: 'h',
802
- aliases: ['help'],
803
- helpKey: 'h',
800
+ name: "h",
801
+ aliases: ["help"],
802
+ helpKey: "h",
804
803
  run: (ctx) => {
805
804
  // Upstream consumes the rest of the line in `OT_WHOLE_LINE` mode so
806
805
  // multi-word topics like "CREATE TABLE" come through intact.
807
806
  const topic = ctx.restOfLine();
808
807
  helpSQL(process.stdout, topic.length === 0 ? null : topic, screenWidth());
809
- return Promise.resolve({ status: 'ok' });
808
+ return Promise.resolve({ status: "ok" });
810
809
  },
811
810
  };
812
811
  /**
@@ -824,9 +823,9 @@ export const cmdHelpSQL = {
824
823
  * so a stray topic doesn't leak into the next command.
825
824
  */
826
825
  export const cmdSlashHelp = {
827
- name: '?',
828
- argMode: 'whole-line',
829
- helpKey: '?',
826
+ name: "?",
827
+ argMode: "whole-line",
828
+ helpKey: "?",
830
829
  run: (ctx) => {
831
830
  // Consume the rest of the line (`\? options`, `\? variables`) so the
832
831
  // cursor doesn't strand trailing text; we only render the command help.
@@ -834,7 +833,7 @@ export const cmdSlashHelp = {
834
833
  const out = process.stdout;
835
834
  const pager = Boolean(out.isTTY);
836
835
  slashUsage(out, pager);
837
- return Promise.resolve({ status: 'ok' });
836
+ return Promise.resolve({ status: "ok" });
838
837
  },
839
838
  };
840
839
  /**
@@ -850,7 +849,7 @@ export const resolveEditor = (env = process.env) => {
850
849
  const explicit = env.PSQL_EDITOR ?? env.EDITOR ?? env.VISUAL;
851
850
  if (explicit !== undefined && explicit.length > 0)
852
851
  return explicit;
853
- return process.platform === 'win32' ? 'notepad.exe' : 'vi';
852
+ return process.platform === "win32" ? "notepad.exe" : "vi";
854
853
  };
855
854
  /**
856
855
  * `\e` / `\edit [FILE] [LINE]` — edit the current query buffer (or a file)
@@ -875,43 +874,44 @@ export const resolveEditor = (env = process.env) => {
875
874
  * (`\e file`) and `\ef`/`\ev` on top.
876
875
  */
877
876
  export const cmdEdit = {
878
- name: 'e',
879
- aliases: ['edit'],
880
- argMode: 'whole-line',
881
- helpKey: 'e',
877
+ name: "e",
878
+ aliases: ["edit"],
879
+ argMode: "whole-line",
880
+ helpKey: "e",
882
881
  run: (ctx) => {
883
882
  // We don't yet support `\e FILE`; consume the args so they don't strand.
884
883
  ctx.restOfLine();
885
884
  const editor = resolveEditor();
886
885
  // psql seeds the temp file with the current query buffer. A trailing
887
886
  // newline keeps editors that expect newline-terminated files happy.
888
- const seed = ctx.queryBuf.length > 0 && !ctx.queryBuf.endsWith('\n')
889
- ? ctx.queryBuf + '\n'
887
+ const seed = ctx.queryBuf.length > 0 && !ctx.queryBuf.endsWith("\n")
888
+ ? ctx.queryBuf + "\n"
890
889
  : ctx.queryBuf;
891
890
  let dir = null;
892
891
  try {
893
- dir = mkdtempSync(join(tmpdir(), 'psql.edit.'));
894
- const file = join(dir, 'edit.sql');
895
- writeFileSync(file, seed, 'utf8');
896
- const result = spawnSync(editor, [file], { stdio: 'inherit' });
897
- if (result.error || (result.status !== null && result.status !== 0)) {
892
+ dir = mkdtempSync(join(tmpdir(), "psql.edit."));
893
+ const file = join(dir, "edit.sql");
894
+ writeFileSync(file, seed, "utf8");
895
+ const result = spawnSync(editor, [file], { stdio: "inherit" });
896
+ if (result.error ||
897
+ (result.status !== null && result.status !== 0)) {
898
898
  const why = result.error
899
899
  ? result.error.message
900
900
  : `editor exited with status ${String(result.status)}`;
901
901
  writeErr(`\\${ctx.cmdName}: ${why}\n`);
902
- return Promise.resolve({ status: 'error' });
902
+ return Promise.resolve({ status: "error" });
903
903
  }
904
- let edited = readFileSync(file, 'utf8');
904
+ let edited = readFileSync(file, "utf8");
905
905
  // Upstream drops a single trailing newline the editor may have added
906
906
  // so an unchanged round-trip restores the original buffer exactly.
907
- if (edited.endsWith('\n'))
907
+ if (edited.endsWith("\n"))
908
908
  edited = edited.slice(0, -1);
909
- return Promise.resolve({ status: 'reset-buf', newBuf: edited });
909
+ return Promise.resolve({ status: "reset-buf", newBuf: edited });
910
910
  }
911
911
  catch (err) {
912
912
  const msg = err instanceof Error ? err.message : String(err);
913
913
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
914
- return Promise.resolve({ status: 'error' });
914
+ return Promise.resolve({ status: "error" });
915
915
  }
916
916
  finally {
917
917
  if (dir) {
@@ -942,29 +942,29 @@ export const cmdEdit = {
942
942
  * populated as each line is submitted (see `io/history.ts`).
943
943
  */
944
944
  export const cmdS = {
945
- name: 's',
946
- helpKey: 's',
945
+ name: "s",
946
+ helpKey: "s",
947
947
  run: (ctx) => {
948
- const fname = ctx.nextArg('normal');
948
+ const fname = ctx.nextArg("normal");
949
949
  const entries = getHistory();
950
950
  // Each entry is one logical command; readline's `\s` prints them one
951
951
  // per line, so a trailing newline per entry reproduces that layout.
952
- const body = entries.map((e) => e + '\n').join('');
952
+ const body = entries.map((e) => e + "\n").join("");
953
953
  if (fname === null || fname.length === 0) {
954
954
  writeOut(body);
955
- return Promise.resolve({ status: 'ok' });
955
+ return Promise.resolve({ status: "ok" });
956
956
  }
957
957
  try {
958
- writeFileSync(fname, body, 'utf8');
958
+ writeFileSync(fname, body, "utf8");
959
959
  }
960
960
  catch (err) {
961
961
  const msg = err instanceof Error ? err.message : String(err);
962
962
  writeErr(`\\${ctx.cmdName}: ${msg}\n`);
963
- return Promise.resolve({ status: 'error' });
963
+ return Promise.resolve({ status: "error" });
964
964
  }
965
965
  if (!ctx.settings.quiet) {
966
966
  writeOut(`Wrote history to file "${fname}".\n`);
967
967
  }
968
- return Promise.resolve({ status: 'ok' });
968
+ return Promise.resolve({ status: "ok" });
969
969
  },
970
970
  };