@neon/env 1.6.14 → 2.0.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.
package/README.md CHANGED
@@ -54,8 +54,9 @@ Both return the same namespaced `NeonEnv` shape: `postgres` is always present; `
54
54
  | Function | Description |
55
55
  | --- | --- |
56
56
  | `fetchEnv(config, { projectId, branch, ... })` | Async. Calls the Neon API for the given project + branch and returns live connection strings (and Auth/Data API/Realtime values when enabled). `projectId` and `branch` are required; `branch` accepts a branch **name** (e.g. `main`) or a `br-…` id. (The legacy id-only `branchId` option still works.) Pass `keys` to fetch only some vars — see [Fetching a subset](#fetching-a-subset). Reads nothing from `process.env` or disk. |
57
- | `parseEnv(config)` / `parseEnv(config, slug)` / `parseEnv(config, keys)` | Sync. Reads/validates the Neon env vars already present in `process.env` against the static policy toggles. With a function `slug`, also returns a typed `function` namespace of that function's declared env keys; `functions.<slug>.baseUrl` is the invocation URL. With a `keys` array (e.g. `["DATABASE_URL"]`), only those vars are required and returned, as a narrowed namespaced shape — the keys are typesafe against the policy. Throws `PlatformError(EnvNotInjected)` listing missing vars when the env isn't populated. |
57
+ | `parseEnv(config)` / `parseEnv(config, slug)` / `parseEnv(config, keys)` | Sync. Reads/validates the Neon env vars already present in `process.env` against the static policy toggles. With a function `slug`, also returns a typed `function` namespace of that function's declared env keys. With a `keys` array (e.g. `["DATABASE_URL"]`), only those vars are required and returned, as a narrowed namespaced shape — the keys are typesafe against the policy. Throws `PlatformError(EnvNotInjected)` listing missing vars when the env isn't populated. |
58
58
  | `toEntries(env)` | Project a resolved `NeonEnv` into `{ KEY: value }` pairs for cross-process transport (named after the web `.entries()` convention; returns a `Record`). |
59
+ | `functionBaseUrl(slug, env = process.env)` | Sync. The base URL of another Neon Function on the same branch, without a trailing slash. See [Calling another function](#calling-another-function). |
59
60
 
60
61
  ## CLI
61
62
 
@@ -68,7 +69,7 @@ neon-env run -- npm run dev
68
69
  neon-env run -- pnpm dev
69
70
  ```
70
71
 
71
- `run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH` / `NEON_BRANCH_ID`, or the `branch` field in `.neon[/project.json]` — by name or id), fetches the connection strings from Neon, and spawns the command with `NEON_BRANCH` / `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (plus the Auth, Data API, Realtime, object-storage `AWS_*`, AI Gateway `NEON_AI_GATEWAY_*`, and function `NEON_FUNCTION_*_BASE_URL` vars when the policy enables them — see [Env vars produced](#env-vars-produced)) injected on top of the inherited environment. Stdio is inherited so interactive dev servers keep working, and the parent exits with the child's exit code.
72
+ `run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH` / `NEON_BRANCH_ID`, or the `branch` field in `.neon[/project.json]` — by name or id), fetches the connection strings from Neon, and spawns the command with `NEON_BRANCH` / `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (plus the Auth, Data API, Realtime, object-storage `AWS_*`, AI Gateway `NEON_AI_GATEWAY_*`, and function routing `NEON_BRANCH_ID` / `NEON_FUNCTION_HOST_SUFFIX` vars when the policy enables them — see [Env vars produced](#env-vars-produced)) injected on top of the inherited environment. Stdio is inherited so interactive dev servers keep working, and the parent exits with the child's exit code.
72
73
 
73
74
  ### `export` — print env to stdout
74
75
 
@@ -104,6 +105,7 @@ These are the OS-level vars `fetchEnv` / `parseEnv` read and `toEntries` (so `ne
104
105
  | Key | From |
105
106
  | --- | --- |
106
107
  | `NEON_BRANCH` | the resolved branch **name** — mirrors what the Neon Functions runtime injects on every branch, so local dev matches the deployed runtime |
108
+ | `NEON_BRANCH_ID` | the resolved branch **id** (`br-…`), also injected by the Functions runtime |
107
109
  | `DATABASE_URL` | pooled connection string |
108
110
  | `DATABASE_URL_UNPOOLED` | direct connection string |
109
111
 
@@ -145,25 +147,41 @@ Derive the database passed to `createRealtime` from `DATABASE_URL` so the two ca
145
147
  | `NEON_AI_GATEWAY_TOKEN` | branch credential's API token (bearer) |
146
148
  | `NEON_AI_GATEWAY_BASE_URL` | bare branch gateway host (`https://<branch>-api.ai.<region>.…`, no path) |
147
149
 
148
- **Functions** (when `functions` declares at least one slug). Each declared slug maps to `NEON_FUNCTION_<SLUG>_BASE_URL`, read as `env.functions.<slug>.baseUrl: string`. These are the public function URLs, not the function's declared `env` secrets (those are uploaded at deploy). `fetchEnv` / `neon env pull` derive the URL from the branch connection host; the function does not have to be deployed. `neon dev` injects `http://localhost:<port>` instead.
149
-
150
- The deployed Functions runtime does not inject these vars yet. Without a key filter, `parseEnv` therefore returns `functions` only when every declared slug's URL is set, and leaves it `undefined` otherwise. A value that is set must be a URL. Select the key to require it.
150
+ **Functions** (when `functions` declares at least one slug). The Functions runtime injects the same host suffix into every function on the branch, so one variable covers every slug:
151
151
 
152
152
  | Key | From |
153
153
  | --- | --- |
154
- | `NEON_FUNCTION_<SLUG>_BASE_URL` | the function's public URL (`https://<branchId>-<slug>.compute.…`), or `http://localhost:<port>` under `neon dev`. `<SLUG>` is the slug uppercased. |
154
+ | `NEON_FUNCTION_HOST_SUFFIX` | the part of a function hostname after `<branchId>-<slug>`, with its leading dot (`.compute.c-2.us-east-2.aws.neon.tech`). Derived from the branch's compute host; the function does not have to be deployed. |
155
+
156
+ Both land in `env.branch` (`branch.id`, `branch.functionHostSuffix`). Without a key filter `parseEnv` treats them as optional, like `NEON_BRANCH`; select them to require them.
157
+
158
+ ### Calling another function
159
+
160
+ `functionBaseUrl(slug)` builds `https://<NEON_BRANCH_ID>-<slug><NEON_FUNCTION_HOST_SUFFIX>`, the URL the deployed function answers at. Under `neon dev`, a slug that process is serving resolves to `http://localhost:<port>` instead, through the `NEON_DEV_FUNCTION_PORTS` map `neon dev` sets.
155
161
 
156
162
  ```ts
157
- const env = parseEnv(config);
158
- env.functions?.hello.baseUrl; // string | undefined
163
+ import { functionBaseUrl } from "@neon/env";
164
+
165
+ const res = await fetch(`${functionBaseUrl("hello")}/greet`);
166
+ ```
167
+
168
+ | Where it runs | `NEON_BRANCH_ID` / `NEON_FUNCTION_HOST_SUFFIX` come from | `functionBaseUrl("hello")` |
169
+ | --- | --- | --- |
170
+ | A deployed Neon Function | the Functions runtime | `https://br-…-hello.compute.…` |
171
+ | An app after `neon env pull` or under `neon-env run` | the pulled or injected env | `https://br-…-hello.compute.…` |
172
+ | `neon dev` serving `hello` | `NEON_DEV_FUNCTION_PORTS` | `http://localhost:<port>` |
159
173
 
160
- const { functions: required } = parseEnv(config, ["NEON_FUNCTION_HELLO_BASE_URL"]);
161
- required.hello.baseUrl; // string, throws when unset
174
+ It reads `process.env` by default. Pass any record with the two variables instead, such as a hook's resolved env:
162
175
 
163
- const { functions } = await fetchEnv(config, { projectId, branch: "main" });
164
- functions.hello.baseUrl; // string
176
+ ```ts
177
+ functionBaseUrl("hello", {
178
+ NEON_BRANCH_ID: env.branch?.id,
179
+ NEON_FUNCTION_HOST_SUFFIX: env.branch?.functionHostSuffix,
180
+ });
165
181
  ```
166
182
 
183
+ It throws a `TypeError` for a slug outside `[a-z0-9]{1,20}`, and `PlatformError(EnvNotInjected)` when a variable is missing or the two do not form a hostname.
184
+
167
185
  ### The branch credential
168
186
 
169
187
  Object storage and the AI Gateway are backed by platform default credentials on every branch in a region that has those products (`Default object storage credential`, `Default AI gateway credential`). `fetchEnv` reveals those by exact name. It mints a combined `neon-env ${branch}` credential only when a needed default is missing.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, 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.js";
2
+ import { a as credentialName, d as policyEnvKeys, f as resolveBranchPolicy, i as credentialEnvKeys, l as fetchEnvKeysState, n as NEON_ENV_VAR_KEYS, o as defaultAiGatewayCredential, p as toEntries, r as createApiFromOptions, s as defaultStorageCredential, u as isLiveCredential } from "./env.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { existsSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
@@ -49,7 +49,7 @@ async function fetchEnvReusingSecrets(config, options) {
49
49
  const { branch, desired } = await resolveBranchPolicy(config, options, api, resolvedBranch);
50
50
  const allPolicyKeys = policyEnvKeys(desired);
51
51
  const requested = requestedKeys ? new Set(requestedKeys) : null;
52
- const selectedPolicyKeys = requested === null ? allPolicyKeys : [...allPolicyKeys.filter((key) => requested.has(key)), ...[...requested].filter(isFunctionBaseUrlKey).sort()];
52
+ const selectedPolicyKeys = requested === null ? allPolicyKeys : [...allPolicyKeys.filter((key) => requested.has(key)), ...Object.values(NEON_ENV_VAR_KEYS.branch).filter((key) => requested.has(key) && !allPolicyKeys.includes(key))];
53
53
  const selected = new Set(selectedPolicyKeys);
54
54
  const K = NEON_ENV_VAR_KEYS;
55
55
  const storageCredentialSelected = (desired.preview?.buckets.length ?? 0) > 0 && (selected.has(K.storage.accessKeyId) || selected.has(K.storage.secretAccessKey));
@@ -71,8 +71,7 @@ async function fetchEnvReusingSecrets(config, options) {
71
71
  fresh: [],
72
72
  revoked: [],
73
73
  superseded: []
74
- },
75
- ...fetched.functionUrlsUnavailable ? { functionUrlsUnavailable: true } : {}
74
+ }
76
75
  };
77
76
  }
78
77
  const persisted = readPersistedSecrets(source);
@@ -104,7 +103,6 @@ async function fetchEnvReusingSecrets(config, options) {
104
103
  desired
105
104
  });
106
105
  const vars = preferPersisted(toEntries(fetched.env), source);
107
- const unavailable = fetched.functionUrlsUnavailable ? { functionUrlsUnavailable: true } : {};
108
106
  for (const key of keptSecretKeys) {
109
107
  const value = source[key];
110
108
  if (value !== void 0) vars[key] = value;
@@ -117,8 +115,7 @@ async function fetchEnvReusingSecrets(config, options) {
117
115
  fresh: [],
118
116
  revoked: [],
119
117
  superseded: []
120
- },
121
- ...unavailable
118
+ }
122
119
  };
123
120
  const ours = /* @__PURE__ */ new Set();
124
121
  for (const meta of [storageCredentialManaged && !keepStorage ? named.storage : null, gatewayCredentialManaged && !keepGateway ? named.gateway : null]) if (meta !== null && meta.principalType === "user" && meta.name === credentialName(branch.name)) ours.add(meta.tokenId);
@@ -131,8 +128,7 @@ async function fetchEnvReusingSecrets(config, options) {
131
128
  fresh: secretKeys.filter((key) => !keptSecretKeys.includes(key)),
132
129
  revoked: revokeSuperseded ? [...ours] : [],
133
130
  superseded: revokeSuperseded ? [] : [...ours]
134
- },
135
- ...unavailable
131
+ }
136
132
  };
137
133
  }
138
134
  /** Read the branch credential's secrets out of an env source. */