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
@@ -27,11 +27,11 @@
27
27
  * is the default and `servername` is set. The connection layer wires
28
28
  * `servername` to the configured host before calling us.
29
29
  */
30
- import * as tls from 'node:tls';
31
- import { createHash, createPrivateKey } from 'node:crypto';
32
- import { promises as fs, appendFileSync } from 'node:fs';
33
- import * as path from 'node:path';
34
- import { SSLRequest } from './protocol.js';
30
+ import { createHash, createPrivateKey } from "node:crypto";
31
+ import { appendFileSync, promises as fs } from "node:fs";
32
+ import * as path from "node:path";
33
+ import * as tls from "node:tls";
34
+ import { SSLRequest } from "./protocol.js";
35
35
  /**
36
36
  * Hash of the peer cert for `tls-server-end-point`. libpq always uses SHA-256
37
37
  * of the DER-encoded certificate; we match that.
@@ -43,9 +43,9 @@ export function computeChannelBindingData(cert) {
43
43
  // marks it as `Buffer | undefined` on some versions, hence the guard.
44
44
  const raw = cert.raw;
45
45
  if (!raw || raw.length === 0) {
46
- throw new Error('TLS channel binding: peer certificate has no DER bytes');
46
+ throw new Error("TLS channel binding: peer certificate has no DER bytes");
47
47
  }
48
- return createHash('sha256').update(raw).digest();
48
+ return createHash("sha256").update(raw).digest();
49
49
  }
50
50
  /**
51
51
  * Send SSLRequest, read the 1-byte server response, and either upgrade the
@@ -73,32 +73,32 @@ export function computeChannelBindingData(cert) {
73
73
  * fallback, so a server that does not speak TLS surfaces the handshake
74
74
  * failure rather than a quiet downgrade.
75
75
  */
76
- export async function negotiateTls(socket, sslMode, tlsOpts = {}, fileOpts = {}, negotiation = 'postgres') {
77
- if (sslMode === 'disable') {
78
- return { kind: 'plain', socket };
76
+ export async function negotiateTls(socket, sslMode, tlsOpts = {}, fileOpts = {}, negotiation = "postgres") {
77
+ if (sslMode === "disable") {
78
+ return { kind: "plain", socket };
79
79
  }
80
80
  // Direct SSL (libpq `sslnegotiation=direct`, PG 17+): skip the `SSLRequest`
81
81
  // probe and start the TLS handshake straight away. The parse layer has
82
82
  // already rejected weak sslmodes, so this path is only reached with an
83
83
  // encrypted mode — never falling back to plaintext.
84
- if (negotiation === 'direct') {
84
+ if (negotiation === "direct") {
85
85
  const mergedOpts = await loadTlsFileOptions(tlsOpts, fileOpts, sslMode);
86
86
  return upgradeToTls(socket, mergedOpts, fileOpts.sslkeylogfile, fileOpts.sslkey,
87
87
  /* requireAlpn */ true);
88
88
  }
89
89
  const reply = await sendSslRequest(socket);
90
- if (reply === 'S') {
90
+ if (reply === "S") {
91
91
  const mergedOpts = await loadTlsFileOptions(tlsOpts, fileOpts, sslMode);
92
92
  return upgradeToTls(socket, mergedOpts, fileOpts.sslkeylogfile, fileOpts.sslkey);
93
93
  }
94
94
  // reply === 'N': server refused TLS.
95
- if (sslMode === 'require' ||
96
- sslMode === 'verify-ca' ||
97
- sslMode === 'verify-full') {
95
+ if (sslMode === "require" ||
96
+ sslMode === "verify-ca" ||
97
+ sslMode === "verify-full") {
98
98
  throw new Error(`SSL connection required (sslmode=${sslMode}) but server refused (replied 'N')`);
99
99
  }
100
100
  // 'allow' / 'prefer': fall back to plain text.
101
- return { kind: 'plain', socket };
101
+ return { kind: "plain", socket };
102
102
  }
103
103
  /**
104
104
  * Read each non-empty file path in `fileOpts` and merge the bytes onto a
@@ -126,12 +126,12 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
126
126
  // validate the chain. Mirror that so a stale / placeholder
127
127
  // `sslrootcert=` doesn't blow up sslmode=require connections.
128
128
  const needsRootCert = sslMode === undefined ||
129
- sslMode === 'verify-ca' ||
130
- sslMode === 'verify-full';
129
+ sslMode === "verify-ca" ||
130
+ sslMode === "verify-full";
131
131
  if (needsRootCert &&
132
132
  fileOpts.sslrootcert !== undefined &&
133
- fileOpts.sslrootcert !== '') {
134
- if (fileOpts.sslrootcert === 'system') {
133
+ fileOpts.sslrootcert !== "") {
134
+ if (fileOpts.sslrootcert === "system") {
135
135
  // `sslrootcert=system`: use the OS / OpenSSL trust store instead of a
136
136
  // file. libpq's OpenSSL build honours OpenSSL's $SSL_CERT_FILE (a single
137
137
  // bundle) and $SSL_CERT_DIR (a directory of hashed CA files); we read
@@ -140,11 +140,11 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
140
140
  // for verify-* modes) is left intact.
141
141
  const systemCas = [];
142
142
  const sslCertFile = process.env.SSL_CERT_FILE;
143
- if (sslCertFile !== undefined && sslCertFile !== '') {
144
- systemCas.push(await readPem('sslrootcert', sslCertFile, 'CERTIFICATE'));
143
+ if (sslCertFile !== undefined && sslCertFile !== "") {
144
+ systemCas.push(await readPem("sslrootcert", sslCertFile, "CERTIFICATE"));
145
145
  }
146
146
  const sslCertDir = process.env.SSL_CERT_DIR;
147
- if (sslCertDir !== undefined && sslCertDir !== '') {
147
+ if (sslCertDir !== undefined && sslCertDir !== "") {
148
148
  systemCas.push(...(await readCaDir(sslCertDir)));
149
149
  }
150
150
  if (systemCas.length === 1) {
@@ -155,7 +155,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
155
155
  }
156
156
  }
157
157
  else {
158
- merged.ca = await readPem('sslrootcert', fileOpts.sslrootcert, 'CERTIFICATE');
158
+ merged.ca = await readPem("sslrootcert", fileOpts.sslrootcert, "CERTIFICATE");
159
159
  }
160
160
  }
161
161
  // libpq `sslcertmode` gates whether the client cert/key are sent.
@@ -163,30 +163,30 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
163
163
  // - `require`: a cert MUST be configured (we only honour an explicit
164
164
  // `sslcert`, not libpq's default `~/.postgresql/postgresql.crt`).
165
165
  // - `allow` / unset: current behaviour (send when present).
166
- const certMode = fileOpts.sslcertmode ?? 'allow';
167
- const clientCert = fileOpts.sslcert !== undefined && fileOpts.sslcert !== ''
166
+ const certMode = fileOpts.sslcertmode ?? "allow";
167
+ const clientCert = fileOpts.sslcert !== undefined && fileOpts.sslcert !== ""
168
168
  ? fileOpts.sslcert
169
169
  : undefined;
170
- if (certMode === 'require' && clientCert === undefined) {
170
+ if (certMode === "require" && clientCert === undefined) {
171
171
  throw new Error(`sslcertmode value "require" requires a client certificate`);
172
172
  }
173
- if (certMode !== 'disable') {
173
+ if (certMode !== "disable") {
174
174
  if (clientCert !== undefined) {
175
- merged.cert = await readPem('sslcert', clientCert, 'CERTIFICATE');
175
+ merged.cert = await readPem("sslcert", clientCert, "CERTIFICATE");
176
176
  }
177
- if (fileOpts.sslkey !== undefined && fileOpts.sslkey !== '') {
177
+ if (fileOpts.sslkey !== undefined && fileOpts.sslkey !== "") {
178
178
  await assertKeyPermissions(fileOpts.sslkey);
179
- merged.key = await readPem('sslkey', fileOpts.sslkey, 'PRIVATE KEY');
179
+ merged.key = await readPem("sslkey", fileOpts.sslkey, "PRIVATE KEY");
180
180
  }
181
181
  }
182
182
  // CRLs come from a single file (`sslcrl`) and/or every file in a directory
183
183
  // (`sslcrldir`). Node's `crl` option accepts an array of PEM buffers, so we
184
184
  // collect each source and only set `crl` when at least one was read.
185
185
  const crls = [];
186
- if (fileOpts.sslcrl !== undefined && fileOpts.sslcrl !== '') {
187
- crls.push(await readPem('sslcrl', fileOpts.sslcrl));
186
+ if (fileOpts.sslcrl !== undefined && fileOpts.sslcrl !== "") {
187
+ crls.push(await readPem("sslcrl", fileOpts.sslcrl));
188
188
  }
189
- if (fileOpts.sslcrldir !== undefined && fileOpts.sslcrldir !== '') {
189
+ if (fileOpts.sslcrldir !== undefined && fileOpts.sslcrldir !== "") {
190
190
  crls.push(...(await readCrlDir(fileOpts.sslcrldir)));
191
191
  }
192
192
  if (crls.length === 1) {
@@ -198,7 +198,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
198
198
  // sslpassword is plumbed through verbatim — OpenSSL applies it when it
199
199
  // sees an encrypted key. Empty string is "no passphrase" (libpq's
200
200
  // convention) so we skip it.
201
- if (fileOpts.sslpassword !== undefined && fileOpts.sslpassword !== '') {
201
+ if (fileOpts.sslpassword !== undefined && fileOpts.sslpassword !== "") {
202
202
  merged.passphrase = fileOpts.sslpassword;
203
203
  }
204
204
  // sslkeylogfile is not a tls.connect option; the keylog listener is wired
@@ -206,7 +206,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
206
206
  // by opening it for append, so an unwritable path fails fast at connect
207
207
  // time with `could not open sslkeylogfile "<path>": <reason>` rather than
208
208
  // silently dropping keys mid-handshake.
209
- if (fileOpts.sslkeylogfile !== undefined && fileOpts.sslkeylogfile !== '') {
209
+ if (fileOpts.sslkeylogfile !== undefined && fileOpts.sslkeylogfile !== "") {
210
210
  await assertKeyLogFileWritable(fileOpts.sslkeylogfile);
211
211
  }
212
212
  return merged;
@@ -219,7 +219,7 @@ export async function loadTlsFileOptions(tlsOpts, fileOpts, sslMode) {
219
219
  */
220
220
  async function assertKeyLogFileWritable(filePath) {
221
221
  try {
222
- const handle = await fs.open(filePath, 'a');
222
+ const handle = await fs.open(filePath, "a");
223
223
  await handle.close();
224
224
  }
225
225
  catch (err) {
@@ -232,19 +232,19 @@ function isPemArmored(bytes) {
232
232
  // A PEM file is ASCII text; scan a bounded prefix (skipping leading
233
233
  // whitespace libpq tolerates) for the armor marker. DER is binary and will
234
234
  // not contain this token at the front.
235
- const head = bytes.subarray(0, 64).toString('latin1');
236
- return head.includes('-----BEGIN');
235
+ const head = bytes.subarray(0, 64).toString("latin1");
236
+ return head.includes("-----BEGIN");
237
237
  }
238
238
  /**
239
239
  * Wrap raw DER bytes in the requested PEM armor: base64 the DER, split into
240
240
  * 64-char lines (the PEM convention), and bracket with the BEGIN/END markers.
241
241
  */
242
242
  function derToPem(der, armor) {
243
- const b64 = der.toString('base64');
243
+ const b64 = der.toString("base64");
244
244
  const lines = b64.match(/.{1,64}/g) ?? [];
245
- const body = lines.join('\n');
245
+ const body = lines.join("\n");
246
246
  const pem = `-----BEGIN ${armor}-----\n${body}\n-----END ${armor}-----\n`;
247
- return Buffer.from(pem, 'ascii');
247
+ return Buffer.from(pem, "ascii");
248
248
  }
249
249
  /**
250
250
  * Convert a DER-encoded private key to canonical PKCS#8 PEM, the way libpq
@@ -258,11 +258,11 @@ function derToPem(der, armor) {
258
258
  */
259
259
  function derPrivateKeyToPem(der) {
260
260
  let lastErr;
261
- for (const type of ['pkcs8', 'pkcs1', 'sec1']) {
261
+ for (const type of ["pkcs8", "pkcs1", "sec1"]) {
262
262
  try {
263
- const key = createPrivateKey({ key: der, format: 'der', type });
264
- const pem = key.export({ format: 'pem', type: 'pkcs8' });
265
- return typeof pem === 'string' ? Buffer.from(pem, 'ascii') : pem;
263
+ const key = createPrivateKey({ key: der, format: "der", type });
264
+ const pem = key.export({ format: "pem", type: "pkcs8" });
265
+ return typeof pem === "string" ? Buffer.from(pem, "ascii") : pem;
266
266
  }
267
267
  catch (err) {
268
268
  lastErr = err;
@@ -291,7 +291,7 @@ async function readPem(label, filePath, derArmor) {
291
291
  if (derArmor !== undefined && !isPemArmored(bytes)) {
292
292
  // Private keys need format-aware decoding (PKCS#8 / PKCS#1 / SEC1);
293
293
  // certs are a single ASN.1 shape and wrap directly.
294
- return derArmor === 'PRIVATE KEY'
294
+ return derArmor === "PRIVATE KEY"
295
295
  ? derPrivateKeyToPem(bytes)
296
296
  : derToPem(bytes, derArmor);
297
297
  }
@@ -310,7 +310,7 @@ async function readPem(label, filePath, derArmor) {
310
310
  * carry a private key here).
311
311
  */
312
312
  async function assertKeyPermissions(keyPath) {
313
- if (process.platform === 'win32')
313
+ if (process.platform === "win32")
314
314
  return;
315
315
  let stat;
316
316
  try {
@@ -355,7 +355,7 @@ async function readCrlDir(dirPath) {
355
355
  for (const entry of entries) {
356
356
  if (!entry.isFile())
357
357
  continue;
358
- out.push(await readPem('sslcrldir', path.join(dirPath, entry.name)));
358
+ out.push(await readPem("sslcrldir", path.join(dirPath, entry.name)));
359
359
  }
360
360
  return out;
361
361
  }
@@ -381,7 +381,7 @@ export async function readCaDir(dirPath) {
381
381
  for (const entry of entries) {
382
382
  if (!entry.isFile())
383
383
  continue;
384
- out.push(await readPem('SSL_CERT_DIR', path.join(dirPath, entry.name), 'CERTIFICATE'));
384
+ out.push(await readPem("SSL_CERT_DIR", path.join(dirPath, entry.name), "CERTIFICATE"));
385
385
  }
386
386
  return out;
387
387
  }
@@ -403,7 +403,7 @@ export function sendSslRequest(socket) {
403
403
  if (chunk.length === 0)
404
404
  return;
405
405
  const first = String.fromCharCode(chunk[0]);
406
- if (first !== 'S' && first !== 'N') {
406
+ if (first !== "S" && first !== "N") {
407
407
  cleanup();
408
408
  reject(new Error(`Unexpected SSLRequest response byte 0x${chunk[0].toString(16)}`));
409
409
  return;
@@ -416,11 +416,11 @@ export function sendSslRequest(socket) {
416
416
  resolve(first);
417
417
  };
418
418
  const cleanup = () => {
419
- socket.removeListener('data', onData);
420
- socket.removeListener('error', onError);
419
+ socket.removeListener("data", onData);
420
+ socket.removeListener("error", onError);
421
421
  };
422
- socket.on('data', onData);
423
- socket.on('error', onError);
422
+ socket.on("data", onData);
423
+ socket.on("error", onError);
424
424
  socket.write(SSLRequest());
425
425
  });
426
426
  }
@@ -455,16 +455,16 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
455
455
  // Encrypted client-key decrypt failure (wrong / missing `sslpassword`).
456
456
  // OpenSSL throws this synchronously out of `tls.connect`; reshape it to
457
457
  // libpq's `could not load private key file "<path>": ... bad decrypt`.
458
- if (code === 'ERR_OSSL_BAD_DECRYPT' || /bad decrypt/i.test(msg)) {
459
- const where = keyPath !== undefined && keyPath !== ''
458
+ if (code === "ERR_OSSL_BAD_DECRYPT" || /bad decrypt/i.test(msg)) {
459
+ const where = keyPath !== undefined && keyPath !== ""
460
460
  ? `private key file "${keyPath}"`
461
- : 'private key file';
461
+ : "private key file";
462
462
  const mapped = new Error(`could not load ${where}: ${msg}`);
463
463
  mapped.cause = err;
464
464
  return mapped;
465
465
  }
466
- if (code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
467
- const host = servername ?? '';
466
+ if (code === "ERR_TLS_CERT_ALTNAME_INVALID") {
467
+ const host = servername ?? "";
468
468
  const mapped = new Error(`server certificate for "${host}" does not match host name "${host}"`);
469
469
  mapped.cause = err;
470
470
  return mapped;
@@ -474,10 +474,10 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
474
474
  // `SELF_SIGNED_CERT_IN_CHAIN`, `CERT_HAS_EXPIRED`, etc. libpq collapses
475
475
  // them all to `certificate verify failed`.
476
476
  const isVerifyFailure = code !== undefined &&
477
- code !== 'ERR_TLS_CERT_ALTNAME_INVALID' &&
477
+ code !== "ERR_TLS_CERT_ALTNAME_INVALID" &&
478
478
  /CERT|SIGNATURE|SELF_SIGNED|UNABLE_TO|CHAIN|EXPIRED|NOT_YET_VALID|INVALID_CA/.test(code);
479
479
  if (isVerifyFailure || /certificate verify failed/i.test(msg)) {
480
- const mapped = new Error('certificate verify failed');
480
+ const mapped = new Error("certificate verify failed");
481
481
  mapped.cause = err;
482
482
  return mapped;
483
483
  }
@@ -485,7 +485,7 @@ export function mapTlsHandshakeError(err, servername, keyPath) {
485
485
  }
486
486
  /** Pull the SNI `servername` from the TLS options for error messages. */
487
487
  function getServername(tlsOpts) {
488
- return typeof tlsOpts.servername === 'string'
488
+ return typeof tlsOpts.servername === "string"
489
489
  ? tlsOpts.servername
490
490
  : undefined;
491
491
  }
@@ -498,7 +498,7 @@ requireAlpn = false) {
498
498
  return new Promise((resolve, reject) => {
499
499
  let tlsSocket;
500
500
  const cleanup = () => {
501
- tlsSocket?.removeListener('error', onError);
501
+ tlsSocket?.removeListener("error", onError);
502
502
  };
503
503
  const onError = (err) => {
504
504
  cleanup();
@@ -523,10 +523,11 @@ requireAlpn = false) {
523
523
  // Enforce the mandatory `postgresql` ALPN for direct SSL. Without
524
524
  // it a TLS terminator / HTTPS proxy with a valid host cert would be
525
525
  // accepted and the startup packet sent in the blind (review #9).
526
- if (requireAlpn && established.alpnProtocol !== 'postgresql') {
526
+ if (requireAlpn &&
527
+ established.alpnProtocol !== "postgresql") {
527
528
  cleanup();
528
529
  established.destroy();
529
- reject(new Error('direct SSL connection requires ALPN, but the server did ' +
530
+ reject(new Error("direct SSL connection requires ALPN, but the server did " +
530
531
  'not negotiate the "postgresql" protocol'));
531
532
  return;
532
533
  }
@@ -538,7 +539,7 @@ requireAlpn = false) {
538
539
  // `getPeerCertificate(true)` for compatibility.
539
540
  const x509 = established.getPeerX509Certificate?.();
540
541
  if (x509?.raw && x509.raw.length > 0) {
541
- channelBindingData = createHash('sha256')
542
+ channelBindingData = createHash("sha256")
542
543
  .update(x509.raw)
543
544
  .digest();
544
545
  }
@@ -549,7 +550,8 @@ requireAlpn = false) {
549
550
  // accept that channel binding is unavailable.
550
551
  const peerCert = established.getPeerCertificate(true);
551
552
  if (peerCert?.raw && peerCert.raw.length > 0) {
552
- channelBindingData = computeChannelBindingData(peerCert);
553
+ channelBindingData =
554
+ computeChannelBindingData(peerCert);
553
555
  }
554
556
  }
555
557
  }
@@ -558,18 +560,22 @@ requireAlpn = false) {
558
560
  // path will fall back to SCRAM-SHA-256 (non-PLUS).
559
561
  channelBindingData = null;
560
562
  }
561
- resolve({ kind: 'tls', socket: established, channelBindingData });
563
+ resolve({
564
+ kind: "tls",
565
+ socket: established,
566
+ channelBindingData,
567
+ });
562
568
  });
563
569
  }
564
570
  catch (err) {
565
571
  reject(mapTlsHandshakeError(err instanceof Error ? err : new Error(String(err)), getServername(tlsOpts), keyPath));
566
572
  return;
567
573
  }
568
- tlsSocket.on('error', onError);
574
+ tlsSocket.on("error", onError);
569
575
  // libpq `sslkeylogfile`: append each emitted key-log line so the
570
576
  // handshake can be decrypted offline. The path was pre-checked for
571
577
  // writability in loadTlsFileOptions.
572
- if (sslkeylogfile !== undefined && sslkeylogfile !== '') {
578
+ if (sslkeylogfile !== undefined && sslkeylogfile !== "") {
573
579
  attachKeyLogListener(tlsSocket, sslkeylogfile);
574
580
  }
575
581
  });
@@ -585,12 +591,12 @@ requireAlpn = false) {
585
591
  * fake socket without a real TLS handshake. Exported for tests.
586
592
  */
587
593
  export function attachKeyLogListener(socket, filePath) {
588
- socket.on('keylog', (line) => {
594
+ socket.on("keylog", (line) => {
589
595
  try {
590
596
  appendFileSync(filePath, line);
591
597
  }
592
598
  catch (err) {
593
- socket.emit('error', err instanceof Error ? err : new Error(String(err)));
599
+ socket.emit("error", err instanceof Error ? err : new Error(String(err)));
594
600
  }
595
601
  });
596
602
  }
@@ -1,14 +1,13 @@
1
1
  // Typed client helpers for the branch object-storage (bucket/object) API.
2
2
  //
3
3
  // These endpoints are part of the Neon object-storage surface (the "Buckets"
4
- // tag in the public API). They are not yet exposed as typed methods on the
5
- // published `@neondatabase/api-client` package, so the request/response types
6
- // and the thin call helpers live here. They are implemented on top of the
7
- // api-client's public `request()` method, which means they reuse the exact
8
- // same authentication, base URL, headers and retry behaviour as every other
9
- // neonctl command. When the generated client gains these methods, the call
10
- // sites in `src/commands/bucket.ts` can switch over with no behavioural
11
- // change.
4
+ // tag in the public API). They are not yet exposed as typed methods on
5
+ // `@neon/sdk`, so the request/response types and the thin call helpers live
6
+ // here. They are implemented on top of the API client's low-level `request()`
7
+ // method, which reuses the exact same authentication, base URL, headers and
8
+ // retry behaviour as every other neonctl command. When the SDK gains these
9
+ // methods, the call sites in `src/commands/bucket.ts` can switch over with no
10
+ // behavioural change.
12
11
  const bucketsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/buckets`;
13
12
  const bucketPath = (projectId, branchId, bucketName) => `${bucketsPath(projectId, branchId)}/${encodeURIComponent(bucketName)}`;
14
13
  /**
@@ -24,9 +23,9 @@ export const createProjectBranchBucket = (apiClient, { projectId, branchId, name
24
23
  }
25
24
  return apiClient.request({
26
25
  path: bucketsPath(projectId, branchId),
27
- method: 'POST',
26
+ method: "POST",
28
27
  body,
29
- format: 'json',
28
+ format: "json",
30
29
  secure: true,
31
30
  });
32
31
  };
@@ -37,8 +36,8 @@ export const createProjectBranchBucket = (apiClient, { projectId, branchId, name
37
36
  */
38
37
  export const listProjectBranchBuckets = (apiClient, { projectId, branchId }) => apiClient.request({
39
38
  path: bucketsPath(projectId, branchId),
40
- method: 'GET',
41
- format: 'json',
39
+ method: "GET",
40
+ format: "json",
42
41
  secure: true,
43
42
  });
44
43
  /**
@@ -48,7 +47,7 @@ export const listProjectBranchBuckets = (apiClient, { projectId, branchId }) =>
48
47
  */
49
48
  export const deleteProjectBranchBucket = (apiClient, { projectId, branchId, bucketName, }) => apiClient.request({
50
49
  path: bucketPath(projectId, branchId, bucketName),
51
- method: 'DELETE',
50
+ method: "DELETE",
52
51
  secure: true,
53
52
  });
54
53
  /**
@@ -58,9 +57,9 @@ export const deleteProjectBranchBucket = (apiClient, { projectId, branchId, buck
58
57
  */
59
58
  export const listProjectBranchBucketObjects = (apiClient, { projectId, branchId, bucketName, ...query }) => apiClient.request({
60
59
  path: `${bucketPath(projectId, branchId, bucketName)}/objects`,
61
- method: 'GET',
60
+ method: "GET",
62
61
  query,
63
- format: 'json',
62
+ format: "json",
64
63
  secure: true,
65
64
  });
66
65
  /**
@@ -80,8 +79,8 @@ export const listProjectBranchBucketObjects = (apiClient, { projectId, branchId,
80
79
  */
81
80
  export const getProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
82
81
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/download`,
83
- method: 'GET',
84
- format: 'stream',
82
+ method: "GET",
83
+ format: "stream",
85
84
  secure: true,
86
85
  });
87
86
  /**
@@ -94,7 +93,7 @@ export const getProjectBranchBucketObject = (apiClient, { projectId, branchId, b
94
93
  */
95
94
  export const deleteProjectBranchBucketObject = (apiClient, { projectId, branchId, bucketName, objectKey, }) => apiClient.request({
96
95
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}`,
97
- method: 'DELETE',
96
+ method: "DELETE",
98
97
  secure: true,
99
98
  });
100
99
  /**
@@ -107,9 +106,9 @@ export const deleteProjectBranchBucketObject = (apiClient, { projectId, branchId
107
106
  */
108
107
  export const deleteProjectBranchBucketObjectsByPrefix = (apiClient, { projectId, branchId, bucketName, prefix, }) => apiClient.request({
109
108
  path: `${bucketPath(projectId, branchId, bucketName)}/objects-by-prefix`,
110
- method: 'DELETE',
109
+ method: "DELETE",
111
110
  query: { prefix },
112
- format: 'json',
111
+ format: "json",
113
112
  secure: true,
114
113
  });
115
114
  /**
@@ -131,7 +130,7 @@ export const deleteProjectBranchBucketObjectsByPrefix = (apiClient, { projectId,
131
130
  * @request POST /projects/{project_id}/branches/{branch_id}/buckets/{bucket_name}/objects/{object_key}/presign
132
131
  */
133
132
  export const presignUpload = (apiClient, { projectId, branchId, bucketName, objectKey, contentType, expiresInSeconds, }) => {
134
- const body = { operation: 'upload' };
133
+ const body = { operation: "upload" };
135
134
  if (contentType !== undefined) {
136
135
  body.content_type = contentType;
137
136
  }
@@ -140,9 +139,9 @@ export const presignUpload = (apiClient, { projectId, branchId, bucketName, obje
140
139
  }
141
140
  return apiClient.request({
142
141
  path: `${bucketPath(projectId, branchId, bucketName)}/objects/${encodeURIComponent(objectKey)}/presign`,
143
- method: 'POST',
142
+ method: "POST",
144
143
  body,
145
- format: 'json',
144
+ format: "json",
146
145
  secure: true,
147
146
  });
148
147
  };