neon 8.4.2 → 8.4.4

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.
package/README.md CHANGED
@@ -306,6 +306,35 @@ regression + TAP tests.
306
306
 
307
307
  - **TLS cipher is runtime-dependent.** The negotiated TLS 1.3 ciphersuite is chosen by the host runtime's TLS library from an offer byte-identical to libpq's. Under Node (OpenSSL) that is `TLS_AES_256_GCM_SHA384`, matching vanilla psql; under Bun (BoringSSL) it is `TLS_AES_128_GCM_SHA256`. Both are TLS 1.3 AEAD suites with no practical security difference, and neither runtime exposes a client-side knob to steer the selection.
308
308
 
309
+ ## Subscribe to a Realtime query
310
+
311
+ `neon subscribe` streams a live SQL query until interrupted:
312
+
313
+ ```bash
314
+ neon subscribe "SELECT id, title, completed FROM realtime_todos ORDER BY id"
315
+ ```
316
+
317
+ The default terminal output is append-only. It identifies the selected project,
318
+ branch, and database; prints the initial result once; reports connection-state
319
+ changes; and then prints each published batch with its transaction IDs, a compact
320
+ `+insert ~update -delete` summary, and the affected rows. It does not take over the
321
+ terminal or require keyboard navigation. Pass `--changes` to display the raw reset
322
+ and batch events instead of the materialized view.
323
+
324
+ For scripts and agents, JSON output is a JSON Lines stream with one complete event
325
+ per line. YAML output uses one document per event:
326
+
327
+ ```bash
328
+ neon subscribe --changes --output json "SELECT * FROM realtime_todos" |
329
+ jq --unbuffered 'select(.type == "batch")'
330
+
331
+ neon subscribe --output yaml "SELECT * FROM realtime_todos"
332
+ ```
333
+
334
+ Machine-readable output retains the event stream: without `--changes`, each event is
335
+ a complete `snapshot`; with `--changes`, events are raw `reset` and `batch` objects.
336
+ Lifecycle labels and the target header are terminal-only.
337
+
309
338
  ## Configure autocompletion
310
339
 
311
340
  The Neon CLI supports autocompletion, which you can configure in a few easy steps. See [Neon CLI commands — completion](https://neon.com/docs/cli/completion) for instructions.
package/dist/analytics.js CHANGED
@@ -2,7 +2,7 @@ import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-ByNuojoE.js";
2
2
  import { getCliAgent, getGithubEnvVars, isCi } from "./env.js";
3
3
  import { log } from "./log.js";
4
4
  import pkg_default from "./pkg.js";
5
- import { getApiClient, isNeonApiError } from "./api.js";
5
+ import { UnreadableResponseError, getApiClient, isNeonApiError } from "./api.js";
6
6
  import { getAuthContext } from "./auth_context.js";
7
7
  import { n as credentialsPath } from "./_chunks/paths-DMq0Lt7a.js";
8
8
  import "./config.js";
@@ -271,7 +271,7 @@ const sendError = (err, errCode) => {
271
271
  });
272
272
  });
273
273
  log.debug("Sent CLI error event: %s", errCode);
274
- if (errCode === "UNKNOWN_ERROR" && isUnexpectedError(err)) {
274
+ if (errCode === "UNKNOWN_ERROR" && isUnexpectedError(err) || err instanceof UnreadableResponseError) {
275
275
  const report = import("./error_reporting.js").then(({ reportUnexpectedError }) => reportUnexpectedError(err, redactCredentialTokenIds)).catch((reportError) => {
276
276
  log.debug("Could not report the error to Sentry: %s", reportError);
277
277
  });
package/dist/api.js CHANGED
@@ -42,6 +42,16 @@ var NeonApiError = class extends Error {
42
42
  this.code = init.code;
43
43
  }
44
44
  };
45
+ /**
46
+ * A 2xx response whose body could not be read or parsed. The request succeeded, so this is
47
+ * reported to Sentry as well as counted as an API error.
48
+ */
49
+ var UnreadableResponseError = class extends NeonApiError {
50
+ constructor(message, init) {
51
+ super(message, init);
52
+ this.name = "UnreadableResponseError";
53
+ }
54
+ };
45
55
  /** Narrow an unknown error to a {@link NeonApiError}. */
46
56
  function isNeonApiError(err) {
47
57
  return err instanceof NeonApiError;
@@ -170,6 +180,20 @@ function httpError(response, body) {
170
180
  });
171
181
  }
172
182
  /**
183
+ * The cause's message is left out: `JSON.parse` quotes the input, and the body can carry a
184
+ * password or token that would reach stderr and analytics.
185
+ */
186
+ function unreadableBodyError(response, cause) {
187
+ const base = httpError(response, void 0);
188
+ const reason = cause instanceof SyntaxError ? "the body is not valid JSON" : "the body could not be read";
189
+ return new UnreadableResponseError(`Could not read the Neon API response (${response.status} ${response.statusText}) to ${base.requestPath ?? "the request"}: ${reason}`, {
190
+ status: base.status,
191
+ statusText: base.statusText,
192
+ headers: base.headers,
193
+ requestPath: base.requestPath
194
+ });
195
+ }
196
+ /**
173
197
  * Translate a thrown `fetch` failure into a {@link NeonApiError}. A request
174
198
  * timeout uses `ECONNABORTED` (matching the old axios behaviour, and excluded
175
199
  * from `isNetworkError`'s connectivity codes so it's still reported as a
@@ -283,6 +307,7 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
283
307
  const response = result.response;
284
308
  if (!response) throw networkError(result.error ?? /* @__PURE__ */ new Error("No response from Neon API"));
285
309
  if (!response.ok) throw httpError(response, rawErrorBody(result.error) ?? result.data);
310
+ if (result.error !== void 0 && result.error !== null) throw unreadableBodyError(response, rawErrorBody(result.error));
286
311
  return {
287
312
  data: result.data,
288
313
  status: response.status,
@@ -978,4 +1003,4 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
978
1003
  };
979
1004
  };
980
1005
  //#endregion
981
- export { ContentType, NeonApiError, codeFromBody, getApiClient, isNeonApiError, messageFromBody, retryOnLock };
1006
+ export { ContentType, NeonApiError, UnreadableResponseError, codeFromBody, getApiClient, isNeonApiError, messageFromBody, retryOnLock };
@@ -4,11 +4,11 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
6
  import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
7
- import { branchIdResolve } from "../utils/enrichers.js";
8
- import { bundleEntry } from "../utils/esbuild.js";
9
7
  import { a as resolveDevEnv } from "../_chunks/env-4WGRW2nw.js";
10
8
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
11
9
  import { resolveWatchInputs } from "../dev/inputs.js";
10
+ import { branchIdResolve } from "../utils/enrichers.js";
11
+ import { bundleEntry } from "../utils/esbuild.js";
12
12
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
13
13
  import { basename, dirname, join, relative, resolve } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
@@ -8,9 +8,9 @@ import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
8
8
  import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
9
9
  import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
10
10
  import { listedServices } from "../utils/staging.js";
11
+ import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
11
12
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
12
13
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
13
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
14
14
  import { formatPulledEnv, pulledBranch } from "./env_output.js";
15
15
  import { existsSync } from "node:fs";
16
16
  import chalk from "chalk";
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { writer } from "../writer.js";
3
- import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
4
3
  import { o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
4
+ import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
5
5
  import YAML from "yaml";
6
6
  //#region src/commands/subscribe.ts
7
7
  var subscribe_exports = /* @__PURE__ */ __exportAll({
@@ -26,11 +26,11 @@ const builder = (argv) => argv.usage("$0 subscribe \"<query>\" [options]").posit
26
26
  type: "string"
27
27
  },
28
28
  changes: {
29
- describe: "Show raw resets and transaction batches instead of full query results",
29
+ describe: "Show raw resets and transaction batches",
30
30
  type: "boolean",
31
31
  default: false
32
32
  }
33
- }).example("$0 subscribe \"SELECT * FROM items\"", "Print the full result whenever it changes").example("$0 subscribe --changes \"SELECT * FROM items\"", "Print raw reset and delta events").epilogue("The subscription runs until interrupted. JSON output is newline-delimited; YAML output uses one document per event.").middleware(fillSingleProject).strict();
33
+ }).example("$0 subscribe \"SELECT * FROM items\"", "Print the initial result followed by a readable transaction log").example("$0 subscribe --changes \"SELECT * FROM items\"", "Print raw reset and delta events").epilogue("The subscription runs until interrupted. Terminal output is an append-only snapshot and transaction log; JSON output is newline-delimited; YAML output uses one document per event.").middleware(fillSingleProject).strict();
34
34
  const handler = async (props) => {
35
35
  const controller = new AbortController();
36
36
  const stop = (exitCode) => {
@@ -56,6 +56,7 @@ const subscribe = async (props, dependencies = {}) => {
56
56
  const resolveBranchId = dependencies.resolveBranchId ?? branchIdFromProps;
57
57
  const resolveEnv = dependencies.resolveEnv ?? resolveNeonEnvVars;
58
58
  const connect = dependencies.connect ?? connectRealtime;
59
+ const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
59
60
  const branchId = await resolveBranchId(props);
60
61
  const { vars } = await resolveEnv({
61
62
  cwd: props.cwd ?? process.cwd(),
@@ -67,12 +68,20 @@ const subscribe = async (props, dependencies = {}) => {
67
68
  services: ["postgres", "realtime"],
68
69
  ...props.runtimeApi ? { api: props.runtimeApi } : {}
69
70
  });
71
+ const url = requiredEnv(vars, "NEON_REALTIME_URL");
72
+ const secret = requiredEnv(vars, "NEON_REALTIME_SECRET");
73
+ const database = await databaseName(requiredEnv(vars, "DATABASE_URL"));
70
74
  const realtime = await connect({
71
- url: requiredEnv(vars, "NEON_REALTIME_URL"),
72
- secret: requiredEnv(vars, "NEON_REALTIME_SECRET"),
73
- database: await databaseName(requiredEnv(vars, "DATABASE_URL"))
75
+ url,
76
+ secret,
77
+ database
74
78
  });
75
- const render = createRenderer(props);
79
+ const render = createRenderer(props, {
80
+ project: props.projectId,
81
+ branch: branchDisplayName(props, branchId),
82
+ database,
83
+ query: props.query
84
+ }, now);
76
85
  let subscription;
77
86
  try {
78
87
  if (props.changes) {
@@ -81,7 +90,8 @@ const subscribe = async (props, dependencies = {}) => {
81
90
  } else {
82
91
  const materialized = await realtime.subscribeMaterialized(props.query);
83
92
  subscription = materialized;
84
- await consumeSnapshots(materialized, render, props.signal);
93
+ if (props.output === "table") await consumeChanges(materialized, render, props.signal);
94
+ else await consumeSnapshots(materialized, render, props.signal);
85
95
  }
86
96
  } finally {
87
97
  subscription?.unsubscribe();
@@ -123,10 +133,23 @@ const databaseName = async (connectionString) => {
123
133
  if (database) return database;
124
134
  throw new Error("DATABASE_URL must include a database name.");
125
135
  };
126
- const createRenderer = (props) => {
136
+ const createRenderer = (props, context, now) => {
127
137
  const out = props.out ?? process.stdout;
128
- let renderedTableFrame = false;
138
+ let hasBaseline = false;
139
+ let lastStatus;
140
+ const rowsById = /* @__PURE__ */ new Map();
141
+ if (props.output === "table") renderHumanHeader(out, context, props.changes);
129
142
  return (event) => {
143
+ if (event.type === "status") {
144
+ if (props.output === "table") {
145
+ const status = humanStatus(event.state);
146
+ if (status !== lastStatus) {
147
+ lastStatus = status;
148
+ out.write(`${timestamp(now)} ${status}\n`);
149
+ }
150
+ }
151
+ return;
152
+ }
130
153
  if (props.output === "json") {
131
154
  out.write(`${JSON.stringify(event)}\n`);
132
155
  return;
@@ -135,22 +158,25 @@ const createRenderer = (props) => {
135
158
  out.write(`---\n${YAML.stringify(event)}`);
136
159
  return;
137
160
  }
138
- if (renderedTableFrame) out.write("\n");
139
- renderedTableFrame = true;
140
- renderTableEvent(out, event);
161
+ out.write("\n");
162
+ if (event.type === "reset" && !props.changes) {
163
+ const label = hasBaseline ? "Reset" : "Initial snapshot";
164
+ hasBaseline = true;
165
+ rowsById.clear();
166
+ for (const { rowId, row } of event.rows) rowsById.set(rowId, row);
167
+ renderMaterializedRows(out, event.rows.map(({ row }) => row), `${timestamp(now)} ${label}`);
168
+ return;
169
+ }
170
+ if (event.type === "batch" && !props.changes) {
171
+ renderHumanBatch(out, event, rowsById, now);
172
+ return;
173
+ }
174
+ renderTableEvent(out, event, now);
141
175
  };
142
176
  };
143
- const renderTableEvent = (out, event) => {
177
+ const renderTableEvent = (out, event, now) => {
144
178
  if (event.type === "snapshot") {
145
- const fields = rowFields(event.rows);
146
- writer({
147
- output: "table",
148
- out
149
- }).end(event.rows, {
150
- fields,
151
- humanTitle: "Query result",
152
- emptyMessage: "Query returned no rows."
153
- });
179
+ renderMaterializedRows(out, event.rows, `${timestamp(now)} Snapshot`);
154
180
  return;
155
181
  }
156
182
  if (event.type === "reset") {
@@ -158,14 +184,7 @@ const renderTableEvent = (out, event) => {
158
184
  row_id: rowId,
159
185
  row
160
186
  }));
161
- writer({
162
- output: "table",
163
- out
164
- }).end(rows, {
165
- fields: ["row_id", "row"],
166
- humanTitle: "Reset",
167
- emptyMessage: "Reset contains no rows."
168
- });
187
+ renderRows(out, rows, ["row_id", "row"], `${timestamp(now)} Reset · ${rowCount(rows.length)}`, "Reset contains no rows.");
169
188
  return;
170
189
  }
171
190
  const rows = event.changes.map((change) => ({
@@ -173,46 +192,135 @@ const renderTableEvent = (out, event) => {
173
192
  row_id: change.rowId,
174
193
  row: change.type === "upsert" ? change.row : void 0
175
194
  }));
195
+ renderRows(out, rows, [
196
+ "type",
197
+ "row_id",
198
+ "row"
199
+ ], `${timestamp(now)} ${transactionLabel(event.txids)} · ${rawChangeSummary(event.changes)}`, "Batch contains no row changes.");
200
+ };
201
+ const renderHumanHeader = (out, context, rawChanges) => {
202
+ const target = {
203
+ project: context.project,
204
+ branch: context.branch,
205
+ database: context.database,
206
+ mode: rawChanges ? "Raw changes" : "Materialized result",
207
+ query: context.query
208
+ };
176
209
  writer({
177
210
  output: "table",
178
211
  out
179
- }).end(rows, {
212
+ }).end(target, {
180
213
  fields: [
181
- "type",
182
- "row_id",
183
- "row"
214
+ "project",
215
+ "branch",
216
+ "database",
217
+ "mode",
218
+ "query"
184
219
  ],
185
- humanTitle: `Batch (txids: ${event.txids.join(", ") || "none"})`,
186
- emptyMessage: "Batch contains no row changes."
220
+ humanTitle: "Live query"
187
221
  });
188
222
  };
223
+ const renderMaterializedRows = (out, rows, title) => {
224
+ const humanTitle = `${title} · ${rowCount(rows.length)}`;
225
+ renderRows(out, rows, rowFields(rows), humanTitle, "Query returned no rows.");
226
+ };
227
+ const renderHumanBatch = (out, event, rowsById, now) => {
228
+ const changes = classifyChanges(event.changes, rowsById);
229
+ const inserted = changes.filter((change) => change.operation === "+ insert");
230
+ const updated = changes.filter((change) => change.operation === "~ update");
231
+ const deleted = changes.filter((change) => change.operation === "- delete");
232
+ const rowFieldsInBatch = rowFields(changes.flatMap((change) => change.row ? [change.row] : []));
233
+ const operationField = uniqueOperationField(rowFieldsInBatch);
234
+ const rows = changes.map((change) => ({
235
+ ...change.row ?? {},
236
+ [operationField]: change.operation
237
+ }));
238
+ renderRows(out, rows, [operationField, ...rowFieldsInBatch], `${timestamp(now)} ${transactionLabel(event.txids)} · +${inserted.length} ~${updated.length} -${deleted.length}`, "Transaction contains no row changes.");
239
+ };
240
+ const renderRows = (out, rows, fields, title, emptyMessage) => {
241
+ writer({
242
+ output: "table",
243
+ out
244
+ }).end(rows, {
245
+ fields,
246
+ humanTitle: title
247
+ });
248
+ if (rows.length === 0) out.write(`${emptyMessage}\n`);
249
+ };
250
+ const classifyChanges = (changes, rowsById) => changes.map((change) => {
251
+ if (change.type === "upsert") {
252
+ const operation = rowsById.has(change.rowId) ? "~ update" : "+ insert";
253
+ rowsById.set(change.rowId, change.row);
254
+ return {
255
+ operation,
256
+ row: change.row
257
+ };
258
+ }
259
+ const row = rowsById.get(change.rowId);
260
+ rowsById.delete(change.rowId);
261
+ return {
262
+ operation: "- delete",
263
+ ...row ? { row } : {}
264
+ };
265
+ });
266
+ const uniqueOperationField = (fields) => {
267
+ for (const candidate of [
268
+ "change",
269
+ "operation",
270
+ "event"
271
+ ]) if (!fields.includes(candidate)) return candidate;
272
+ let suffix = 1;
273
+ while (fields.includes(`change_${suffix}`)) suffix += 1;
274
+ return `change_${suffix}`;
275
+ };
276
+ const transactionLabel = (txids) => {
277
+ if (txids.length === 0) return "Update (no transaction ID)";
278
+ return `${txids.length === 1 ? "Transaction" : "Transactions"} ${txids.join(", ")}`;
279
+ };
280
+ const rawChangeSummary = (changes) => {
281
+ const upserts = changes.filter((change) => change.type === "upsert").length;
282
+ const removals = changes.length - upserts;
283
+ return `${upserts} upsert${upserts === 1 ? "" : "s"}, ${removals} removal${removals === 1 ? "" : "s"}`;
284
+ };
285
+ const rowCount = (count) => `${count} ${count === 1 ? "row" : "rows"}`;
286
+ const timestamp = (now) => `[${now().toISOString()}]`;
287
+ const humanStatus = (state) => {
288
+ switch (state.status) {
289
+ case "connecting": return "Connecting…";
290
+ case "live": return "Live — waiting for changes.";
291
+ case "stale": return "Stale — showing the last known result.";
292
+ case "closed": return "Subscription closed.";
293
+ case "error": return `Subscription failed — ${state.error.message}`;
294
+ }
295
+ };
296
+ const branchDisplayName = (props, branchId) => {
297
+ const branch = "branch" in props ? props.branch : props.id;
298
+ return branch === branchId ? branchId : `${branch} (${branchId})`;
299
+ };
189
300
  const rowFields = (rows) => {
190
301
  const seen = /* @__PURE__ */ new Set();
191
302
  for (const row of rows) for (const key of Object.keys(row)) seen.add(key);
192
303
  return [...seen];
193
304
  };
194
- const consumeSnapshots = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onReset(() => attempt(fail, () => {
195
- const rows = subscription.getSnapshot().data;
196
- if (rows !== void 0) render({
197
- type: "snapshot",
198
- rows
199
- });
200
- })), subscription.onBatch(() => attempt(fail, () => {
305
+ const consumeSnapshots = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onUpdate(() => attempt(fail, () => {
201
306
  const rows = subscription.getSnapshot().data;
202
307
  if (rows !== void 0) render({
203
308
  type: "snapshot",
204
309
  rows
205
310
  });
206
- }))], signal);
207
- const consumeChanges = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onReset((rows) => attempt(fail, () => render({
208
- type: "reset",
209
- rows
210
- }))), subscription.onBatch((changes, batch) => attempt(fail, () => render({
211
- type: "batch",
212
- txids: batch.txids,
213
- changes
214
- })))], signal);
215
- const consumeSubscription = (subscription, listenForData, signal) => new Promise((resolve, reject) => {
311
+ }))], render, signal);
312
+ const consumeChanges = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onUpdate((update) => {
313
+ if (update.type === "rows") attempt(fail, () => render({
314
+ type: "reset",
315
+ rows: update.rows
316
+ }));
317
+ else attempt(fail, () => render({
318
+ type: "batch",
319
+ txids: update.batch.txids,
320
+ changes: update.changes
321
+ }));
322
+ })], render, signal);
323
+ const consumeSubscription = (subscription, listenForData, render, signal) => new Promise((resolve, reject) => {
216
324
  let settled = false;
217
325
  let detachData = [];
218
326
  let detachState = () => void 0;
@@ -227,6 +335,11 @@ const consumeSubscription = (subscription, listenForData, signal) => new Promise
227
335
  };
228
336
  const fail = (error) => finish(reject, error);
229
337
  const onState = (state) => {
338
+ attempt(fail, () => render({
339
+ type: "status",
340
+ state
341
+ }));
342
+ if (settled) return;
230
343
  if (state.status === "error") fail(state.error);
231
344
  else if (state.status === "closed") finish(resolve);
232
345
  };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { log } from "./log.js";
2
2
  import pkg_default from "./pkg.js";
3
- import { codeFromBody, isNeonApiError, messageFromBody } from "./api.js";
3
+ import { UnreadableResponseError, codeFromBody, isNeonApiError, messageFromBody } from "./api.js";
4
4
  import { getAuthContext } from "./auth_context.js";
5
5
  import { r as defaultDir } from "./_chunks/paths-DMq0Lt7a.js";
6
6
  import { ensureConfigDir } from "./config.js";
@@ -188,6 +188,7 @@ async function handleError(msg, err, canRetry) {
188
188
  } else {
189
189
  const serverMessage = messageFromBody(err.data);
190
190
  if (serverMessage) log.error(serverMessage);
191
+ else if (err instanceof UnreadableResponseError) log.error(err.message);
191
192
  else log.error([`HTTP ${err.status ?? ""} ${err.statusText ?? ""}`.trim(), err.requestPath].filter(Boolean).join(" | "));
192
193
  log.debug("status: %d %s | path: %s", err.status, err.statusText, err.requestPath);
193
194
  sendError(err, "API_ERROR");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "8.4.2",
3
+ "version": "8.4.4",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -68,7 +68,7 @@
68
68
  "yoctocolors": "^2.1.2",
69
69
  "@neon/config": "1.9.1",
70
70
  "@neon/config-runtime": "1.9.1",
71
- "@neon/realtime": "0.0.1",
71
+ "@neon/realtime": "0.0.3",
72
72
  "@neon/sdk": "7.2.0"
73
73
  },
74
74
  "optionalDependencies": {