neon 8.3.3 → 8.3.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.
@@ -2,7 +2,7 @@ import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
3
  import { declaredNeonServices } from "../config_services.js";
4
4
  import { loadProjectConfig } from "../project.js";
5
- import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-HYNFtxu5.js";
5
+ import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-XsUZj_Ay.js";
6
6
  import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
7
7
  import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
8
8
  import { ErrorCode, createNeonApiFromOptions, isPlatformError } from "@neon/config";
@@ -1,3 +1,4 @@
1
+ import { n as defaultConnectionRole, t as NEON_DEFAULT_OWNER_ROLE } from "./roles-C3M4q9DS.js";
1
2
  import { ErrorCode, PlatformError, createNeonApiFromOptions, deriveCredentialScopes, isPlatformError, resolveConfig } from "@neon/config/v1";
2
3
  //#region ../../internals/env-core/dist/env.js
3
4
  /**
@@ -22,28 +23,11 @@ import { ErrorCode, PlatformError, createNeonApiFromOptions, deriveCredentialSco
22
23
  * {@link dataApiEnvSchema}.
23
24
  */
24
25
  /**
25
- * Neon's default branch owner role, created with every project. This is the role a
26
- * `DATABASE_URL` should connect as.
27
- */
28
- const NEON_DEFAULT_OWNER_ROLE = "neondb_owner";
29
- /**
30
26
  * Neon's default database, created with every project. When a branch has several databases
31
27
  * and none was requested, this is preferred for the `DATABASE_URL` so the common case (a
32
28
  * user added a second database next to `neondb`) auto-picks without asking.
33
29
  */
34
30
  const NEON_DEFAULT_DATABASE = "neondb";
35
- /**
36
- * Roles Neon provisions for the Auth / Data API (PostgREST) stack. They exist to back
37
- * RLS-scoped Data API requests authenticated by JWT — never to hold a `DATABASE_URL` —
38
- * so they're skipped when auto-picking the connection role. Enabling Neon Auth or the
39
- * Data API (`neon config apply`) adds these next to the owner role, which is why a plain
40
- * branch routinely reports more than one role.
41
- */
42
- const NEON_MANAGED_AUTH_ROLES = /* @__PURE__ */ new Set([
43
- "authenticator",
44
- "anonymous",
45
- "authenticated"
46
- ]);
47
31
  const NEON_ENV_VAR_KEYS = {
48
32
  /**
49
33
  * Branch identity. `NEON_BRANCH` carries the branch **name** and is injected into the
@@ -540,11 +524,8 @@ function pickRoleName(roles, branch, requested) {
540
524
  return requested;
541
525
  }
542
526
  if (roles.length === 0) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: branch ${branch.name} (${branch.id}) has no roles.`, "Create one via the Neon console or pass `roleName` explicitly."].join(" "), { details: { branchId: branch.id } });
543
- if (roles.length === 1) return roles[0].name;
544
- const owner = roles.find((r) => r.name === NEON_DEFAULT_OWNER_ROLE);
545
- if (owner) return owner.name;
546
- const appRoles = roles.filter((r) => !NEON_MANAGED_AUTH_ROLES.has(r.name));
547
- if (appRoles.length === 1) return appRoles[0].name;
527
+ const picked = defaultConnectionRole(roles.map((r) => r.name));
528
+ if (picked) return picked;
548
529
  throw new PlatformError(ErrorCode.AmbiguousBranchAuth, [`fetchEnv: branch ${branch.name} (${branch.id}) has ${roles.length} roles and none is "${NEON_DEFAULT_OWNER_ROLE}"; cannot auto-pick.`, `Pass \`roleName\` explicitly. Available: ${roles.map((r) => r.name).join(", ")}.`].join(" "), { details: {
549
530
  branchId: branch.id,
550
531
  availableRoles: roles.map((r) => r.name)
@@ -0,0 +1,27 @@
1
+ //#region ../../internals/env-core/dist/roles.js
2
+ /**
3
+ * Neon's default branch owner role, created with every project. This is the role a
4
+ * `DATABASE_URL` should connect as.
5
+ */
6
+ const NEON_DEFAULT_OWNER_ROLE = "neondb_owner";
7
+ /**
8
+ * Roles Neon provisions for the Auth / Data API (PostgREST) stack. They exist to back
9
+ * RLS-scoped Data API requests authenticated by JWT — never to hold a `DATABASE_URL` —
10
+ * so they're skipped when auto-picking the connection role. Enabling Neon Auth or the
11
+ * Data API (`neon config apply`) adds these next to the owner role, which is why a plain
12
+ * branch routinely reports more than one role.
13
+ */
14
+ const NEON_MANAGED_AUTH_ROLES = /* @__PURE__ */ new Set([
15
+ "authenticator",
16
+ "anonymous",
17
+ "authenticated"
18
+ ]);
19
+ /** `undefined` when more than one app role remains (or none exist), so the caller has to ask. */
20
+ function defaultConnectionRole(roleNames) {
21
+ if (roleNames.length === 1) return roleNames[0];
22
+ if (roleNames.includes("neondb_owner")) return NEON_DEFAULT_OWNER_ROLE;
23
+ const appRoles = roleNames.filter((name) => !NEON_MANAGED_AUTH_ROLES.has(name));
24
+ return appRoles.length === 1 ? appRoles[0] : void 0;
25
+ }
26
+ //#endregion
27
+ export { defaultConnectionRole as n, NEON_DEFAULT_OWNER_ROLE as t };
@@ -1,4 +1,5 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
+ import { n as defaultConnectionRole } from "../_chunks/roles-C3M4q9DS.js";
2
3
  import { writer } from "../writer.js";
3
4
  import { EndpointType } from "../utils/api_enums.js";
4
5
  import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
@@ -33,7 +34,7 @@ const builder = (argv) => {
33
34
  },
34
35
  "role-name": {
35
36
  type: "string",
36
- describe: "Role name"
37
+ describe: "Role name. Defaults to neondb_owner, else the only role besides the Data API and Neon Auth roles"
37
38
  },
38
39
  "database-name": {
39
40
  type: "string",
@@ -100,7 +101,8 @@ const handler = async (props) => {
100
101
  if (!endpoint) throw new Error(`No ${props.endpointType ?? ""} endpoint found for the branch: ${branchId}`);
101
102
  const role = !rolesRequest ? props.roleName : await rolesRequest.then(({ data }) => {
102
103
  if (data.roles.length === 0) throw new Error(`No roles found for the branch: ${branchId}`);
103
- if (data.roles.length === 1) return data.roles[0].name;
104
+ const picked = defaultConnectionRole(data.roles.map((r) => r.name));
105
+ if (picked) return picked;
104
106
  throw new Error(`Multiple roles found for the branch, please provide one with the --role-name option: ${data.roles.map((r) => r.name).join(", ")}`);
105
107
  });
106
108
  const { data: { databases: branchDatabases } } = await databasesRequest;
@@ -3,10 +3,10 @@ import { log } from "../log.js";
3
3
  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
- import { l as functionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
6
+ import { l as functionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
7
7
  import { branchIdResolve } from "../utils/enrichers.js";
8
8
  import { bundleEntry } from "../utils/esbuild.js";
9
- import { a as resolveDevEnv } from "../_chunks/env-BWqJqBDJ.js";
9
+ import { a as resolveDevEnv } from "../_chunks/env-L2DPzDAK.js";
10
10
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
11
11
  import { resolveWatchInputs } from "../dev/inputs.js";
12
12
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -5,11 +5,11 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
5
5
  import { getCliName } from "../utils/cli_name.js";
6
6
  import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
7
7
  import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
8
- import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
8
+ import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
9
9
  import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
10
10
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
11
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
12
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BWqJqBDJ.js";
12
+ import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-L2DPzDAK.js";
13
13
  import { formatPulledEnv, pulledBranch } from "./env_output.js";
14
14
  import { existsSync } from "node:fs";
15
15
  import chalk from "chalk";
@@ -1,5 +1,5 @@
1
1
  import { NEON_SERVICES, NEON_SERVICE_LABELS } from "../neon_services.js";
2
- import { u as isFunctionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
2
+ import { u as isFunctionBaseUrlKey } from "../_chunks/env-XsUZj_Ay.js";
3
3
  import { BRANCH_ENV_KEY, ENV_PULL_KEYS, serviceForEnvKey } from "../env_services.js";
4
4
  import { isAbsolute, relative } from "node:path";
5
5
  import { homedir } from "node:os";
@@ -71,7 +71,7 @@ const dbBuilder = (argv) => {
71
71
  type: "string"
72
72
  },
73
73
  "role-name": {
74
- describe: "Role name",
74
+ describe: "Role name. Defaults to neondb_owner, else the only role besides the Data API and Neon Auth roles",
75
75
  type: "string"
76
76
  },
77
77
  "db-url": {
package/dist/dev/env.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-BWqJqBDJ.js";
1
+ import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-L2DPzDAK.js";
2
2
  export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, dropClaimableUnsupported, resolveDevEnv, resolveNeonEnvVars };
@@ -1,5 +1,5 @@
1
1
  import { NEON_SERVICES } from "./neon_services.js";
2
- import { f as parseFunctionBaseUrlKey, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./_chunks/env-HYNFtxu5.js";
2
+ import { f as parseFunctionBaseUrlKey, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./_chunks/env-XsUZj_Ay.js";
3
3
  //#region src/env_services.ts
4
4
  const ENV_PULL_SERVICES = NEON_SERVICES;
5
5
  const ENV_PULL_KEYS = [
@@ -1,6 +1,6 @@
1
1
  import { log } from "../log.js";
2
2
  import { loadProjectConfig } from "../project.js";
3
- import { s as fetchEnvKeys } from "../_chunks/env-HYNFtxu5.js";
3
+ import { s as fetchEnvKeys } from "../_chunks/env-XsUZj_Ay.js";
4
4
  import { runHook } from "@neon/config-runtime";
5
5
  //#region src/utils/hooks.ts
6
6
  /**
@@ -1,3 +1,4 @@
1
+ import { n as defaultConnectionRole } from "../_chunks/roles-C3M4q9DS.js";
1
2
  import { EndpointType } from "./api_enums.js";
2
3
  import { branchIdFromProps } from "./enrichers.js";
3
4
  import { parsePITBranch } from "./point_in_time.js";
@@ -48,20 +49,37 @@ const connectionUriForDatabase = (connectionUri, database) => {
48
49
  url.pathname = `/${encodeURIComponent(database)}`;
49
50
  return url.toString();
50
51
  };
51
- const listBranchDatabases = async (props) => {
52
+ /**
53
+ * Resolves the branch once and starts its endpoint, role, and database reads together.
54
+ * Callers read them in the order a sequential run would, so the first error is the same.
55
+ */
56
+ const startBranchConnectionReads = async (props) => {
52
57
  const projectId = props.projectId;
53
58
  const parsedPIT = props.branch ? parsePITBranch(props.branch) : {
54
59
  tag: "head",
55
60
  branch: ""
56
61
  };
57
- const branchId = await branchIdFromProps({
58
- ...props,
59
- ...props.branch ? { branch: parsedPIT.branch } : {}
62
+ if (props.branch) props.branch = parsedPIT.branch;
63
+ const branchId = await branchIdFromProps(props);
64
+ const endpoints = props.apiClient.listProjectBranchEndpoints(projectId, branchId).then(({ data }) => data.endpoints);
65
+ const role = props.roleName ? Promise.resolve(props.roleName) : props.apiClient.listProjectBranchRoles(projectId, branchId).then(({ data }) => {
66
+ if (data.roles.length === 0) throw new Error(`No roles found for the branch: ${branchId}`);
67
+ const picked = defaultConnectionRole(data.roles.map((r) => r.name));
68
+ if (picked) return picked;
69
+ throw new Error(`Multiple roles found for the branch, please provide one with the --role-name option: ${data.roles.map((r) => r.name).join(", ")}`);
60
70
  });
61
- const { data: { databases } } = await props.apiClient.listProjectBranchDatabases(projectId, branchId);
71
+ const databases = props.apiClient.listProjectBranchDatabases(projectId, branchId).then(({ data }) => data.databases);
72
+ for (const read of [
73
+ endpoints,
74
+ role,
75
+ databases
76
+ ]) read.catch(() => void 0);
62
77
  return {
63
78
  branchId,
64
- names: databases.map((d) => d.name)
79
+ parsedPIT,
80
+ endpoints,
81
+ role,
82
+ databases
65
83
  };
66
84
  };
67
85
  /**
@@ -70,25 +88,16 @@ const listBranchDatabases = async (props) => {
70
88
  * pooling, prisma tuning, and SSL mode). Mirrors how `connection-string`
71
89
  * resolves a branch so `inspect db` connects the same way.
72
90
  */
73
- const resolveConnectionUri = async (props) => {
74
- const projectId = props.projectId;
75
- const parsedPIT = props.branch ? parsePITBranch(props.branch) : {
76
- tag: "head",
77
- branch: ""
78
- };
79
- if (props.branch) props.branch = parsedPIT.branch;
80
- const branchId = await branchIdFromProps(props);
81
- const { data: { endpoints } } = await props.apiClient.listProjectBranchEndpoints(projectId, branchId);
91
+ const resolveConnectionUri = async (props, started) => {
92
+ const reads = started ?? await startBranchConnectionReads(props);
93
+ const { branchId, parsedPIT } = reads;
94
+ const endpoints = await reads.endpoints;
82
95
  const matchEndpointType = props.endpointType ?? EndpointType.ReadWrite;
83
96
  let endpoint = endpoints.find((e) => e.type === matchEndpointType);
84
97
  if (!endpoint && props.endpointType == null) endpoint = endpoints[0];
85
98
  if (!endpoint) throw new Error(`No ${props.endpointType ?? ""} endpoint found for the branch: ${branchId}`);
86
- const role = props.roleName || await props.apiClient.listProjectBranchRoles(projectId, branchId).then(({ data }) => {
87
- if (data.roles.length === 0) throw new Error(`No roles found for the branch: ${branchId}`);
88
- if (data.roles.length === 1) return data.roles[0].name;
89
- throw new Error(`Multiple roles found for the branch, please provide one with the --role-name option: ${data.roles.map((r) => r.name).join(", ")}`);
90
- });
91
- const { data: { databases: branchDatabases } } = await props.apiClient.listProjectBranchDatabases(projectId, branchId);
99
+ const role = await reads.role;
100
+ const branchDatabases = await reads.databases;
92
101
  const database = props.databaseName || (() => {
93
102
  if (branchDatabases.length === 0) throw new Error(`No databases found for the branch: ${branchId}`);
94
103
  if (branchDatabases.length === 1) return branchDatabases[0].name;
@@ -158,8 +167,9 @@ const resolveInspectTargets = async (props, scope) => {
158
167
  branchDatabaseCount: 1
159
168
  };
160
169
  }
161
- const { branchId, names } = await listBranchDatabases(props);
162
- if (names.length === 0) throw new Error(`No databases found for the branch: ${branchId}`);
170
+ const reads = await startBranchConnectionReads(props);
171
+ const names = (await reads.databases).map((d) => d.name);
172
+ if (names.length === 0) throw new Error(`No databases found for the branch: ${reads.branchId}`);
163
173
  const selection = selectInspectTargets({
164
174
  branchDatabases: names,
165
175
  scope
@@ -167,7 +177,7 @@ const resolveInspectTargets = async (props, scope) => {
167
177
  const first = await resolveConnectionUri({
168
178
  ...props,
169
179
  databaseName: selection.databases[0]
170
- });
180
+ }, reads);
171
181
  return {
172
182
  targets: selection.databases.map((database) => ({
173
183
  database,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "8.3.3",
3
+ "version": "8.3.5",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -65,8 +65,8 @@
65
65
  "yargs": "17.7.2",
66
66
  "yoctocolors": "^2.1.2",
67
67
  "@neon/config": "1.8.6",
68
- "@neon/config-runtime": "1.8.0",
69
- "@neon/sdk": "7.0.1"
68
+ "@neon/sdk": "7.0.1",
69
+ "@neon/config-runtime": "1.8.0"
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "@napi-rs/keyring": "1.3.0",
@@ -101,9 +101,9 @@
101
101
  "typescript": "^5.9.0",
102
102
  "vitest": "^3.0.9",
103
103
  "@neon-internals/cli-core": "0.0.0",
104
- "@neon-internals/env-core": "0.0.29",
105
104
  "@neon/e2e-harness": "0.0.0",
106
- "@neon/functions": "0.11.0"
105
+ "@neon/functions": "0.11.0",
106
+ "@neon-internals/env-core": "0.0.29"
107
107
  },
108
108
  "publishConfig": {
109
109
  "access": "public",