neon 8.4.3 → 8.4.5
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 +4 -4
- package/dist/analytics.js +2 -2
- package/dist/api.js +26 -1
- package/dist/commands/index.js +0 -2
- package/dist/commands/manifest.js +1 -7
- package/dist/commands/realtime.js +2 -1
- package/dist/commands/subscribe.js +22 -44
- package/dist/index.js +3 -2
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -308,10 +308,10 @@ regression + TAP tests.
|
|
|
308
308
|
|
|
309
309
|
## Subscribe to a Realtime query
|
|
310
310
|
|
|
311
|
-
`neon subscribe` streams a live SQL query until interrupted:
|
|
311
|
+
`neon realtime subscribe` streams a live SQL query until interrupted:
|
|
312
312
|
|
|
313
313
|
```bash
|
|
314
|
-
neon subscribe "SELECT id, title, completed FROM realtime_todos ORDER BY id"
|
|
314
|
+
neon realtime subscribe "SELECT id, title, completed FROM realtime_todos ORDER BY id"
|
|
315
315
|
```
|
|
316
316
|
|
|
317
317
|
The default terminal output is append-only. It identifies the selected project,
|
|
@@ -325,10 +325,10 @@ For scripts and agents, JSON output is a JSON Lines stream with one complete eve
|
|
|
325
325
|
per line. YAML output uses one document per event:
|
|
326
326
|
|
|
327
327
|
```bash
|
|
328
|
-
neon subscribe --changes --output json "SELECT * FROM realtime_todos" |
|
|
328
|
+
neon realtime subscribe --changes --output json "SELECT * FROM realtime_todos" |
|
|
329
329
|
jq --unbuffered 'select(.type == "batch")'
|
|
330
330
|
|
|
331
|
-
neon subscribe --output yaml "SELECT * FROM realtime_todos"
|
|
331
|
+
neon realtime subscribe --output yaml "SELECT * FROM realtime_todos"
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
Machine-readable output retains the event stream: without `--changes`, each event is
|
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 };
|
package/dist/commands/index.js
CHANGED
|
@@ -39,7 +39,6 @@ import { t as roles_exports } from "./roles.js";
|
|
|
39
39
|
import { t as set_context_exports } from "./set_context.js";
|
|
40
40
|
import { t as snapshots_exports } from "./snapshots.js";
|
|
41
41
|
import { t as status_exports } from "./status.js";
|
|
42
|
-
import { t as subscribe_exports } from "./subscribe.js";
|
|
43
42
|
import { t as triggers_exports } from "./triggers.js";
|
|
44
43
|
import { t as user_exports } from "./user.js";
|
|
45
44
|
import { t as vpc_endpoints_exports } from "./vpc_endpoints.js";
|
|
@@ -78,7 +77,6 @@ var commands_default = [
|
|
|
78
77
|
feedback_exports,
|
|
79
78
|
data_api_exports,
|
|
80
79
|
realtime_exports,
|
|
81
|
-
subscribe_exports,
|
|
82
80
|
functions_exports,
|
|
83
81
|
triggers_exports,
|
|
84
82
|
credentials_exports,
|
|
@@ -3,7 +3,7 @@ import { REALTIME_COMING_SOON } from "../utils/staging.js";
|
|
|
3
3
|
import { Parser } from "yargs/helpers";
|
|
4
4
|
//#region src/commands/manifest.ts
|
|
5
5
|
/**
|
|
6
|
-
* Every top-level command,
|
|
6
|
+
* Every top-level command, with the metadata yargs needs to list and route it.
|
|
7
7
|
* A command's module (and everything it imports) loads only when yargs selects that command,
|
|
8
8
|
* so `neon --help` and `neon projects list` don't pay for `init`, `dev`, or `psql`.
|
|
9
9
|
*
|
|
@@ -193,12 +193,6 @@ const commandManifest = [
|
|
|
193
193
|
load: () => import("./realtime.js"),
|
|
194
194
|
stagingOnly: REALTIME_COMING_SOON
|
|
195
195
|
},
|
|
196
|
-
{
|
|
197
|
-
command: "subscribe <query>",
|
|
198
|
-
describe: "Subscribe to a live SQL query with Neon Realtime",
|
|
199
|
-
load: () => import("./subscribe.js"),
|
|
200
|
-
stagingOnly: REALTIME_COMING_SOON
|
|
201
|
-
},
|
|
202
196
|
{
|
|
203
197
|
command: "functions",
|
|
204
198
|
aliases: ["function"],
|
|
@@ -2,6 +2,7 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { retryOnLock } from "../api.js";
|
|
4
4
|
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
5
|
+
import { builder as builder$1, handler as handler$1 } from "./subscribe.js";
|
|
5
6
|
import { defineConfig } from "@neon/config";
|
|
6
7
|
//#region src/commands/realtime.ts
|
|
7
8
|
var realtime_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -44,7 +45,7 @@ const builder = (argv) => argv.usage("$0 realtime <sub-command> [options]").opti
|
|
|
44
45
|
}), (args) => enable({
|
|
45
46
|
...args,
|
|
46
47
|
...args.allowedOrigin !== void 0 ? { allowedOrigins: args.allowedOrigin } : {}
|
|
47
|
-
})).command("disable", "Disable Realtime on a branch", (yargs) => yargs.strict(), (args) => disable(args));
|
|
48
|
+
})).command("disable", "Disable Realtime on a branch", (yargs) => yargs.strict(), (args) => disable(args)).command("subscribe <query>", "Subscribe to a live SQL query with Neon Realtime", builder$1, handler$1);
|
|
48
49
|
const handler = (_args) => {};
|
|
49
50
|
//#endregion
|
|
50
51
|
export { builder, command, describe, disable, enable, handler, realtime_exports as t };
|
|
@@ -1,37 +1,18 @@
|
|
|
1
|
-
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
1
|
import { writer } from "../writer.js";
|
|
3
|
-
import { branchIdFromProps
|
|
2
|
+
import { branchIdFromProps } from "../utils/enrichers.js";
|
|
4
3
|
import { o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
|
|
5
4
|
import YAML from "yaml";
|
|
6
5
|
//#region src/commands/subscribe.ts
|
|
7
|
-
|
|
8
|
-
builder: () => builder,
|
|
9
|
-
command: () => command,
|
|
10
|
-
describe: () => describe,
|
|
11
|
-
handler: () => handler,
|
|
12
|
-
subscribe: () => subscribe
|
|
13
|
-
});
|
|
14
|
-
const command = "subscribe <query>";
|
|
15
|
-
const describe = "Subscribe to a live SQL query with Neon Realtime";
|
|
16
|
-
const builder = (argv) => argv.usage("$0 subscribe \"<query>\" [options]").positional("query", {
|
|
6
|
+
const builder = (argv) => argv.usage("$0 realtime subscribe \"<query>\" [options]").positional("query", {
|
|
17
7
|
describe: "SELECT query to subscribe to",
|
|
18
8
|
type: "string"
|
|
19
|
-
}).options({
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type: "string"
|
|
27
|
-
},
|
|
28
|
-
changes: {
|
|
29
|
-
describe: "Show raw resets and transaction batches",
|
|
30
|
-
type: "boolean",
|
|
31
|
-
default: false
|
|
32
|
-
}
|
|
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
|
-
const handler = async (props) => {
|
|
9
|
+
}).options({ changes: {
|
|
10
|
+
describe: "Show raw resets and transaction batches",
|
|
11
|
+
type: "boolean",
|
|
12
|
+
default: false
|
|
13
|
+
} }).example("$0 realtime subscribe \"SELECT * FROM items\"", "Print the initial result followed by a readable transaction log").example("$0 realtime 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.").strict();
|
|
14
|
+
const handler = async (args) => {
|
|
15
|
+
const props = args;
|
|
35
16
|
const controller = new AbortController();
|
|
36
17
|
const stop = (exitCode) => {
|
|
37
18
|
controller.abort();
|
|
@@ -302,27 +283,24 @@ const rowFields = (rows) => {
|
|
|
302
283
|
for (const row of rows) for (const key of Object.keys(row)) seen.add(key);
|
|
303
284
|
return [...seen];
|
|
304
285
|
};
|
|
305
|
-
const consumeSnapshots = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.
|
|
306
|
-
const rows = subscription.getSnapshot().data;
|
|
307
|
-
if (rows !== void 0) render({
|
|
308
|
-
type: "snapshot",
|
|
309
|
-
rows
|
|
310
|
-
});
|
|
311
|
-
})), subscription.onBatch(() => attempt(fail, () => {
|
|
286
|
+
const consumeSnapshots = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onUpdate(() => attempt(fail, () => {
|
|
312
287
|
const rows = subscription.getSnapshot().data;
|
|
313
288
|
if (rows !== void 0) render({
|
|
314
289
|
type: "snapshot",
|
|
315
290
|
rows
|
|
316
291
|
});
|
|
317
292
|
}))], render, signal);
|
|
318
|
-
const consumeChanges = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.
|
|
319
|
-
type
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
293
|
+
const consumeChanges = async (subscription, render, signal) => consumeSubscription(subscription, (fail) => [subscription.onUpdate((update) => {
|
|
294
|
+
if (update.type === "rows") attempt(fail, () => render({
|
|
295
|
+
type: "reset",
|
|
296
|
+
rows: update.rows
|
|
297
|
+
}));
|
|
298
|
+
else attempt(fail, () => render({
|
|
299
|
+
type: "batch",
|
|
300
|
+
txids: update.batch.txids,
|
|
301
|
+
changes: update.changes
|
|
302
|
+
}));
|
|
303
|
+
})], render, signal);
|
|
326
304
|
const consumeSubscription = (subscription, listenForData, render, signal) => new Promise((resolve, reject) => {
|
|
327
305
|
let settled = false;
|
|
328
306
|
let detachData = [];
|
|
@@ -360,4 +338,4 @@ const attempt = (fail, action) => {
|
|
|
360
338
|
}
|
|
361
339
|
};
|
|
362
340
|
//#endregion
|
|
363
|
-
export { builder,
|
|
341
|
+
export { builder, handler, subscribe };
|
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";
|
|
@@ -30,7 +30,6 @@ const NO_SUBCOMMANDS_VERBS = [
|
|
|
30
30
|
"cs",
|
|
31
31
|
"connection-string",
|
|
32
32
|
"psql",
|
|
33
|
-
"subscribe",
|
|
34
33
|
"set-context",
|
|
35
34
|
"checkout",
|
|
36
35
|
"link",
|
|
@@ -52,6 +51,7 @@ const NO_SUBCOMMANDS_VERBS = [
|
|
|
52
51
|
let builder = yargs(hideBin(process.argv));
|
|
53
52
|
builder = builder.scriptName(pkg_default.name).locale("en").usage("$0 <command> [options]").parserConfiguration({
|
|
54
53
|
"populate--": true,
|
|
54
|
+
"sort-commands": true,
|
|
55
55
|
"parse-positional-numbers": false
|
|
56
56
|
}).help().option("output", {
|
|
57
57
|
alias: "o",
|
|
@@ -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.
|
|
3
|
+
"version": "8.4.5",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
"yaml": "^2.9.0",
|
|
67
67
|
"yargs": "17.7.2",
|
|
68
68
|
"yoctocolors": "^2.1.2",
|
|
69
|
-
"@neon/config-runtime": "1.9.1",
|
|
70
69
|
"@neon/config": "1.9.1",
|
|
71
|
-
"@neon/
|
|
72
|
-
"@neon/realtime": "0.0
|
|
70
|
+
"@neon/config-runtime": "1.9.1",
|
|
71
|
+
"@neon/realtime": "0.1.0",
|
|
72
|
+
"@neon/sdk": "7.2.0"
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
75
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@apidevtools/swagger-parser": "12.1.0",
|
|
80
|
-
"hono": "^4.12.25",
|
|
81
80
|
"@rollup/plugin-commonjs": "25.0.8",
|
|
82
81
|
"@rollup/plugin-json": "6.1.0",
|
|
83
82
|
"@rollup/plugin-node-resolve": "15.2.3",
|
|
@@ -94,6 +93,7 @@
|
|
|
94
93
|
"@yao-pkg/pkg": "6.10.0",
|
|
95
94
|
"emocks": "3.0.4",
|
|
96
95
|
"express": "4.19.2",
|
|
96
|
+
"hono": "^4.12.25",
|
|
97
97
|
"node-pty": "1.1.0",
|
|
98
98
|
"oauth2-mock-server": "8.1.0",
|
|
99
99
|
"openapi-types": "12.1.3",
|
|
@@ -103,8 +103,9 @@
|
|
|
103
103
|
"tsx": "4.22.3",
|
|
104
104
|
"typescript": "^5.9.0",
|
|
105
105
|
"vitest": "^3.0.9",
|
|
106
|
-
"@neon-internals/cli-core": "0.0.0",
|
|
107
106
|
"@neon-internals/env-core": "0.0.32",
|
|
107
|
+
"@neon-internals/cli-core": "0.0.0",
|
|
108
|
+
"@neon/env": "1.6.2",
|
|
108
109
|
"@neon/e2e-harness": "0.0.0",
|
|
109
110
|
"@neon/functions": "0.11.0"
|
|
110
111
|
},
|
|
@@ -137,7 +138,7 @@
|
|
|
137
138
|
"lint:fix": "pnpm typecheck && biome check src --write",
|
|
138
139
|
"test": "pnpm --filter neonctl... build && vitest run",
|
|
139
140
|
"test:ci": "pnpm build && vitest run",
|
|
140
|
-
"test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
|
|
141
|
+
"test:e2e": "pnpm --filter \"@neon/env...\" build && pnpm build && vitest run --config vitest.e2e.config.ts",
|
|
141
142
|
"test:perf": "pnpm --filter neon... build && vitest run src/perf_budget.test.ts",
|
|
142
143
|
"perf": "tsx scripts/perf/perf.ts",
|
|
143
144
|
"perf:ci-report": "tsx scripts/perf/ci-report.ts",
|