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
@@ -1,4 +1,4 @@
1
- import { formatNumericLocale } from './units.js';
1
+ import { formatNumericLocale } from "./units.js";
2
2
  /**
3
3
  * Aligned tabular printer (psql's default output mode).
4
4
  *
@@ -312,13 +312,13 @@ export const padToWidth = (text, width, alignment) => {
312
312
  if (w >= width)
313
313
  return text;
314
314
  const pad = width - w;
315
- if (alignment === 'left')
316
- return text + ' '.repeat(pad);
317
- if (alignment === 'right')
318
- return ' '.repeat(pad) + text;
315
+ if (alignment === "left")
316
+ return text + " ".repeat(pad);
317
+ if (alignment === "right")
318
+ return " ".repeat(pad) + text;
319
319
  const leftPad = pad >> 1;
320
320
  const rightPad = pad - leftPad;
321
- return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
321
+ return " ".repeat(leftPad) + text + " ".repeat(rightPad);
322
322
  };
323
323
  // ---------------------------------------------------------------------------
324
324
  // Type heuristics & cell formatting (shared with vertical mode).
@@ -357,46 +357,46 @@ const isRightAlignedField = (oid) => RIGHT_ALIGNED_OIDS.has(oid);
357
357
  const renderCell = (cell, nullPrint, numericLocale) => {
358
358
  if (cell === null || cell === undefined)
359
359
  return nullPrint;
360
- if (typeof cell === 'string') {
360
+ if (typeof cell === "string") {
361
361
  return formatNumericLocale(cell, numericLocale);
362
362
  }
363
- if (typeof cell === 'number' || typeof cell === 'bigint') {
363
+ if (typeof cell === "number" || typeof cell === "bigint") {
364
364
  return formatNumericLocale(cell.toString(), numericLocale);
365
365
  }
366
- if (typeof cell === 'boolean')
367
- return cell ? 't' : 'f';
366
+ if (typeof cell === "boolean")
367
+ return cell ? "t" : "f";
368
368
  if (cell instanceof Date)
369
369
  return cell.toISOString();
370
370
  if (cell instanceof Uint8Array) {
371
- let hex = '\\x';
371
+ let hex = "\\x";
372
372
  for (const b of cell)
373
- hex += b.toString(16).padStart(2, '0');
373
+ hex += b.toString(16).padStart(2, "0");
374
374
  return hex;
375
375
  }
376
376
  return JSON.stringify(cell);
377
377
  };
378
378
  const ASCII_GLYPHS = {
379
- hrule: '-',
380
- vrule: '|',
381
- topLeft: '+',
382
- topMid: '+',
383
- topRight: '+',
384
- midLeft: '+',
385
- midMid: '+',
386
- midRight: '+',
387
- botLeft: '+',
388
- botMid: '+',
389
- botRight: '+',
390
- headerNlLeft: ' ',
391
- headerNlRight: '+',
392
- wrapLeft: '.',
393
- wrapRight: '.',
394
- nlLeft: ' ',
395
- nlRight: '+',
379
+ hrule: "-",
380
+ vrule: "|",
381
+ topLeft: "+",
382
+ topMid: "+",
383
+ topRight: "+",
384
+ midLeft: "+",
385
+ midMid: "+",
386
+ midRight: "+",
387
+ botLeft: "+",
388
+ botMid: "+",
389
+ botRight: "+",
390
+ headerNlLeft: " ",
391
+ headerNlRight: "+",
392
+ wrapLeft: ".",
393
+ wrapRight: ".",
394
+ nlLeft: " ",
395
+ nlRight: "+",
396
396
  wrapRightBorder: true,
397
- midvruleNl: '|',
398
- midvruleWrap: '|',
399
- midvruleBlank: '|',
397
+ midvruleNl: "|",
398
+ midvruleWrap: "|",
399
+ midvruleBlank: "|",
400
400
  };
401
401
  // `pg_asciiformat_old` — the legacy ASCII renderer kept around for
402
402
  // `\pset linestyle old-ascii`. Two visible quirks vs the modern ASCII
@@ -408,27 +408,27 @@ const ASCII_GLYPHS = {
408
408
  // joining cell has more `\n`-split lines below, `;` when it has more
409
409
  // wrap-split lines, ` ` when the cell is exhausted (`midvrule_blank`).
410
410
  const OLD_ASCII_GLYPHS = {
411
- hrule: '-',
412
- vrule: '|',
413
- topLeft: '+',
414
- topMid: '+',
415
- topRight: '+',
416
- midLeft: '+',
417
- midMid: '+',
418
- midRight: '+',
419
- botLeft: '+',
420
- botMid: '+',
421
- botRight: '+',
422
- headerNlLeft: '+',
423
- headerNlRight: ' ',
424
- wrapLeft: ' ',
425
- wrapRight: ' ',
426
- nlLeft: ' ',
427
- nlRight: ' ',
411
+ hrule: "-",
412
+ vrule: "|",
413
+ topLeft: "+",
414
+ topMid: "+",
415
+ topRight: "+",
416
+ midLeft: "+",
417
+ midMid: "+",
418
+ midRight: "+",
419
+ botLeft: "+",
420
+ botMid: "+",
421
+ botRight: "+",
422
+ headerNlLeft: "+",
423
+ headerNlRight: " ",
424
+ wrapLeft: " ",
425
+ wrapRight: " ",
426
+ nlLeft: " ",
427
+ nlRight: " ",
428
428
  wrapRightBorder: false,
429
- midvruleNl: ':',
430
- midvruleWrap: ';',
431
- midvruleBlank: ' ',
429
+ midvruleNl: ":",
430
+ midvruleWrap: ";",
431
+ midvruleBlank: " ",
432
432
  };
433
433
  // Light box-drawing glyphs (the only Unicode variant we expose today —
434
434
  // `unicode_border_linestyle=double` / `unicode_column_linestyle=double` /
@@ -452,35 +452,35 @@ const OLD_ASCII_GLYPHS = {
452
452
  // U+2534 ┴ Box Drawings Light Up and Horizontal (botMid)
453
453
  // U+2518 ┘ Box Drawings Light Up and Left (botRight)
454
454
  const UNICODE_GLYPHS = {
455
- hrule: '',
456
- vrule: '',
457
- topLeft: '',
458
- topMid: '',
459
- topRight: '',
460
- midLeft: '',
461
- midMid: '',
462
- midRight: '',
463
- botLeft: '',
464
- botMid: '',
465
- botRight: '',
455
+ hrule: "",
456
+ vrule: "",
457
+ topLeft: "",
458
+ topMid: "",
459
+ topRight: "",
460
+ midLeft: "",
461
+ midMid: "",
462
+ midRight: "",
463
+ botLeft: "",
464
+ botMid: "",
465
+ botRight: "",
466
466
  // U+21B5 ↵ "Downwards Arrow with Corner Leftwards" — newline marker.
467
467
  // U+2026 … "Horizontal Ellipsis" — in-cell wrap marker.
468
468
  // Matches `unicode_style` in upstream `fe_utils/print.c`.
469
- headerNlLeft: ' ',
470
- headerNlRight: '',
471
- wrapLeft: '',
472
- wrapRight: '',
473
- nlLeft: ' ',
474
- nlRight: '',
469
+ headerNlLeft: " ",
470
+ headerNlRight: "",
471
+ wrapLeft: "",
472
+ wrapRight: "",
473
+ nlLeft: " ",
474
+ nlRight: "",
475
475
  wrapRightBorder: true,
476
- midvruleNl: '',
477
- midvruleWrap: '',
478
- midvruleBlank: '',
476
+ midvruleNl: "",
477
+ midvruleWrap: "",
478
+ midvruleBlank: "",
479
479
  };
480
480
  const glyphsFor = (style) => {
481
- if (style === 'unicode')
481
+ if (style === "unicode")
482
482
  return UNICODE_GLYPHS;
483
- if (style === 'old-ascii')
483
+ if (style === "old-ascii")
484
484
  return OLD_ASCII_GLYPHS;
485
485
  return ASCII_GLYPHS;
486
486
  };
@@ -502,7 +502,7 @@ export const computeColumnWidths = (rs, topt) => {
502
502
  for (const row of rs.rows) {
503
503
  for (let i = 0; i < rs.fields.length; i++) {
504
504
  const cellText = renderCell(row[i], nullPrint, numericLocale);
505
- for (const line of cellText.split('\n')) {
505
+ for (const line of cellText.split("\n")) {
506
506
  const w = displayWidth(line);
507
507
  if (w > widths[i])
508
508
  widths[i] = w;
@@ -512,7 +512,7 @@ export const computeColumnWidths = (rs, topt) => {
512
512
  return widths;
513
513
  };
514
514
  const formatCell = (text) => {
515
- const lines = text.split('\n');
515
+ const lines = text.split("\n");
516
516
  let width = 0;
517
517
  for (const l of lines) {
518
518
  const w = displayWidth(l);
@@ -538,12 +538,12 @@ const buildRule = (widths, border, glyphs, position) => {
538
538
  let left;
539
539
  let mid;
540
540
  let right;
541
- if (position === 'top') {
541
+ if (position === "top") {
542
542
  left = glyphs.topLeft;
543
543
  mid = glyphs.topMid;
544
544
  right = glyphs.topRight;
545
545
  }
546
- else if (position === 'middle') {
546
+ else if (position === "middle") {
547
547
  left = glyphs.midLeft;
548
548
  mid = glyphs.midMid;
549
549
  right = glyphs.midRight;
@@ -553,7 +553,7 @@ const buildRule = (widths, border, glyphs, position) => {
553
553
  mid = glyphs.botMid;
554
554
  right = glyphs.botRight;
555
555
  }
556
- let out = '';
556
+ let out = "";
557
557
  if (border === 2 || border === 3) {
558
558
  out += left;
559
559
  }
@@ -564,7 +564,7 @@ const buildRule = (widths, border, glyphs, position) => {
564
564
  out += hrule.repeat(widths[i] + pad * 2);
565
565
  if (i < widths.length - 1) {
566
566
  if (border === 0) {
567
- out += ' '; // single space between columns in border 0
567
+ out += " "; // single space between columns in border 0
568
568
  }
569
569
  else {
570
570
  out += mid;
@@ -580,10 +580,10 @@ const renderHorizontal = (rs, opts, wrapped) => {
580
580
  const topt = opts.topt;
581
581
  const border = topt.border;
582
582
  const tuplesOnly = topt.tuplesOnly;
583
- const nullPrint = opts.nullPrint !== '' ? opts.nullPrint : topt.nullPrint;
583
+ const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
584
584
  const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
585
585
  const headers = rs.fields.map((f) => f.name);
586
- const aligns = rs.fields.map((f) => isRightAlignedField(f.dataTypeID) ? 'right' : 'left');
586
+ const aligns = rs.fields.map((f) => isRightAlignedField(f.dataTypeID) ? "right" : "left");
587
587
  // Pre-render & measure every cell.
588
588
  const cellGrid = rs.rows.map((row) => row.map((cell) => formatCell(renderCell(cell, nullPrint, topt.numericLocale))));
589
589
  const headerCells = headers.map((h) => formatCell(h));
@@ -664,7 +664,8 @@ const renderHorizontal = (rs, opts, wrapped) => {
664
664
  // - width_wrap[i] > width_header[i] (header is the floor;
665
665
  // can't shrink below it without overlapping the header).
666
666
  if (widthAverage[i] > 0 && widths[i] > headerW) {
667
- const ratio = widths[i] / widthAverage[i] + maxWidth[i] * 0.01;
667
+ const ratio = widths[i] / widthAverage[i] +
668
+ maxWidth[i] * 0.01;
668
669
  if (ratio > maxRatio) {
669
670
  maxRatio = ratio;
670
671
  worstCol = i;
@@ -679,8 +680,8 @@ const renderHorizontal = (rs, opts, wrapped) => {
679
680
  }
680
681
  }
681
682
  }
682
- let out = '';
683
- const newline = '\n';
683
+ let out = "";
684
+ const newline = "\n";
684
685
  // Compute total table width once: needed for title centring (and only
685
686
  // there at the moment). Mirrors upstream `width_total` from
686
687
  // print.c lines 935-950: when the title is narrower than the table,
@@ -699,12 +700,12 @@ const renderHorizontal = (rs, opts, wrapped) => {
699
700
  }
700
701
  else {
701
702
  const pad = (widthTotal - titleW) >> 1;
702
- out += ' '.repeat(pad) + topt.title + newline;
703
+ out += " ".repeat(pad) + topt.title + newline;
703
704
  }
704
705
  }
705
706
  // Top rule: emitted when border == 2 or 3.
706
707
  if (!tuplesOnly && (border === 2 || border === 3)) {
707
- out += buildRule(widths, border, glyphs, 'top') + newline;
708
+ out += buildRule(widths, border, glyphs, "top") + newline;
708
709
  }
709
710
  // ------- Header row(s) -------
710
711
  if (!tuplesOnly) {
@@ -732,13 +733,13 @@ const renderHorizontal = (rs, opts, wrapped) => {
732
733
  // `nl` the cell has more `\n`-split content below
733
734
  // (header_nl_left/header_nl_right marker)
734
735
  // `none` the cell is done (already emitted its last line)
735
- const cellWrapPrev = headerColLines.map((l) => (lineIdx > 0 && lineIdx < l.length ? 'nl' : 'none'));
736
- const cellWrapNext = headerColLines.map((l) => (lineIdx < l.length - 1 ? 'nl' : 'none'));
737
- out += renderHeaderLine(headerColLines.map((l) => l[lineIdx] ?? ''), widths, border, glyphs, cellWrapPrev, cellWrapNext, lineIdx === 0);
736
+ const cellWrapPrev = headerColLines.map((l) => (lineIdx > 0 && lineIdx < l.length ? "nl" : "none"));
737
+ const cellWrapNext = headerColLines.map((l) => (lineIdx < l.length - 1 ? "nl" : "none"));
738
+ out += renderHeaderLine(headerColLines.map((l) => l[lineIdx] ?? ""), widths, border, glyphs, cellWrapPrev, cellWrapNext, lineIdx === 0);
738
739
  out += newline;
739
740
  }
740
741
  // Header rule.
741
- out += buildRule(widths, border, glyphs, 'middle') + newline;
742
+ out += buildRule(widths, border, glyphs, "middle") + newline;
742
743
  }
743
744
  // ------- Data rows -------
744
745
  for (let r = 0; r < cellGrid.length; r++) {
@@ -771,14 +772,16 @@ const renderHorizontal = (rs, opts, wrapped) => {
771
772
  const kinds = [];
772
773
  const lines = cell.lines;
773
774
  for (let si = 0; si < lines.length; si++) {
774
- const chunks = wrapped ? wrapLine(lines[si], widths[i]) : [lines[si]];
775
+ const chunks = wrapped
776
+ ? wrapLine(lines[si], widths[i])
777
+ : [lines[si]];
775
778
  for (let ci = 0; ci < chunks.length; ci++) {
776
779
  if (si === 0 && ci === 0)
777
- kinds.push('start');
780
+ kinds.push("start");
778
781
  else if (ci === 0)
779
- kinds.push('nl');
782
+ kinds.push("nl");
780
783
  else
781
- kinds.push('wrap');
784
+ kinds.push("wrap");
782
785
  }
783
786
  }
784
787
  return kinds;
@@ -794,26 +797,26 @@ const renderHorizontal = (rs, opts, wrapped) => {
794
797
  // still emitting more content.
795
798
  const cellWrapPrev = colLineKinds.map((kinds, j) => {
796
799
  if (li === 0 || li >= colLines[j].length)
797
- return 'none';
798
- return kinds[li] === 'wrap' ? 'wrap' : 'nl';
800
+ return "none";
801
+ return kinds[li] === "wrap" ? "wrap" : "nl";
799
802
  });
800
803
  const cellWrapNext = colLineKinds.map((kinds, j) => {
801
804
  const nextIdx = li + 1;
802
805
  if (nextIdx >= colLines[j].length)
803
- return 'none';
804
- return kinds[nextIdx] === 'wrap' ? 'wrap' : 'nl';
806
+ return "none";
807
+ return kinds[nextIdx] === "wrap" ? "wrap" : "nl";
805
808
  });
806
- out += renderDataLine(colLines.map((l) => l[li] ?? ''), widths, aligns, border, glyphs, cellWrapPrev, cellWrapNext, li === 0);
809
+ out += renderDataLine(colLines.map((l) => l[li] ?? ""), widths, aligns, border, glyphs, cellWrapPrev, cellWrapNext, li === 0);
807
810
  out += newline;
808
811
  }
809
812
  // Heavy borders (border == 3) insert a rule between rows.
810
813
  if (border === 3 && r < cellGrid.length - 1) {
811
- out += buildRule(widths, border, glyphs, 'middle') + newline;
814
+ out += buildRule(widths, border, glyphs, "middle") + newline;
812
815
  }
813
816
  }
814
817
  // Bottom rule when border 2/3.
815
818
  if (!tuplesOnly && (border === 2 || border === 3)) {
816
- out += buildRule(widths, border, glyphs, 'bottom') + newline;
819
+ out += buildRule(widths, border, glyphs, "bottom") + newline;
817
820
  }
818
821
  // Footer: (N rows) + trailing blank line. Upstream's
819
822
  // `print_aligned_text` emits `printTableAddFooter()` and then
@@ -831,7 +834,7 @@ const renderHorizontal = (rs, opts, wrapped) => {
831
834
  const hasUserFooters = !!opts.footers && opts.footers.length > 0;
832
835
  if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) {
833
836
  const n = rs.rows.length;
834
- out += `(${String(n)} ${n === 1 ? 'row' : 'rows'})` + newline;
837
+ out += `(${String(n)} ${n === 1 ? "row" : "rows"})` + newline;
835
838
  }
836
839
  if (!tuplesOnly && opts.footers) {
837
840
  for (const f of opts.footers)
@@ -870,9 +873,9 @@ const pickMidvrule = (glyphs,
870
873
  _left, right, firstLine) => {
871
874
  if (firstLine)
872
875
  return glyphs.vrule;
873
- if (right === 'wrap')
876
+ if (right === "wrap")
874
877
  return glyphs.midvruleWrap;
875
- if (right === 'nl')
878
+ if (right === "nl")
876
879
  return glyphs.midvruleNl;
877
880
  return glyphs.midvruleBlank;
878
881
  };
@@ -885,16 +888,16 @@ _left, right, firstLine) => {
885
888
  * without border)
886
889
  */
887
890
  const dataRightMarker = (state, glyphs, isLast, border) => {
888
- if (state === 'wrap')
891
+ if (state === "wrap")
889
892
  return glyphs.wrapRight;
890
- if (state === 'nl')
893
+ if (state === "nl")
891
894
  return glyphs.nlRight;
892
895
  // state === 'none': trailing edge — only emit a space when there's
893
896
  // something AFTER us (another column to follow, or the right border
894
897
  // of a border=2/3 box). For the LAST cell on a borderless row, nothing.
895
898
  if (isLast && border !== 2 && border !== 3)
896
- return '';
897
- return ' ';
899
+ return "";
900
+ return " ";
898
901
  };
899
902
  /**
900
903
  * Left-marker glyph for a data row's cell. Mirrors `wrap[j]` at upstream
@@ -907,11 +910,11 @@ const dataRightMarker = (state, glyphs, isLast, border) => {
907
910
  * upstream skips this entirely (no leading marker).
908
911
  */
909
912
  const dataLeftMarker = (state, glyphs) => {
910
- if (state === 'wrap')
913
+ if (state === "wrap")
911
914
  return glyphs.wrapLeft;
912
- if (state === 'nl')
915
+ if (state === "nl")
913
916
  return glyphs.nlLeft;
914
- return ' ';
917
+ return " ";
915
918
  };
916
919
  /**
917
920
  * Render one display-line of a header row. Centered cells, fixed
@@ -942,7 +945,7 @@ cellWrapNext,
942
945
  // exhausted on that line — so the choice can't be made per-column.
943
946
  firstLine) => {
944
947
  const { vrule } = glyphs;
945
- let out = '';
948
+ let out = "";
946
949
  if (border === 2 || border === 3)
947
950
  out += vrule;
948
951
  for (let i = 0; i < cells.length; i++) {
@@ -955,12 +958,12 @@ firstLine) => {
955
958
  // cells too at border=0 — the inter-column slot doubles as the
956
959
  // leading-gutter slot of col[i].
957
960
  if (border !== 0 || (!glyphs.wrapRightBorder && i > 0)) {
958
- out += firstLine ? ' ' : glyphs.headerNlLeft;
961
+ out += firstLine ? " " : glyphs.headerNlLeft;
959
962
  }
960
963
  // Header cells are always centered and padded to full width
961
964
  // (upstream emits `%-*s%s%-*s` regardless of position — no
962
965
  // skip-padding-on-last-cell quirk on header rows).
963
- out += padToWidth(cells[i], widths[i], 'center');
966
+ out += padToWidth(cells[i], widths[i], "center");
964
967
  // Trailing marker. Upstream `print.c` line 1003-1005:
965
968
  // if (opt_border != 0 || format->wrap_right_border)
966
969
  // fputs(!header_done[i] ? format->header_nl_right : " ", fout);
@@ -971,7 +974,7 @@ firstLine) => {
971
974
  // border=0 — the inter-column space comes from the NEXT cell's
972
975
  // leading-gutter emit on the next loop iteration.
973
976
  if (border !== 0 || glyphs.wrapRightBorder) {
974
- out += cellWrapNext[i] !== 'none' ? glyphs.headerNlRight : ' ';
977
+ out += cellWrapNext[i] !== "none" ? glyphs.headerNlRight : " ";
975
978
  }
976
979
  // Column divider (not on the last column). For border 0 ASCII the
977
980
  // trailing-marker slot above already consumes the inter-column gap.
@@ -1016,7 +1019,7 @@ cellWrapNext,
1016
1019
  // emitted).
1017
1020
  firstLine) => {
1018
1021
  const { vrule } = glyphs;
1019
- let out = '';
1022
+ let out = "";
1020
1023
  if (border === 2 || border === 3)
1021
1024
  out += vrule;
1022
1025
  for (let i = 0; i < cells.length; i++) {
@@ -1036,15 +1039,15 @@ firstLine) => {
1036
1039
  // right-aligned cells always pad on the left side regardless.
1037
1040
  // `finalspaces` = (border == 2 || not last column).
1038
1041
  const finalspaces = border === 2 || border === 3 || !isLast;
1039
- const needsTrailingPad = finalspaces || cellWrapNext[i] !== 'none';
1040
- if (aligns[i] === 'right') {
1042
+ const needsTrailingPad = finalspaces || cellWrapNext[i] !== "none";
1043
+ if (aligns[i] === "right") {
1041
1044
  // Right-aligned cells always get full padding on the left so
1042
1045
  // content right-aligns. Trailing pad only when finalspaces (or
1043
1046
  // wrap, to keep marker aligned).
1044
- out += padToWidth(cells[i], widths[i], 'right');
1047
+ out += padToWidth(cells[i], widths[i], "right");
1045
1048
  }
1046
1049
  else if (needsTrailingPad) {
1047
- out += padToWidth(cells[i], widths[i], 'left');
1050
+ out += padToWidth(cells[i], widths[i], "left");
1048
1051
  }
1049
1052
  else {
1050
1053
  // Last cell, no wrap state — emit raw content (no trailing pad).
@@ -1063,7 +1066,7 @@ firstLine) => {
1063
1066
  if (border !== 0 || glyphs.wrapRightBorder) {
1064
1067
  out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
1065
1068
  }
1066
- else if (isLast && cellWrapNext[i] !== 'none') {
1069
+ else if (isLast && cellWrapNext[i] !== "none") {
1067
1070
  out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
1068
1071
  }
1069
1072
  // Column divider. Upstream lines 1158-1169: in old-ascii the
@@ -1091,13 +1094,13 @@ const wrapLine = (line, width) => {
1091
1094
  if (displayWidth(line) <= width)
1092
1095
  return [line];
1093
1096
  const chunks = [];
1094
- let buf = '';
1097
+ let buf = "";
1095
1098
  let bufW = 0;
1096
1099
  for (const ch of line) {
1097
1100
  const cw = codePointWidth(ch.codePointAt(0) ?? 0);
1098
1101
  if (bufW + cw > width && buf.length > 0) {
1099
1102
  chunks.push(buf);
1100
- buf = '';
1103
+ buf = "";
1101
1104
  bufW = 0;
1102
1105
  }
1103
1106
  buf += ch;
@@ -1114,25 +1117,25 @@ const renderVertical = (rs, opts) => {
1114
1117
  const topt = opts.topt;
1115
1118
  const border = topt.border;
1116
1119
  const tuplesOnly = topt.tuplesOnly;
1117
- const nullPrint = opts.nullPrint !== '' ? opts.nullPrint : topt.nullPrint;
1120
+ const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
1118
1121
  const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
1119
1122
  // `old-ascii` swaps several emission decisions (leading-slot at border<2,
1120
1123
  // suppressed data trailing marker, alternate midvrule glyph on
1121
1124
  // continuation lines). Track once at the top so the per-line code stays
1122
1125
  // readable.
1123
- const oldAscii = topt.unicodeBorderLineStyle === 'old-ascii';
1126
+ const oldAscii = topt.unicodeBorderLineStyle === "old-ascii";
1124
1127
  const headers = rs.fields.map((f) => f.name);
1125
1128
  // Width of the name column = max header line width (multi-line headers
1126
1129
  // count per-line; upstream `pg_wcssize` returns the widest line).
1127
1130
  let nameWidth = 0;
1128
1131
  let hmultiline = false;
1129
1132
  for (const h of headers) {
1130
- for (const line of h.split('\n')) {
1133
+ for (const line of h.split("\n")) {
1131
1134
  const w = displayWidth(line);
1132
1135
  if (w > nameWidth)
1133
1136
  nameWidth = w;
1134
1137
  }
1135
- if (h.includes('\n'))
1138
+ if (h.includes("\n"))
1136
1139
  hmultiline = true;
1137
1140
  }
1138
1141
  // Width of the value column = max value width across all rows.
@@ -1141,9 +1144,9 @@ const renderVertical = (rs, opts) => {
1141
1144
  const cellGrid = rs.rows.map((row) => row.map((cell) => renderCell(cell, nullPrint, topt.numericLocale)));
1142
1145
  for (const row of cellGrid) {
1143
1146
  for (const v of row) {
1144
- if (v.includes('\n'))
1147
+ if (v.includes("\n"))
1145
1148
  dmultiline = true;
1146
- for (const line of v.split('\n')) {
1149
+ for (const line of v.split("\n")) {
1147
1150
  const w = displayWidth(line);
1148
1151
  if (w > valueWidth)
1149
1152
  valueWidth = w;
@@ -1168,7 +1171,7 @@ const renderVertical = (rs, opts) => {
1168
1171
  // The two-pass loop turns `dmultiline` on the first iteration if a
1169
1172
  // wrap is needed, then recomputes with the bumped swidth. We
1170
1173
  // implement that as a single pass over the two possible swidths.
1171
- const wrappedMode = topt.format === 'wrapped';
1174
+ const wrappedMode = topt.format === "wrapped";
1172
1175
  let dwidth = valueWidth;
1173
1176
  if (wrappedMode) {
1174
1177
  const outputColumns = topt.columns > 0 ? topt.columns : topt.envColumns;
@@ -1190,7 +1193,9 @@ const renderVertical = (rs, opts) => {
1190
1193
  const labelOverhead = border === 0 ? 9 : border === 1 ? 12 : 15;
1191
1194
  const nrows = cellGrid.length;
1192
1195
  const rwidth = labelOverhead +
1193
- (nrows > 0 ? 1 + Math.floor(Math.log10(Math.max(1, nrows))) : 0);
1196
+ (nrows > 0
1197
+ ? 1 + Math.floor(Math.log10(Math.max(1, nrows)))
1198
+ : 0);
1194
1199
  const compute = (swidth) => {
1195
1200
  let width = nameWidth + swidth + valueWidth;
1196
1201
  if (width < rwidth)
@@ -1218,8 +1223,8 @@ const renderVertical = (rs, opts) => {
1218
1223
  dwidth = newDwidth < valueWidth ? newDwidth : valueWidth;
1219
1224
  }
1220
1225
  }
1221
- let out = '';
1222
- const newline = '\n';
1226
+ let out = "";
1227
+ const newline = "\n";
1223
1228
  if (!tuplesOnly && topt.title) {
1224
1229
  out += topt.title + newline;
1225
1230
  }
@@ -1230,7 +1235,7 @@ const renderVertical = (rs, opts) => {
1230
1235
  // against vanilla psql 18: `\\pset expanded on` then `select 1
1231
1236
  // where false;` emits `(0 rows)\n\n`.
1232
1237
  if (!tuplesOnly)
1233
- out += '(0 rows)' + newline;
1238
+ out += "(0 rows)" + newline;
1234
1239
  if (!tuplesOnly && opts.footers) {
1235
1240
  for (const f of opts.footers)
1236
1241
  out += f + newline;
@@ -1276,8 +1281,8 @@ const renderVertical = (rs, opts) => {
1276
1281
  out += newline;
1277
1282
  }
1278
1283
  for (let c = 0; c < headers.length; c++) {
1279
- const headerLines = headers[c].split('\n');
1280
- const dataLines = cellGrid[r][c].split('\n');
1284
+ const headerLines = headers[c].split("\n");
1285
+ const dataLines = cellGrid[r][c].split("\n");
1281
1286
  out += renderVerticalCell(headerLines, dataLines, nameWidth, dwidth, border, glyphs, hmultiline, emitHeaderMarker, emitDataMarker, emitHeaderLeftSlot, oldAscii);
1282
1287
  }
1283
1288
  }
@@ -1364,7 +1369,7 @@ const renderRecordHeader = (record, nameWidth, valueWidth, border, glyphs, isFir
1364
1369
  // blank line padded to width; at border>=1 a continuous hrule run.
1365
1370
  if (record === 0) {
1366
1371
  if (border === 0) {
1367
- return ' '.repeat(nameWidth + valueWidth);
1372
+ return " ".repeat(nameWidth + valueWidth);
1368
1373
  }
1369
1374
  if (border === 1) {
1370
1375
  const rowWidth = nameWidth + 3 + valueWidth;
@@ -1389,7 +1394,7 @@ const renderRecordHeader = (record, nameWidth, valueWidth, border, glyphs, isFir
1389
1394
  const target = nameWidth + valueWidth;
1390
1395
  if (label.length >= target)
1391
1396
  return label;
1392
- return label + ' '.repeat(target - label.length);
1397
+ return label + " ".repeat(target - label.length);
1393
1398
  }
1394
1399
  const label = `[ RECORD ${String(record)} ]`;
1395
1400
  // border 1: `-[ RECORD N ]---+---------`
@@ -1437,7 +1442,11 @@ const renderRecordHeader = (record, nameWidth, valueWidth, border, glyphs, isFir
1437
1442
  // top/mid junction, then rightSegLen hrules, then top/mid right
1438
1443
  // corner.
1439
1444
  const leftPadded = leftCore + hrule.repeat(leftSegLen - leftCore.length);
1440
- return (outerLeft + leftPadded + outerMid + hrule.repeat(rightSegLen) + outerRight);
1445
+ return (outerLeft +
1446
+ leftPadded +
1447
+ outerMid +
1448
+ hrule.repeat(rightSegLen) +
1449
+ outerRight);
1441
1450
  }
1442
1451
  // Label overflows the left segment. Drop the mid junction and pad
1443
1452
  // hrules between the outer corners. When the natural row inner width
@@ -1482,7 +1491,7 @@ const renderRecordHeader = (record, nameWidth, valueWidth, border, glyphs, isFir
1482
1491
  */
1483
1492
  const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, glyphs, hmultiline, emitHeaderMarker, emitDataMarker, emitHeaderLeftSlot, oldAscii) => {
1484
1493
  const { vrule } = glyphs;
1485
- let out = '';
1494
+ let out = "";
1486
1495
  const dataChunks = [];
1487
1496
  for (const src of dataLines) {
1488
1497
  if (dwidth > 0 && displayWidth(src) > dwidth) {
@@ -1528,10 +1537,10 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1528
1537
  // `border==2 || (hmultiline && oldAscii)`. Old-ascii routes the
1529
1538
  // continuation marker through the LEFT side via header_nl_left.
1530
1539
  if (emitHeaderLeftSlot) {
1531
- out += hLine > 0 ? glyphs.headerNlLeft : ' ';
1540
+ out += hLine > 0 ? glyphs.headerNlLeft : " ";
1532
1541
  }
1533
1542
  const text = headerLines[hLine];
1534
- out += padToWidth(text, nameWidth, 'left');
1543
+ out += padToWidth(text, nameWidth, "left");
1535
1544
  const hasMore = hLine + 1 < headerLines.length;
1536
1545
  if (hasMore) {
1537
1546
  if (emitHeaderMarker)
@@ -1540,7 +1549,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1540
1549
  }
1541
1550
  else {
1542
1551
  if (emitHeaderMarker)
1543
- out += ' ';
1552
+ out += " ";
1544
1553
  hcomplete = true;
1545
1554
  }
1546
1555
  }
@@ -1556,7 +1565,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1556
1565
  swidth++;
1557
1566
  if (border === 0 && hmultiline && !oldAscii)
1558
1567
  swidth++;
1559
- out += ' '.repeat(swidth);
1568
+ out += " ".repeat(swidth);
1560
1569
  }
1561
1570
  // ---- Separator ----
1562
1571
  // Border > 0 emits the column rule. Upstream `print.c` 1710-1719
@@ -1580,7 +1589,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1580
1589
  // Leading slot: " " on the first chunk of a source line,
1581
1590
  // wrap_left on a wrap continuation. ALWAYS emitted (upstream
1582
1591
  // print.c line 1731 has no border guard).
1583
- out += offset === 0 ? ' ' : glyphs.wrapLeft;
1592
+ out += offset === 0 ? " " : glyphs.wrapLeft;
1584
1593
  out += chunk.text;
1585
1594
  // Mirror upstream's `offset += bytes_to_output`: bumped even on
1586
1595
  // the last chunk of a cell, so the next iteration (header still
@@ -1589,7 +1598,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1589
1598
  const isLastChunk = dLine === dataChunks.length - 1;
1590
1599
  const nextChunk = !isLastChunk ? dataChunks[dLine + 1] : null;
1591
1600
  let needsPad = false;
1592
- let markerGlyph = '';
1601
+ let markerGlyph = "";
1593
1602
  if (nextChunk && !chunk.isWrapEnd) {
1594
1603
  // Wrap continuation: next chunk continues THIS source line.
1595
1604
  if (emitDataMarker) {
@@ -1613,7 +1622,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1613
1622
  if (border > 1) {
1614
1623
  // Border 2/3: pad to dwidth, trailing space, then vrule.
1615
1624
  needsPad = true;
1616
- markerGlyph = ' ';
1625
+ markerGlyph = " ";
1617
1626
  }
1618
1627
  dcomplete = true;
1619
1628
  // Offset stays bumped — header-tail iterations should see
@@ -1622,7 +1631,7 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1622
1631
  if (needsPad) {
1623
1632
  const pad = dwidth - chunk.width;
1624
1633
  if (pad > 0)
1625
- out += ' '.repeat(pad);
1634
+ out += " ".repeat(pad);
1626
1635
  }
1627
1636
  out += markerGlyph;
1628
1637
  // ---- Right border ----
@@ -1635,10 +1644,10 @@ const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, g
1635
1644
  // (upstream: `fprintf(fout, "%*s %s\n", dwidth, "", rightvrule)`
1636
1645
  // — note the TWO trailing spaces between the dwidth pad and rule).
1637
1646
  if (border === 2 || border === 3) {
1638
- out += ' '.repeat(dwidth) + ' ' + vrule;
1647
+ out += " ".repeat(dwidth) + " " + vrule;
1639
1648
  }
1640
1649
  }
1641
- out += '\n';
1650
+ out += "\n";
1642
1651
  }
1643
1652
  return out;
1644
1653
  };
@@ -1661,9 +1670,9 @@ const horizontalTotalWidth = (rs, topt) => {
1661
1670
  return overhead + widths.reduce((a, b) => a + b, 0);
1662
1671
  };
1663
1672
  const chooseExpanded = (rs, topt) => {
1664
- if (topt.expanded === 'on')
1673
+ if (topt.expanded === "on")
1665
1674
  return true;
1666
- if (topt.expanded === 'auto') {
1675
+ if (topt.expanded === "auto") {
1667
1676
  const maxColumns = topt.columns > 0 ? topt.columns : topt.envColumns;
1668
1677
  if (maxColumns <= 0)
1669
1678
  return false;
@@ -1675,10 +1684,10 @@ const chooseExpanded = (rs, topt) => {
1675
1684
  // Public printer.
1676
1685
  // ---------------------------------------------------------------------------
1677
1686
  export const alignedPrinter = {
1678
- format: 'aligned',
1687
+ format: "aligned",
1679
1688
  printQuery(rs, opts, out) {
1680
1689
  const expanded = chooseExpanded(rs, opts.topt);
1681
- const wrapped = opts.topt.format === 'wrapped';
1690
+ const wrapped = opts.topt.format === "wrapped";
1682
1691
  let text;
1683
1692
  if (expanded) {
1684
1693
  text = renderVertical(rs, opts);
@@ -1703,7 +1712,7 @@ const renderEmpty = (rs, opts) => {
1703
1712
  const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
1704
1713
  const headerCells = rs.fields.map((f) => formatCell(f.name));
1705
1714
  const widths = headerCells.map((c) => c.width);
1706
- let out = '';
1715
+ let out = "";
1707
1716
  if (!tuplesOnly && topt.title) {
1708
1717
  // Centre the title above the table, matching the horizontal-path
1709
1718
  // logic (and upstream `print_aligned_text` `width_total` formula).
@@ -1717,33 +1726,33 @@ const renderEmpty = (rs, opts) => {
1717
1726
  const widthTotal = overhead + widths.reduce((a, b) => a + b, 0);
1718
1727
  const titleW = displayWidth(topt.title);
1719
1728
  if (titleW >= widthTotal) {
1720
- out += topt.title + '\n';
1729
+ out += topt.title + "\n";
1721
1730
  }
1722
1731
  else {
1723
- out += ' '.repeat((widthTotal - titleW) >> 1) + topt.title + '\n';
1732
+ out += " ".repeat((widthTotal - titleW) >> 1) + topt.title + "\n";
1724
1733
  }
1725
1734
  }
1726
1735
  if (!tuplesOnly && (border === 2 || border === 3)) {
1727
- out += buildRule(widths, border, glyphs, 'top') + '\n';
1736
+ out += buildRule(widths, border, glyphs, "top") + "\n";
1728
1737
  }
1729
1738
  if (!tuplesOnly) {
1730
- const noneStates = headerCells.map(() => 'none');
1739
+ const noneStates = headerCells.map(() => "none");
1731
1740
  out +=
1732
- renderHeaderLine(headerCells.map((c) => c.lines[0] ?? ''), widths, border, glyphs, noneStates, noneStates, true) + '\n';
1733
- out += buildRule(widths, border, glyphs, 'middle') + '\n';
1741
+ renderHeaderLine(headerCells.map((c) => c.lines[0] ?? ""), widths, border, glyphs, noneStates, noneStates, true) + "\n";
1742
+ out += buildRule(widths, border, glyphs, "middle") + "\n";
1734
1743
  }
1735
1744
  if (!tuplesOnly && (border === 2 || border === 3)) {
1736
- out += buildRule(widths, border, glyphs, 'bottom') + '\n';
1745
+ out += buildRule(widths, border, glyphs, "bottom") + "\n";
1737
1746
  }
1738
1747
  // User footers suppress the default `(0 rows)` row counter — mirrors
1739
1748
  // upstream `footers_with_default` (print.c lines 397-413).
1740
1749
  const hasUserFooters = !!opts.footers && opts.footers.length > 0;
1741
1750
  if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) {
1742
- out += '(0 rows)\n';
1751
+ out += "(0 rows)\n";
1743
1752
  }
1744
1753
  if (!tuplesOnly && opts.footers) {
1745
1754
  for (const f of opts.footers)
1746
- out += f + '\n';
1755
+ out += f + "\n";
1747
1756
  }
1748
1757
  // Trailing blank line between query results, mirroring the horizontal /
1749
1758
  // vertical code paths (which append `\n` via `printTableCleanup`).
@@ -1751,6 +1760,6 @@ const renderEmpty = (rs, opts) => {
1751
1760
  // multiple back-to-back `\d`-family queries run together. Emitted
1752
1761
  // unconditionally (matches `fputc('\n', fout)` on print.c line 1196 /
1753
1762
  // 1821 — both are outside the `!opt_tuples_only` guard).
1754
- out += '\n';
1763
+ out += "\n";
1755
1764
  return out;
1756
1765
  };