@neondatabase/env 0.3.1 → 0.3.2

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
@@ -52,7 +52,7 @@ neon-env run -- npm run dev
52
52
  neon-env run -- pnpm dev
53
53
  ```
54
54
 
55
- `run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH_ID`, or `.neon[/project.json]`), fetches the connection strings from Neon, and spawns the command with `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (and `NEON_AUTH_BASE_URL` / `NEON_DATA_API_URL` when the policy enables them) 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.
55
+ `run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH_ID`, or `.neon[/project.json]`), fetches the connection strings from Neon, and spawns the command with `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (and `NEON_AUTH_BASE_URL` / `NEON_AUTH_JWKS_URL` / `NEON_DATA_API_URL` when the policy enables them) 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.
56
56
 
57
57
  ### `export` — print env to stdout
58
58
 
@@ -79,6 +79,7 @@ Flags (both commands): `--config <path>`, `--project-id`, `--branch`, `--api-key
79
79
  | `DATABASE_URL` | pooled connection string |
80
80
  | `DATABASE_URL_UNPOOLED` | direct connection string |
81
81
  | `NEON_AUTH_BASE_URL` | Neon Auth integration (when `auth` is enabled) |
82
+ | `NEON_AUTH_JWKS_URL` | Neon Auth JWKS endpoint for verifying issued tokens (when `auth` is enabled) |
82
83
  | `NEON_DATA_API_URL` | Data API integration (when `dataApi` is enabled) |
83
84
 
84
85
  ## Resolution
package/dist/lib/env.d.ts CHANGED
@@ -10,6 +10,7 @@ declare const NEON_ENV_VAR_KEYS: {
10
10
  };
11
11
  readonly auth: {
12
12
  readonly baseUrl: "NEON_AUTH_BASE_URL";
13
+ readonly jwksUrl: "NEON_AUTH_JWKS_URL";
13
14
  };
14
15
  readonly dataApi: {
15
16
  readonly url: "NEON_DATA_API_URL";
@@ -33,13 +34,15 @@ interface NeonPostgresEnv {
33
34
  * Bits of a Neon Auth integration for the resolved branch. Only present on `NeonEnv`
34
35
  * when the branch policy enables `auth`.
35
36
  *
36
- * Neon Auth exposes a single `baseUrl` that doubles as the publishable client identifier
37
- * the rest of the surface (project id, JWKS URL, …) is derived from it at runtime by
38
- * the Neon Auth SDK. `fetchEnv` reads it from the live integration; `parseEnv` reads it
39
- * from `process.env` (`NEON_AUTH_BASE_URL`).
37
+ * Neon Auth exposes the `baseUrl` (which doubles as the publishable client identifier) and
38
+ * the `jwksUrl` used to verify tokens it issues. `fetchEnv` reads both from the live
39
+ * integration; `parseEnv` reads them from `process.env` (`NEON_AUTH_BASE_URL` /
40
+ * `NEON_AUTH_JWKS_URL`).
40
41
  */
41
42
  interface NeonAuthEnv {
42
43
  baseUrl: string;
44
+ /** JWKS URL for verifying tokens issued by Neon Auth (`NEON_AUTH_JWKS_URL`). */
45
+ jwksUrl: string;
43
46
  }
44
47
  /** Bits of a Neon Data API integration. Only present when the branch policy enables it. */
45
48
  interface NeonDataApiEnv {
@@ -117,6 +120,11 @@ interface FetchEnvOptions {
117
120
  * is supplied.
118
121
  */
119
122
  apiKey?: string;
123
+ /**
124
+ * Neon **management** API base URL (not the Auth base URL). Falls back to
125
+ * `NEON_API_HOST`, then production. Ignored when a custom `api` is supplied.
126
+ */
127
+ apiHost?: string;
120
128
  /**
121
129
  * Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely
122
130
  * on the default real adapter built from `apiKey`.
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","names":[],"sources":["../../src/lib/env.ts"],"mappings":";;;;;cA2Ca;;;;EAAA,CAAA;EAcI,SAAA,IAAA,EAAA;IAuBA,SAAA,OAAW,EAAA,oBAAA;EAKX,CAAA;EASZ,SAAA,OAAW,EAAA;IAaX,SAAS,GAAA,EAAA,mBAAA;EAAA,CAAA;;;AAIT,UAtDY,eAAA,CAsDZ;;;;AAMI;EAgBG,WAAO,EAAA,MAAA;EAAA;;;;;qBAEJ,EAAA,MAAA;;;;;;;;;AAKA;AAAE;AAGM,UA/DN,WAAA,CA+DM;SAAW,EAAA,MAAA;;;AAG/B,UA7Dc,cAAA,CA6Dd;KACA,EAAA,MAAA;AAAM;AAGT;;;;;KAxDK,WAAA,GAAc,MAwD4B,CAAA,KAAA,EAAA,KAAA,CAAA;AAAO;AAGpD;;;;;;;;;;KA9CG,SAsDa,CAAA,CAAA,CAAA,GAAA,CAtDG,CAsDH,CAAA,SAAA,CAAA,KAAA,CAAA,GAAA,KAAA,GAAA,CApDd,CAoDc,CAAA,SAAA,CAAA;SAAd,EAAA,KAAA;AAAO,CAAA,CAAA,GAAA,KAAA,GAAA,CAlDN,CAkDM,CAAA,SAAA,CAAA,SAAA,CAAA,GAAA,KAAA,GAAA,CAhDL,CAgDK,CAAA,SAAA,CAAA,IAAA,CAAA,GAAA,IAAA,GAAA,CA9CJ,CA8CI,CAAA,SAAA,CAAA;EAQC,OAAA,EAAA,IAAA;CAAe,CAAA,GAAA,IAAA,GAAA,CApDnB,CAoDmB,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,IAAA,GAAA,KAAA;;;;;;AACV;AAGjB;;;;AAqCwB;AA0BxB;AAA8B,KAvGlB,OAuGkB,CAAA,UAvGA,MAuGA,GAvGS,MAuGT,CAAA,GAAA;UAAiB,EAtGpC,eAsGoC;KArG1C,SAsGI,CAtGM,WAsGN,CAtGkB,CAsGlB,CAAA,MAAA,CAAA,CAAA,CAAA,SAAA,IAAA,GAAA;MACC,EAtGC,WAsGD;IArGP,WAsGgB,CAAA,GAAA,CArGjB,SAqGiB,CArGP,WAqGO,CArGK,CAqGL,CAAA,SAAA,CAAA,CAAA,CAAA,SAAA,IAAA,GAAA;SAAR,EApGI,cAoGJ;IAnGP,WAmGD,CAAA;AAAO;AAuWV,KAvcK,kBAucmB,CAAA,UAvcU,MAucV,CAAA,GAvcoB,WAucpB,CAvcgC,CAuchC,CAAA,SAAA,CAAA,CAAA,SAAA;EAAA,SAAA,EAAA,KAAA,EAAA;IApcrB,IACA,MAmcsC,CAAA,KAAA,EAAA,KAAA,CAAA;;AAA4B,KAhczD,cAgcyD,CAAA,UAhchC,MAgcgC,CAAA,GAhctB,OAgcsB,CAAA,MA/b9D,kBA+b8D,CA/b3C,CA+b2C,CAAA,EAAA,MAAA,CAAA;;AAAD,KA1b/D,iBA0b+D,CAAA,UAzbzD,MAybyD,EAAA,UAAA,MAAA,CAAA,GAvbhE,CAubgE,SAAA,MAvbhD,kBAubgD,CAvb7B,CAub6B,CAAA,GAtbjE,WAsbiE,CAtbrD,kBAsbqD,CAtblC,CAsbkC,CAAA,CAtb/B,CAsb+B,CAAA,CAAA,SAAA;EACpD,GAAA,EAAA,KAAQ,EAAA;CAAA,GAtbpB,OAsboB,CAAA,MAtbN,CAsbM,EAAA,MAAA,CAAA,GAAA,KAAA,GAAA,KAAA;;;;;AAGJ,KAjbR,eAibQ,CAAA,UAjbkB,MAiblB,EAAA,UAAA,MAAA,CAAA,GAAA;UAAY,EAhbrB,MAgbqB,CAhbd,iBAgbc,CAhbI,CAgbJ,EAhbO,CAgbP,CAAA,EAAA,MAAA,CAAA;;AAAqB,UA7apC,eAAA,CA6aoC;;;AAAD;AAyGpD;EAAyB,SAAA,EAAA,MAAA;;UAAM,EAAA,MAAA;;AAAwB;;;;;;;;;QApgBhD;;;;;;;;;;;;;;;;;;;QAmBA,MAAA,CAAO;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BQ,yBAAyB,gBACtC,YACC,kBACP,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuWH,yBAAyB,gBAAgB,IAAI,QAAQ;iBACrD,yBACC,wBACA,eAAe,YACtB,UAAU,IAAI,QAAQ,KAAK,gBAAgB,GAAG;;;;;;;;;;;;iBAyGxC,SAAA,MAAe,QAAQ,UAAU"}
1
+ {"version":3,"file":"env.d.ts","names":[],"sources":["../../src/lib/env.ts"],"mappings":"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"}
package/dist/lib/env.js CHANGED
@@ -33,7 +33,10 @@ const NEON_ENV_VAR_KEYS = {
33
33
  databaseUrl: "DATABASE_URL",
34
34
  databaseUrlUnpooled: "DATABASE_URL_UNPOOLED"
35
35
  },
36
- auth: { baseUrl: "NEON_AUTH_BASE_URL" },
36
+ auth: {
37
+ baseUrl: "NEON_AUTH_BASE_URL",
38
+ jwksUrl: "NEON_AUTH_JWKS_URL"
39
+ },
37
40
  dataApi: { url: "NEON_DATA_API_URL" }
38
41
  };
39
42
  /**
@@ -104,7 +107,11 @@ async function fetchEnv(config, options) {
104
107
  projectId,
105
108
  branchId: branch.id
106
109
  } });
107
- result.auth = { baseUrl: resolveAuthBaseUrl(authSnapshot.baseUrl, options.env ?? process.env) };
110
+ const envSource = options.env ?? process.env;
111
+ result.auth = {
112
+ baseUrl: resolveAuthBaseUrl(authSnapshot.baseUrl, envSource),
113
+ jwksUrl: resolveAuthJwksUrl(authSnapshot.jwksUrl, envSource)
114
+ };
108
115
  }
109
116
  if (wantsDataApi) {
110
117
  if (!dataApiSnapshot) throw new PlatformError(ErrorCode.NotFound, [`fetchEnv: branch policy enables dataApi but no Data API integration is enabled on branch ${branch.name} (${branch.id}) database ${databaseName}.`, "Enable it via `apply(config, { projectId, branchId })` or in the Neon Console — then re-run fetchEnv. Or return dataApi.enabled=false."].join(" "), { details: {
@@ -126,8 +133,20 @@ function resolveAuthBaseUrl(snapshotBaseUrl, source) {
126
133
  if (snapshotBaseUrl && snapshotBaseUrl !== "") return snapshotBaseUrl;
127
134
  return source[NEON_ENV_VAR_KEYS.auth.baseUrl] ?? "";
128
135
  }
136
+ /**
137
+ * Resolve the Neon Auth JWKS URL to surface in `env.auth`. Prefer the value returned by the
138
+ * integration (`getNeonAuth` always includes `jwks_url`); fall back to the caller's env
139
+ * source so the value still round-trips through `env run` if a snapshot ever omits it.
140
+ */
141
+ function resolveAuthJwksUrl(snapshotJwksUrl, source) {
142
+ if (snapshotJwksUrl && snapshotJwksUrl !== "") return snapshotJwksUrl;
143
+ return source[NEON_ENV_VAR_KEYS.auth.jwksUrl] ?? "";
144
+ }
129
145
  function createApiFromOptions(options) {
130
- return createNeonApiFromOptions("fetchEnv", options.apiKey ? { apiKey: options.apiKey } : {});
146
+ return createNeonApiFromOptions("fetchEnv", {
147
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
148
+ ...options.apiHost ? { apiHost: options.apiHost } : {}
149
+ });
131
150
  }
132
151
  function resolveBranch(branchId, branches) {
133
152
  const match = branches.find((b) => b.id === branchId);
@@ -187,7 +206,10 @@ const postgresEnvSchema = z.object({
187
206
  DATABASE_URL: z.string({ message: "DATABASE_URL is missing" }).min(1, "DATABASE_URL must not be empty"),
188
207
  DATABASE_URL_UNPOOLED: z.string({ message: "DATABASE_URL_UNPOOLED is missing" }).min(1, "DATABASE_URL_UNPOOLED must not be empty")
189
208
  });
190
- const authEnvSchema = z.object({ NEON_AUTH_BASE_URL: z.string({ message: "NEON_AUTH_BASE_URL is missing" }).min(1, "NEON_AUTH_BASE_URL must not be empty") });
209
+ const authEnvSchema = z.object({
210
+ NEON_AUTH_BASE_URL: z.string({ message: "NEON_AUTH_BASE_URL is missing" }).min(1, "NEON_AUTH_BASE_URL must not be empty"),
211
+ NEON_AUTH_JWKS_URL: z.string({ message: "NEON_AUTH_JWKS_URL is missing" }).min(1, "NEON_AUTH_JWKS_URL must not be empty")
212
+ });
191
213
  const dataApiEnvSchema = z.object({ NEON_DATA_API_URL: z.string({ message: "NEON_DATA_API_URL is missing" }).min(1, "NEON_DATA_API_URL must not be empty") });
192
214
  /** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */
193
215
  function isServiceEnabledInput(toggle) {
@@ -209,8 +231,14 @@ function parseEnv(config, scope) {
209
231
  };
210
232
  else for (const issue of pg.error.issues) issues.push(issue.message);
211
233
  if (isServiceEnabledInput(config.auth)) {
212
- const auth = authEnvSchema.safeParse({ NEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL });
213
- if (auth.success) result.auth = { baseUrl: auth.data.NEON_AUTH_BASE_URL };
234
+ const auth = authEnvSchema.safeParse({
235
+ NEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,
236
+ NEON_AUTH_JWKS_URL: source.NEON_AUTH_JWKS_URL
237
+ });
238
+ if (auth.success) result.auth = {
239
+ baseUrl: auth.data.NEON_AUTH_BASE_URL,
240
+ jwksUrl: auth.data.NEON_AUTH_JWKS_URL
241
+ };
214
242
  else for (const issue of auth.error.issues) issues.push(issue.message);
215
243
  }
216
244
  if (isServiceEnabledInput(config.dataApi)) {
@@ -257,7 +285,10 @@ function toEntries(env) {
257
285
  [NEON_ENV_VAR_KEYS.postgres.databaseUrlUnpooled]: env.postgres.databaseUrlUnpooled
258
286
  };
259
287
  const withAuth = env;
260
- if (withAuth.auth) out[NEON_ENV_VAR_KEYS.auth.baseUrl] = withAuth.auth.baseUrl;
288
+ if (withAuth.auth) {
289
+ out[NEON_ENV_VAR_KEYS.auth.baseUrl] = withAuth.auth.baseUrl;
290
+ out[NEON_ENV_VAR_KEYS.auth.jwksUrl] = withAuth.auth.jwksUrl;
291
+ }
261
292
  const withDataApi = env;
262
293
  if (withDataApi.dataApi) out[NEON_ENV_VAR_KEYS.dataApi.url] = withDataApi.dataApi.url;
263
294
  return out;
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","names":[],"sources":["../../src/lib/env.ts"],"sourcesContent":["import {\n\ttype Config,\n\tcreateNeonApiFromOptions,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype NeonDatabaseSnapshot,\n\ttype NeonRoleSnapshot,\n\tPlatformError,\n\tresolveConfig,\n\ttype ServiceToggleInput,\n} from \"@neondatabase/config/v1\";\nimport { z } from \"zod\";\n\n/**\n * Mapping between the {@link NeonEnv} property paths and the OS-level env-var keys used\n * for cross-process transport (via `.env` files, `env run -- <cmd>`, or anything else\n * that talks to `process.env`).\n *\n * Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the\n * camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used\n * by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /\n * {@link dataApiEnvSchema}.\n */\n/**\n * Neon's default branch owner role, created with every project. This is the role a\n * `DATABASE_URL` should connect as.\n */\nconst NEON_DEFAULT_OWNER_ROLE = \"neondb_owner\";\n\n/**\n * Roles Neon provisions for the Auth / Data API (PostgREST) stack. They exist to back\n * RLS-scoped Data API requests authenticated by JWT — never to hold a `DATABASE_URL` —\n * so they're skipped when auto-picking the connection role. Enabling Neon Auth or the\n * Data API (`neon config apply`) adds these next to the owner role, which is why a plain\n * branch routinely reports more than one role.\n */\nconst NEON_MANAGED_AUTH_ROLES: ReadonlySet<string> = new Set([\n\t\"authenticator\",\n\t\"anonymous\",\n\t\"authenticated\",\n]);\n\nexport const NEON_ENV_VAR_KEYS = {\n\tpostgres: {\n\t\tdatabaseUrl: \"DATABASE_URL\",\n\t\tdatabaseUrlUnpooled: \"DATABASE_URL_UNPOOLED\",\n\t},\n\tauth: {\n\t\tbaseUrl: \"NEON_AUTH_BASE_URL\",\n\t},\n\tdataApi: {\n\t\turl: \"NEON_DATA_API_URL\",\n\t},\n} as const;\n\n/** Per-namespace inner shapes. Exposed so consumers can name the parts independently. */\nexport interface NeonPostgresEnv {\n\t/**\n\t * Pooled connection string (via Neon's PgBouncer pooler). The right default for\n\t * serverless drivers (`@neondatabase/serverless`, edge runtimes, Postgres.js, …).\n\t */\n\tdatabaseUrl: string;\n\t/**\n\t * Direct (unpooled) connection string. Use this when you need session-level\n\t * features (`LISTEN`/`NOTIFY`, prepared statements across calls, transactions\n\t * spanning round-trips) that PgBouncer's transaction-mode pooling drops.\n\t */\n\tdatabaseUrlUnpooled: string;\n}\n\n/**\n * Bits of a Neon Auth integration for the resolved branch. Only present on `NeonEnv`\n * when the branch policy enables `auth`.\n *\n * Neon Auth exposes a single `baseUrl` that doubles as the publishable client identifier\n * — the rest of the surface (project id, JWKS URL, …) is derived from it at runtime by\n * the Neon Auth SDK. `fetchEnv` reads it from the live integration; `parseEnv` reads it\n * from `process.env` (`NEON_AUTH_BASE_URL`).\n */\nexport interface NeonAuthEnv {\n\tbaseUrl: string;\n}\n\n/** Bits of a Neon Data API integration. Only present when the branch policy enables it. */\nexport interface NeonDataApiEnv {\n\turl: string;\n}\n\n/**\n * Empty record alias used as the \"false\" branch of the conditional namespace adds below.\n * `Record<never, never>` is the no-op for intersection — the cleaner alternative to `{}`,\n * which biome rejects (it means \"any non-null\", not \"empty object\").\n */\ntype NoNamespace = Record<never, never>;\n\n/**\n * Resolve a **static** service toggle (the value of `config.auth` / `config.dataApi`) to a\n * type-level boolean. The whole-thing wrapping (`[T] extends […]`) turns off distribution\n * so a union/`undefined` is checked as one unit:\n *\n * - `false` / `{ enabled: false }` / `undefined` → `false`\n * - `true` / `{ enabled: true }` / any other object (`{}`, `{ enabled?: boolean }`) → `true`\n * (a present toggle defaults to enabled)\n * - the bare `boolean | ServiceToggle | undefined` (the default `Config` param, no literal\n * info) → `false`, so an untyped policy yields just `{ postgres }`.\n */\ntype ServiceOn<T> = [T] extends [false]\n\t? false\n\t: [T] extends [{ enabled: false }]\n\t\t? false\n\t\t: [T] extends [undefined]\n\t\t\t? false\n\t\t\t: [T] extends [true]\n\t\t\t\t? true\n\t\t\t\t: [T] extends [{ enabled: true }]\n\t\t\t\t\t? true\n\t\t\t\t\t: [T] extends [object]\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: false;\n\n/**\n * Static, namespaced shape of `fetchEnv` / `parseEnv`'s return value. Generic over the\n * {@link Config} so the type system knows which optional namespaces are present.\n *\n * Because the secret-bearing toggles now live in the **static** top-level `config.auth` /\n * `config.dataApi` (not inside a per-branch closure), the namespace presence is a direct\n * read of those fields — no union-across-branches, no default-config escape hatch:\n *\n * - `postgres` is always present.\n * - `auth` is added iff `config.auth` is statically enabled.\n * - `dataApi` is added iff `config.dataApi` is statically enabled.\n */\nexport type NeonEnv<C extends Config = Config> = {\n\tpostgres: NeonPostgresEnv;\n} & (ServiceOn<NonNullable<C[\"auth\"]>> extends true\n\t? { auth: NeonAuthEnv }\n\t: NoNamespace) &\n\t(ServiceOn<NonNullable<C[\"dataApi\"]>> extends true\n\t\t? { dataApi: NeonDataApiEnv }\n\t\t: NoNamespace);\n\n/** The static `preview.functions` record of a config, or an empty record when absent. */\ntype PreviewFunctionsOf<C extends Config> = NonNullable<C[\"preview\"]> extends {\n\tfunctions: infer F;\n}\n\t? F\n\t: Record<never, never>;\n\n/** The declared function slugs of a config (record keys), as a string union. */\nexport type FunctionSlugOf<C extends Config> = Extract<\n\tkeyof PreviewFunctionsOf<C>,\n\tstring\n>;\n\n/** The declared env-var keys of one function `S`, as a string union. */\ntype FunctionEnvKeysOf<\n\tC extends Config,\n\tS extends string,\n> = S extends keyof PreviewFunctionsOf<C>\n\t? NonNullable<PreviewFunctionsOf<C>[S]> extends { env: infer E }\n\t\t? Extract<keyof E, string>\n\t\t: never\n\t: never;\n\n/**\n * The extra `function` namespace added to `parseEnv`'s result when called with a function\n * slug scope: the declared env-var keys for that function, each resolved to a `string`.\n */\nexport type NeonFunctionEnv<C extends Config, S extends string> = {\n\tfunction: Record<FunctionEnvKeysOf<C, S>, string>;\n};\n\nexport interface FetchEnvOptions {\n\t/**\n\t * Neon project id. **Required** — the management API addresses branches through their\n\t * project. Resolve it in your CLI (e.g. neonctl) and pass it in.\n\t */\n\tprojectId: string;\n\t/** Neon branch id (`br-…`). **Required.** Resolve names to ids before calling. */\n\tbranchId: string;\n\t/**\n\t * Neon API key. Resolved via the standard chain (option → `NEON_API_KEY` →\n\t * `~/.config/neonctl/credentials.json`) when omitted. Ignored when a custom `api`\n\t * is supplied.\n\t */\n\tapiKey?: string;\n\t/**\n\t * Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely\n\t * on the default real adapter built from `apiKey`.\n\t */\n\tapi?: NeonApi;\n\t/**\n\t * Role name to fetch credentials for. When omitted, the connection role is auto-picked:\n\t * the only role on the branch, else Neon's default owner (`neondb_owner`), else the\n\t * single role left after dropping the managed Auth/Data API roles\n\t * (`authenticator`/`anonymous`/`authenticated`). Throws {@link PlatformError} with\n\t * `PLATFORM_AMBIGUOUS_BRANCH_AUTH` only when more than one app role remains.\n\t */\n\troleName?: string;\n\t/**\n\t * Database name. When omitted, the only database on the branch is auto-picked; throws\n\t * {@link PlatformError} with `PLATFORM_AMBIGUOUS_BRANCH_AUTH` if the branch has more\n\t * than one database.\n\t */\n\tdatabaseName?: string;\n\t/**\n\t * Env source used for one-time Auth keys that cannot be refetched after integration\n\t * creation. Defaults to `process.env`; callers may layer values from `.env.local`.\n\t */\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve the project + branch this process should target, then fetch live Neon\n * connection strings for that branch over the network. Async — calls the Neon API.\n *\n * Use this from build scripts and the `neon-env run` command, where top-level await is\n * fine. For application code that needs a synchronous bootstrap (most frameworks: Drizzle\n * config, Next.js, Vite, etc.), inject env vars via `neon-env run -- <cmd>` and use\n * {@link parseEnv} instead — same {@link NeonEnv} shape, but a sync call against\n * `process.env`.\n *\n * Filesystem- and env-agnostic: pass `projectId` and the target `branchId` explicitly\n * (resolve them in your CLI, e.g. neonctl).\n *\n * ```ts\n * import config from \"../neon\";\n * import { fetchEnv } from \"@neondatabase/env/v1\";\n *\n * const env = await fetchEnv(config, { projectId: \"patient-art-12345\", branchId: \"br-…\" });\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n * ```\n *\n * The package does **not** mutate `process.env` or the filesystem itself.\n */\nexport async function fetchEnv<const C extends Config>(\n\tconfig: C,\n\toptions: FetchEnvOptions,\n): Promise<NeonEnv<C>> {\n\tconst api = options.api ?? createApiFromOptions(options);\n\tconst projectId = options.projectId;\n\n\tconst branches = await api.listBranches(projectId);\n\tif (branches.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: project ${projectId} has no branches.`,\n\t\t\t\t\"Deploy your neon.ts policy (or create a branch) first, or pick a different project id.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { projectId } },\n\t\t);\n\t}\n\n\tconst branch = resolveBranch(options.branchId, branches);\n\tconst desired = resolveConfig(config, {\n\t\tname: branch.name,\n\t\tid: branch.id,\n\t\texists: true,\n\t\t...(branch.parentId ? { parentId: branch.parentId } : {}),\n\t\tisDefault: branch.isDefault,\n\t\tisProtected: branch.protected,\n\t\t...(branch.expiresAt ? { expiresAt: branch.expiresAt } : {}),\n\t});\n\n\tconst [roles, databases] = await Promise.all([\n\t\tapi.listBranchRoles(projectId, branch.id),\n\t\tapi.listBranchDatabases(projectId, branch.id),\n\t]);\n\n\tconst roleName = pickRoleName(roles, branch, options.roleName);\n\tconst databaseName = pickDatabaseName(\n\t\tdatabases,\n\t\tbranch,\n\t\troleName,\n\t\toptions.databaseName,\n\t);\n\n\t// Fan out: always fetch both Postgres URIs. Conditionally fetch auth + dataApi based\n\t// on the branch policy. Auth key fields are only returned at integration creation time;\n\t// for Better Auth they may legitimately be empty, so absence in the local env becomes\n\t// empty string values while still emitting the required variable names.\n\tconst wantsAuth = desired.authEnabled;\n\tconst wantsDataApi = desired.dataApiEnabled;\n\n\tconst [pooled, unpooled, authSnapshot, dataApiSnapshot] = await Promise.all(\n\t\t[\n\t\t\tapi.getConnectionUri(projectId, {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tdatabaseName,\n\t\t\t\troleName,\n\t\t\t\tpooled: true,\n\t\t\t}),\n\t\t\tapi.getConnectionUri(projectId, {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tdatabaseName,\n\t\t\t\troleName,\n\t\t\t\tpooled: false,\n\t\t\t}),\n\t\t\twantsAuth\n\t\t\t\t? api.getNeonAuth(projectId, branch.id)\n\t\t\t\t: Promise.resolve(null),\n\t\t\twantsDataApi\n\t\t\t\t? api.getNeonDataApi(projectId, branch.id, databaseName)\n\t\t\t\t: Promise.resolve(null),\n\t\t],\n\t);\n\n\tconst result: Record<string, unknown> = {\n\t\tpostgres: {\n\t\t\tdatabaseUrl: pooled.uri,\n\t\t\tdatabaseUrlUnpooled: unpooled.uri,\n\t\t},\n\t};\n\n\tif (wantsAuth) {\n\t\tif (!authSnapshot) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.NotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: branch policy enables auth but no Neon Auth integration is enabled on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t\"Enable it via `apply(config, { projectId, branchId })` (or `npx neonctl …`), in the Neon Console — then re-run fetchEnv. Or return auth.enabled=false.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: { projectId, branchId: branch.id },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tconst baseUrl = resolveAuthBaseUrl(\n\t\t\tauthSnapshot.baseUrl,\n\t\t\toptions.env ?? process.env,\n\t\t);\n\t\tresult.auth = { baseUrl } satisfies NeonAuthEnv;\n\t}\n\n\tif (wantsDataApi) {\n\t\tif (!dataApiSnapshot) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.NotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: branch policy enables dataApi but no Data API integration is enabled on branch ${branch.name} (${branch.id}) database ${databaseName}.`,\n\t\t\t\t\t\"Enable it via `apply(config, { projectId, branchId })` or in the Neon Console — then re-run fetchEnv. Or return dataApi.enabled=false.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tprojectId,\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\tdatabaseName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tresult.dataApi = { url: dataApiSnapshot.url } satisfies NeonDataApiEnv;\n\t}\n\n\treturn result as NeonEnv<C>;\n}\n\n/**\n * Resolve the Neon Auth base URL to surface in `env.auth`. Prefer the value returned by\n * the integration (`getNeonAuth` includes it); fall back to whatever is already in the\n * caller's env source so older integrations created before `base_url` was returned still\n * round-trip through `env run`.\n */\nfunction resolveAuthBaseUrl(\n\tsnapshotBaseUrl: string | undefined,\n\tsource: NodeJS.ProcessEnv,\n): string {\n\tif (snapshotBaseUrl && snapshotBaseUrl !== \"\") return snapshotBaseUrl;\n\treturn source[NEON_ENV_VAR_KEYS.auth.baseUrl] ?? \"\";\n}\n\nfunction createApiFromOptions(options: FetchEnvOptions): NeonApi {\n\treturn createNeonApiFromOptions(\n\t\t\"fetchEnv\",\n\t\toptions.apiKey ? { apiKey: options.apiKey } : {},\n\t);\n}\n\nfunction resolveBranch(\n\tbranchId: string,\n\tbranches: NeonBranchSnapshot[],\n): NeonBranchSnapshot {\n\tconst match = branches.find((b) => b.id === branchId);\n\tif (match) return match;\n\tthrow new PlatformError(\n\t\tErrorCode.BranchNotFound,\n\t\t[\n\t\t\t`fetchEnv: branch id ${JSON.stringify(branchId)} not found on project.`,\n\t\t\t`Existing branches: ${branches.map((b) => `${b.name} (${b.id})`).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId,\n\t\t\t\tavailable: branches.map((b) => b.id),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction pickRoleName(\n\troles: NeonRoleSnapshot[],\n\tbranch: NeonBranchSnapshot,\n\trequested: string | undefined,\n): string {\n\tif (requested) {\n\t\tif (!roles.some((r) => r.name === requested)) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.BranchNotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: role \"${requested}\" not found on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t`Existing roles: ${roles.map((r) => r.name).join(\", \") || \"(none)\"}.`,\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\troleName: requested,\n\t\t\t\t\t\tavailableRoles: roles.map((r) => r.name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn requested;\n\t}\n\tif (roles.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has no roles.`,\n\t\t\t\t\"Create one via the Neon console or pass `roleName` explicitly.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { branchId: branch.id } },\n\t\t);\n\t}\n\tif (roles.length === 1) return roles[0].name;\n\n\t// Multiple roles. Enabling Neon Auth / the Data API provisions the PostgREST roles\n\t// (authenticator/anonymous/authenticated) alongside the project owner, so a normal\n\t// branch ends up with >1 role even though only the owner backs a `DATABASE_URL`.\n\t// Default to Neon's owner role; if the project was created with a custom owner name,\n\t// fall back to the single role left after dropping the managed auth roles. Only a\n\t// genuinely ambiguous set (more than one app role) still asks the caller to choose.\n\tconst owner = roles.find((r) => r.name === NEON_DEFAULT_OWNER_ROLE);\n\tif (owner) return owner.name;\n\n\tconst appRoles = roles.filter((r) => !NEON_MANAGED_AUTH_ROLES.has(r.name));\n\tif (appRoles.length === 1) return appRoles[0].name;\n\n\tthrow new PlatformError(\n\t\tErrorCode.AmbiguousBranchAuth,\n\t\t[\n\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has ${roles.length} roles and none is \"${NEON_DEFAULT_OWNER_ROLE}\"; cannot auto-pick.`,\n\t\t\t`Pass \\`roleName\\` explicitly. Available: ${roles.map((r) => r.name).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tavailableRoles: roles.map((r) => r.name),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction pickDatabaseName(\n\tdatabases: NeonDatabaseSnapshot[],\n\tbranch: NeonBranchSnapshot,\n\troleName: string,\n\trequested: string | undefined,\n): string {\n\tif (requested) {\n\t\tif (!databases.some((d) => d.name === requested)) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.BranchNotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: database \"${requested}\" not found on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t`Existing databases: ${databases.map((d) => d.name).join(\", \") || \"(none)\"}.`,\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\tdatabaseName: requested,\n\t\t\t\t\t\tavailableDatabases: databases.map((d) => d.name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn requested;\n\t}\n\tif (databases.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has no databases.`,\n\t\t\t\t\"Create one via the Neon console or pass `databaseName` explicitly.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { branchId: branch.id } },\n\t\t);\n\t}\n\tif (databases.length === 1) return databases[0].name;\n\n\t// Prefer a database owned by the role we're connecting as.\n\tconst owned = databases.filter((d) => d.ownerName === roleName);\n\tif (owned.length === 1) return owned[0].name;\n\n\tthrow new PlatformError(\n\t\tErrorCode.AmbiguousBranchAuth,\n\t\t[\n\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has ${databases.length} databases; cannot auto-pick.`,\n\t\t\t`Pass \\`databaseName\\` explicitly. Available: ${databases.map((d) => d.name).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tavailableDatabases: databases.map((d) => d.name),\n\t\t\t},\n\t\t},\n\t);\n}\n\n// ───────────────────────── parseEnv ─────────────────────────\n\n/**\n * Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from\n * `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.\n *\n * `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include\n * URL-illegal characters in the password (rare but legal in Neon's connection-string\n * format) fail the WHATWG `URL` parse, so we settle for \"non-empty string\".\n */\nconst postgresEnvSchema = z.object({\n\tDATABASE_URL: z\n\t\t.string({ message: \"DATABASE_URL is missing\" })\n\t\t.min(1, \"DATABASE_URL must not be empty\"),\n\tDATABASE_URL_UNPOOLED: z\n\t\t.string({ message: \"DATABASE_URL_UNPOOLED is missing\" })\n\t\t.min(1, \"DATABASE_URL_UNPOOLED must not be empty\"),\n});\n\nconst authEnvSchema = z.object({\n\tNEON_AUTH_BASE_URL: z\n\t\t.string({ message: \"NEON_AUTH_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_BASE_URL must not be empty\"),\n});\n\nconst dataApiEnvSchema = z.object({\n\tNEON_DATA_API_URL: z\n\t\t.string({ message: \"NEON_DATA_API_URL is missing\" })\n\t\t.min(1, \"NEON_DATA_API_URL must not be empty\"),\n});\n\n/** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */\nfunction isServiceEnabledInput(\n\ttoggle: ServiceToggleInput | undefined,\n): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/**\n * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env`, validates\n * the required Neon env vars with zod, and returns the same {@link NeonEnv} shape — so the\n * rest of your app touches `env.postgres.databaseUrl` instead of stringly-typed\n * `process.env.DATABASE_URL` lookups.\n *\n * Designed for the **\"env-vars-already-injected\"** path:\n * - You wrapped your dev command with `neon-env run -- <cmd>` or `neon dev`.\n * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.\n * - You are **inside a deployed Neon Function**, whose env was uploaded at `config apply`.\n *\n * Unlike the old API, `parseEnv` does **not** take a branch name: the secret set is now\n * static (top-level `config.auth` / `config.dataApi`), so it reads those directly without\n * evaluating the per-branch closure.\n *\n * The second argument is a **scope**:\n * - omitted — *external* scope (app bootstrap, build scripts, your dev machine). Returns\n * `{ postgres, auth?, dataApi? }`.\n * - a **function slug** (a key of `config.preview.functions`) — *function* scope: you are\n * running inside that function. Returns the same branch secrets **plus** a typed\n * `function` namespace with the function's declared env-var keys.\n *\n * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env\n * isn't fully populated, with a fix hint pointing back at `neon dev` / `neon-env run`.\n *\n * ```ts\n * import config from \"../neon\";\n * import { parseEnv } from \"@neondatabase/env/v1\";\n *\n * // External (app / build):\n * const env = parseEnv(config);\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n *\n * // Inside the \"hello\" function:\n * const env = parseEnv(config, \"hello\");\n * env.function.resendApiKey; // typed from hello's declared env keys\n * ```\n */\nexport function parseEnv<const C extends Config>(config: C): NeonEnv<C>;\nexport function parseEnv<\n\tconst C extends Config,\n\tconst S extends FunctionSlugOf<C>,\n>(config: C, scope: S): NeonEnv<C> & NeonFunctionEnv<C, S>;\nexport function parseEnv(config: Config, scope?: string): unknown {\n\tconst source = process.env;\n\tconst issues: string[] = [];\n\tconst result: Record<string, unknown> = {};\n\n\tconst pg = postgresEnvSchema.safeParse({\n\t\tDATABASE_URL: source.DATABASE_URL,\n\t\tDATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED,\n\t});\n\tif (pg.success) {\n\t\tresult.postgres = {\n\t\t\tdatabaseUrl: pg.data.DATABASE_URL,\n\t\t\tdatabaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED,\n\t\t} satisfies NeonPostgresEnv;\n\t} else {\n\t\tfor (const issue of pg.error.issues) issues.push(issue.message);\n\t}\n\n\tif (isServiceEnabledInput(config.auth)) {\n\t\tconst auth = authEnvSchema.safeParse({\n\t\t\tNEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,\n\t\t});\n\t\tif (auth.success) {\n\t\t\tresult.auth = {\n\t\t\t\tbaseUrl: auth.data.NEON_AUTH_BASE_URL,\n\t\t\t} satisfies NeonAuthEnv;\n\t\t} else {\n\t\t\tfor (const issue of auth.error.issues) issues.push(issue.message);\n\t\t}\n\t}\n\n\tif (isServiceEnabledInput(config.dataApi)) {\n\t\tconst dataApi = dataApiEnvSchema.safeParse({\n\t\t\tNEON_DATA_API_URL: source.NEON_DATA_API_URL,\n\t\t});\n\t\tif (dataApi.success) {\n\t\t\tresult.dataApi = {\n\t\t\t\turl: dataApi.data.NEON_DATA_API_URL,\n\t\t\t} satisfies NeonDataApiEnv;\n\t\t} else {\n\t\t\tfor (const issue of dataApi.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (scope !== undefined) {\n\t\tconst fn = config.preview?.functions?.[scope];\n\t\tif (!fn) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.EnvNotInjected,\n\t\t\t\t[\n\t\t\t\t\t`parseEnv: no function \"${scope}\" is declared in this policy's preview.functions.`,\n\t\t\t\t\t\"Pass a declared function slug (or omit the scope to read external env).\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t{ details: { scope } },\n\t\t\t);\n\t\t}\n\t\tconst envOut: Record<string, string> = {};\n\t\tfor (const key of Object.keys(fn.env ?? {})) {\n\t\t\tconst value = source[key];\n\t\t\t// Only a truly *unset* var is \"not injected\". Function env values carry no\n\t\t\t// non-empty constraint (unlike DATABASE_URL / NEON_AUTH_BASE_URL), so a\n\t\t\t// deliberately empty value is a present, valid value and is passed through.\n\t\t\tif (value === undefined) {\n\t\t\t\tissues.push(`${key} is missing (function \"${scope}\")`);\n\t\t\t} else {\n\t\t\t\tenvOut[key] = value;\n\t\t\t}\n\t\t}\n\t\tresult.function = envOut;\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\" - for the `function` namespace: deploy the function (`neon deploy` / `config apply`) so its env is uploaded.\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\n\treturn result;\n}\n\n// ───────────────────────── env-var mapping helpers ─────────────────────────\n\n/**\n * Project a fully-resolved {@link NeonEnv} into the OS-level `{ KEY: value }` pairs used\n * for cross-process transport. Named after the web-platform `.entries()` convention\n * (`URLSearchParams` / `Headers` / `FormData`); returns a `Record` rather than an\n * iterator of tuples since that's the shape env injection needs (wrap with\n * `Object.entries(...)` if you want literal `[key, value]` pairs). Used by `neon-env run`\n * to inject the vars into a subprocess's `process.env`.\n *\n * Walks the value at runtime so it works for any `NeonEnv<C>` regardless of which\n * conditional namespaces are present.\n */\nexport function toEntries(env: NeonEnv<Config>): Record<string, string> {\n\tconst out: Record<string, string> = {\n\t\t[NEON_ENV_VAR_KEYS.postgres.databaseUrl]: env.postgres.databaseUrl,\n\t\t[NEON_ENV_VAR_KEYS.postgres.databaseUrlUnpooled]:\n\t\t\tenv.postgres.databaseUrlUnpooled,\n\t};\n\tconst withAuth = env as { auth?: NeonAuthEnv };\n\tif (withAuth.auth) {\n\t\tout[NEON_ENV_VAR_KEYS.auth.baseUrl] = withAuth.auth.baseUrl;\n\t}\n\tconst withDataApi = env as { dataApi?: NeonDataApiEnv };\n\tif (withDataApi.dataApi) {\n\t\tout[NEON_ENV_VAR_KEYS.dataApi.url] = withDataApi.dataApi.url;\n\t}\n\treturn out;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"env.js","names":[],"sources":["../../src/lib/env.ts"],"sourcesContent":["import {\n\ttype Config,\n\tcreateNeonApiFromOptions,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype NeonDatabaseSnapshot,\n\ttype NeonRoleSnapshot,\n\tPlatformError,\n\tresolveConfig,\n\ttype ServiceToggleInput,\n} from \"@neondatabase/config/v1\";\nimport { z } from \"zod\";\n\n/**\n * Mapping between the {@link NeonEnv} property paths and the OS-level env-var keys used\n * for cross-process transport (via `.env` files, `env run -- <cmd>`, or anything else\n * that talks to `process.env`).\n *\n * Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the\n * camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used\n * by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /\n * {@link dataApiEnvSchema}.\n */\n/**\n * Neon's default branch owner role, created with every project. This is the role a\n * `DATABASE_URL` should connect as.\n */\nconst NEON_DEFAULT_OWNER_ROLE = \"neondb_owner\";\n\n/**\n * Roles Neon provisions for the Auth / Data API (PostgREST) stack. They exist to back\n * RLS-scoped Data API requests authenticated by JWT — never to hold a `DATABASE_URL` —\n * so they're skipped when auto-picking the connection role. Enabling Neon Auth or the\n * Data API (`neon config apply`) adds these next to the owner role, which is why a plain\n * branch routinely reports more than one role.\n */\nconst NEON_MANAGED_AUTH_ROLES: ReadonlySet<string> = new Set([\n\t\"authenticator\",\n\t\"anonymous\",\n\t\"authenticated\",\n]);\n\nexport const NEON_ENV_VAR_KEYS = {\n\tpostgres: {\n\t\tdatabaseUrl: \"DATABASE_URL\",\n\t\tdatabaseUrlUnpooled: \"DATABASE_URL_UNPOOLED\",\n\t},\n\tauth: {\n\t\tbaseUrl: \"NEON_AUTH_BASE_URL\",\n\t\tjwksUrl: \"NEON_AUTH_JWKS_URL\",\n\t},\n\tdataApi: {\n\t\turl: \"NEON_DATA_API_URL\",\n\t},\n} as const;\n\n/** Per-namespace inner shapes. Exposed so consumers can name the parts independently. */\nexport interface NeonPostgresEnv {\n\t/**\n\t * Pooled connection string (via Neon's PgBouncer pooler). The right default for\n\t * serverless drivers (`@neondatabase/serverless`, edge runtimes, Postgres.js, …).\n\t */\n\tdatabaseUrl: string;\n\t/**\n\t * Direct (unpooled) connection string. Use this when you need session-level\n\t * features (`LISTEN`/`NOTIFY`, prepared statements across calls, transactions\n\t * spanning round-trips) that PgBouncer's transaction-mode pooling drops.\n\t */\n\tdatabaseUrlUnpooled: string;\n}\n\n/**\n * Bits of a Neon Auth integration for the resolved branch. Only present on `NeonEnv`\n * when the branch policy enables `auth`.\n *\n * Neon Auth exposes the `baseUrl` (which doubles as the publishable client identifier) and\n * the `jwksUrl` used to verify tokens it issues. `fetchEnv` reads both from the live\n * integration; `parseEnv` reads them from `process.env` (`NEON_AUTH_BASE_URL` /\n * `NEON_AUTH_JWKS_URL`).\n */\nexport interface NeonAuthEnv {\n\tbaseUrl: string;\n\t/** JWKS URL for verifying tokens issued by Neon Auth (`NEON_AUTH_JWKS_URL`). */\n\tjwksUrl: string;\n}\n\n/** Bits of a Neon Data API integration. Only present when the branch policy enables it. */\nexport interface NeonDataApiEnv {\n\turl: string;\n}\n\n/**\n * Empty record alias used as the \"false\" branch of the conditional namespace adds below.\n * `Record<never, never>` is the no-op for intersection — the cleaner alternative to `{}`,\n * which biome rejects (it means \"any non-null\", not \"empty object\").\n */\ntype NoNamespace = Record<never, never>;\n\n/**\n * Resolve a **static** service toggle (the value of `config.auth` / `config.dataApi`) to a\n * type-level boolean. The whole-thing wrapping (`[T] extends […]`) turns off distribution\n * so a union/`undefined` is checked as one unit:\n *\n * - `false` / `{ enabled: false }` / `undefined` → `false`\n * - `true` / `{ enabled: true }` / any other object (`{}`, `{ enabled?: boolean }`) → `true`\n * (a present toggle defaults to enabled)\n * - the bare `boolean | ServiceToggle | undefined` (the default `Config` param, no literal\n * info) → `false`, so an untyped policy yields just `{ postgres }`.\n */\ntype ServiceOn<T> = [T] extends [false]\n\t? false\n\t: [T] extends [{ enabled: false }]\n\t\t? false\n\t\t: [T] extends [undefined]\n\t\t\t? false\n\t\t\t: [T] extends [true]\n\t\t\t\t? true\n\t\t\t\t: [T] extends [{ enabled: true }]\n\t\t\t\t\t? true\n\t\t\t\t\t: [T] extends [object]\n\t\t\t\t\t\t? true\n\t\t\t\t\t\t: false;\n\n/**\n * Static, namespaced shape of `fetchEnv` / `parseEnv`'s return value. Generic over the\n * {@link Config} so the type system knows which optional namespaces are present.\n *\n * Because the secret-bearing toggles now live in the **static** top-level `config.auth` /\n * `config.dataApi` (not inside a per-branch closure), the namespace presence is a direct\n * read of those fields — no union-across-branches, no default-config escape hatch:\n *\n * - `postgres` is always present.\n * - `auth` is added iff `config.auth` is statically enabled.\n * - `dataApi` is added iff `config.dataApi` is statically enabled.\n */\nexport type NeonEnv<C extends Config = Config> = {\n\tpostgres: NeonPostgresEnv;\n} & (ServiceOn<NonNullable<C[\"auth\"]>> extends true\n\t? { auth: NeonAuthEnv }\n\t: NoNamespace) &\n\t(ServiceOn<NonNullable<C[\"dataApi\"]>> extends true\n\t\t? { dataApi: NeonDataApiEnv }\n\t\t: NoNamespace);\n\n/** The static `preview.functions` record of a config, or an empty record when absent. */\ntype PreviewFunctionsOf<C extends Config> = NonNullable<C[\"preview\"]> extends {\n\tfunctions: infer F;\n}\n\t? F\n\t: Record<never, never>;\n\n/** The declared function slugs of a config (record keys), as a string union. */\nexport type FunctionSlugOf<C extends Config> = Extract<\n\tkeyof PreviewFunctionsOf<C>,\n\tstring\n>;\n\n/** The declared env-var keys of one function `S`, as a string union. */\ntype FunctionEnvKeysOf<\n\tC extends Config,\n\tS extends string,\n> = S extends keyof PreviewFunctionsOf<C>\n\t? NonNullable<PreviewFunctionsOf<C>[S]> extends { env: infer E }\n\t\t? Extract<keyof E, string>\n\t\t: never\n\t: never;\n\n/**\n * The extra `function` namespace added to `parseEnv`'s result when called with a function\n * slug scope: the declared env-var keys for that function, each resolved to a `string`.\n */\nexport type NeonFunctionEnv<C extends Config, S extends string> = {\n\tfunction: Record<FunctionEnvKeysOf<C, S>, string>;\n};\n\nexport interface FetchEnvOptions {\n\t/**\n\t * Neon project id. **Required** — the management API addresses branches through their\n\t * project. Resolve it in your CLI (e.g. neonctl) and pass it in.\n\t */\n\tprojectId: string;\n\t/** Neon branch id (`br-…`). **Required.** Resolve names to ids before calling. */\n\tbranchId: string;\n\t/**\n\t * Neon API key. Resolved via the standard chain (option → `NEON_API_KEY` →\n\t * `~/.config/neonctl/credentials.json`) when omitted. Ignored when a custom `api`\n\t * is supplied.\n\t */\n\tapiKey?: string;\n\t/**\n\t * Neon **management** API base URL (not the Auth base URL). Falls back to\n\t * `NEON_API_HOST`, then production. Ignored when a custom `api` is supplied.\n\t */\n\tapiHost?: string;\n\t/**\n\t * Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely\n\t * on the default real adapter built from `apiKey`.\n\t */\n\tapi?: NeonApi;\n\t/**\n\t * Role name to fetch credentials for. When omitted, the connection role is auto-picked:\n\t * the only role on the branch, else Neon's default owner (`neondb_owner`), else the\n\t * single role left after dropping the managed Auth/Data API roles\n\t * (`authenticator`/`anonymous`/`authenticated`). Throws {@link PlatformError} with\n\t * `PLATFORM_AMBIGUOUS_BRANCH_AUTH` only when more than one app role remains.\n\t */\n\troleName?: string;\n\t/**\n\t * Database name. When omitted, the only database on the branch is auto-picked; throws\n\t * {@link PlatformError} with `PLATFORM_AMBIGUOUS_BRANCH_AUTH` if the branch has more\n\t * than one database.\n\t */\n\tdatabaseName?: string;\n\t/**\n\t * Env source used for one-time Auth keys that cannot be refetched after integration\n\t * creation. Defaults to `process.env`; callers may layer values from `.env.local`.\n\t */\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve the project + branch this process should target, then fetch live Neon\n * connection strings for that branch over the network. Async — calls the Neon API.\n *\n * Use this from build scripts and the `neon-env run` command, where top-level await is\n * fine. For application code that needs a synchronous bootstrap (most frameworks: Drizzle\n * config, Next.js, Vite, etc.), inject env vars via `neon-env run -- <cmd>` and use\n * {@link parseEnv} instead — same {@link NeonEnv} shape, but a sync call against\n * `process.env`.\n *\n * Filesystem- and env-agnostic: pass `projectId` and the target `branchId` explicitly\n * (resolve them in your CLI, e.g. neonctl).\n *\n * ```ts\n * import config from \"../neon\";\n * import { fetchEnv } from \"@neondatabase/env/v1\";\n *\n * const env = await fetchEnv(config, { projectId: \"patient-art-12345\", branchId: \"br-…\" });\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n * ```\n *\n * The package does **not** mutate `process.env` or the filesystem itself.\n */\nexport async function fetchEnv<const C extends Config>(\n\tconfig: C,\n\toptions: FetchEnvOptions,\n): Promise<NeonEnv<C>> {\n\tconst api = options.api ?? createApiFromOptions(options);\n\tconst projectId = options.projectId;\n\n\tconst branches = await api.listBranches(projectId);\n\tif (branches.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: project ${projectId} has no branches.`,\n\t\t\t\t\"Deploy your neon.ts policy (or create a branch) first, or pick a different project id.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { projectId } },\n\t\t);\n\t}\n\n\tconst branch = resolveBranch(options.branchId, branches);\n\tconst desired = resolveConfig(config, {\n\t\tname: branch.name,\n\t\tid: branch.id,\n\t\texists: true,\n\t\t...(branch.parentId ? { parentId: branch.parentId } : {}),\n\t\tisDefault: branch.isDefault,\n\t\tisProtected: branch.protected,\n\t\t...(branch.expiresAt ? { expiresAt: branch.expiresAt } : {}),\n\t});\n\n\tconst [roles, databases] = await Promise.all([\n\t\tapi.listBranchRoles(projectId, branch.id),\n\t\tapi.listBranchDatabases(projectId, branch.id),\n\t]);\n\n\tconst roleName = pickRoleName(roles, branch, options.roleName);\n\tconst databaseName = pickDatabaseName(\n\t\tdatabases,\n\t\tbranch,\n\t\troleName,\n\t\toptions.databaseName,\n\t);\n\n\t// Fan out: always fetch both Postgres URIs. Conditionally fetch auth + dataApi based\n\t// on the branch policy. Auth key fields are only returned at integration creation time;\n\t// for Better Auth they may legitimately be empty, so absence in the local env becomes\n\t// empty string values while still emitting the required variable names.\n\tconst wantsAuth = desired.authEnabled;\n\tconst wantsDataApi = desired.dataApiEnabled;\n\n\tconst [pooled, unpooled, authSnapshot, dataApiSnapshot] = await Promise.all(\n\t\t[\n\t\t\tapi.getConnectionUri(projectId, {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tdatabaseName,\n\t\t\t\troleName,\n\t\t\t\tpooled: true,\n\t\t\t}),\n\t\t\tapi.getConnectionUri(projectId, {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tdatabaseName,\n\t\t\t\troleName,\n\t\t\t\tpooled: false,\n\t\t\t}),\n\t\t\twantsAuth\n\t\t\t\t? api.getNeonAuth(projectId, branch.id)\n\t\t\t\t: Promise.resolve(null),\n\t\t\twantsDataApi\n\t\t\t\t? api.getNeonDataApi(projectId, branch.id, databaseName)\n\t\t\t\t: Promise.resolve(null),\n\t\t],\n\t);\n\n\tconst result: Record<string, unknown> = {\n\t\tpostgres: {\n\t\t\tdatabaseUrl: pooled.uri,\n\t\t\tdatabaseUrlUnpooled: unpooled.uri,\n\t\t},\n\t};\n\n\tif (wantsAuth) {\n\t\tif (!authSnapshot) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.NotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: branch policy enables auth but no Neon Auth integration is enabled on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t\"Enable it via `apply(config, { projectId, branchId })` (or `npx neonctl …`), in the Neon Console — then re-run fetchEnv. Or return auth.enabled=false.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: { projectId, branchId: branch.id },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tconst envSource = options.env ?? process.env;\n\t\tconst baseUrl = resolveAuthBaseUrl(authSnapshot.baseUrl, envSource);\n\t\tconst jwksUrl = resolveAuthJwksUrl(authSnapshot.jwksUrl, envSource);\n\t\tresult.auth = { baseUrl, jwksUrl } satisfies NeonAuthEnv;\n\t}\n\n\tif (wantsDataApi) {\n\t\tif (!dataApiSnapshot) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.NotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: branch policy enables dataApi but no Data API integration is enabled on branch ${branch.name} (${branch.id}) database ${databaseName}.`,\n\t\t\t\t\t\"Enable it via `apply(config, { projectId, branchId })` or in the Neon Console — then re-run fetchEnv. Or return dataApi.enabled=false.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tprojectId,\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\tdatabaseName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tresult.dataApi = { url: dataApiSnapshot.url } satisfies NeonDataApiEnv;\n\t}\n\n\treturn result as NeonEnv<C>;\n}\n\n/**\n * Resolve the Neon Auth base URL to surface in `env.auth`. Prefer the value returned by\n * the integration (`getNeonAuth` includes it); fall back to whatever is already in the\n * caller's env source so older integrations created before `base_url` was returned still\n * round-trip through `env run`.\n */\nfunction resolveAuthBaseUrl(\n\tsnapshotBaseUrl: string | undefined,\n\tsource: NodeJS.ProcessEnv,\n): string {\n\tif (snapshotBaseUrl && snapshotBaseUrl !== \"\") return snapshotBaseUrl;\n\treturn source[NEON_ENV_VAR_KEYS.auth.baseUrl] ?? \"\";\n}\n\n/**\n * Resolve the Neon Auth JWKS URL to surface in `env.auth`. Prefer the value returned by the\n * integration (`getNeonAuth` always includes `jwks_url`); fall back to the caller's env\n * source so the value still round-trips through `env run` if a snapshot ever omits it.\n */\nfunction resolveAuthJwksUrl(\n\tsnapshotJwksUrl: string | undefined,\n\tsource: NodeJS.ProcessEnv,\n): string {\n\tif (snapshotJwksUrl && snapshotJwksUrl !== \"\") return snapshotJwksUrl;\n\treturn source[NEON_ENV_VAR_KEYS.auth.jwksUrl] ?? \"\";\n}\n\nfunction createApiFromOptions(options: FetchEnvOptions): NeonApi {\n\treturn createNeonApiFromOptions(\"fetchEnv\", {\n\t\t...(options.apiKey ? { apiKey: options.apiKey } : {}),\n\t\t...(options.apiHost ? { apiHost: options.apiHost } : {}),\n\t});\n}\n\nfunction resolveBranch(\n\tbranchId: string,\n\tbranches: NeonBranchSnapshot[],\n): NeonBranchSnapshot {\n\tconst match = branches.find((b) => b.id === branchId);\n\tif (match) return match;\n\tthrow new PlatformError(\n\t\tErrorCode.BranchNotFound,\n\t\t[\n\t\t\t`fetchEnv: branch id ${JSON.stringify(branchId)} not found on project.`,\n\t\t\t`Existing branches: ${branches.map((b) => `${b.name} (${b.id})`).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId,\n\t\t\t\tavailable: branches.map((b) => b.id),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction pickRoleName(\n\troles: NeonRoleSnapshot[],\n\tbranch: NeonBranchSnapshot,\n\trequested: string | undefined,\n): string {\n\tif (requested) {\n\t\tif (!roles.some((r) => r.name === requested)) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.BranchNotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: role \"${requested}\" not found on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t`Existing roles: ${roles.map((r) => r.name).join(\", \") || \"(none)\"}.`,\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\troleName: requested,\n\t\t\t\t\t\tavailableRoles: roles.map((r) => r.name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn requested;\n\t}\n\tif (roles.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has no roles.`,\n\t\t\t\t\"Create one via the Neon console or pass `roleName` explicitly.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { branchId: branch.id } },\n\t\t);\n\t}\n\tif (roles.length === 1) return roles[0].name;\n\n\t// Multiple roles. Enabling Neon Auth / the Data API provisions the PostgREST roles\n\t// (authenticator/anonymous/authenticated) alongside the project owner, so a normal\n\t// branch ends up with >1 role even though only the owner backs a `DATABASE_URL`.\n\t// Default to Neon's owner role; if the project was created with a custom owner name,\n\t// fall back to the single role left after dropping the managed auth roles. Only a\n\t// genuinely ambiguous set (more than one app role) still asks the caller to choose.\n\tconst owner = roles.find((r) => r.name === NEON_DEFAULT_OWNER_ROLE);\n\tif (owner) return owner.name;\n\n\tconst appRoles = roles.filter((r) => !NEON_MANAGED_AUTH_ROLES.has(r.name));\n\tif (appRoles.length === 1) return appRoles[0].name;\n\n\tthrow new PlatformError(\n\t\tErrorCode.AmbiguousBranchAuth,\n\t\t[\n\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has ${roles.length} roles and none is \"${NEON_DEFAULT_OWNER_ROLE}\"; cannot auto-pick.`,\n\t\t\t`Pass \\`roleName\\` explicitly. Available: ${roles.map((r) => r.name).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tavailableRoles: roles.map((r) => r.name),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction pickDatabaseName(\n\tdatabases: NeonDatabaseSnapshot[],\n\tbranch: NeonBranchSnapshot,\n\troleName: string,\n\trequested: string | undefined,\n): string {\n\tif (requested) {\n\t\tif (!databases.some((d) => d.name === requested)) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.BranchNotFound,\n\t\t\t\t[\n\t\t\t\t\t`fetchEnv: database \"${requested}\" not found on branch ${branch.name} (${branch.id}).`,\n\t\t\t\t\t`Existing databases: ${databases.map((d) => d.name).join(\", \") || \"(none)\"}.`,\n\t\t\t\t].join(\" \"),\n\t\t\t\t{\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbranchId: branch.id,\n\t\t\t\t\t\tdatabaseName: requested,\n\t\t\t\t\t\tavailableDatabases: databases.map((d) => d.name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn requested;\n\t}\n\tif (databases.length === 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.BranchNotFound,\n\t\t\t[\n\t\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has no databases.`,\n\t\t\t\t\"Create one via the Neon console or pass `databaseName` explicitly.\",\n\t\t\t].join(\" \"),\n\t\t\t{ details: { branchId: branch.id } },\n\t\t);\n\t}\n\tif (databases.length === 1) return databases[0].name;\n\n\t// Prefer a database owned by the role we're connecting as.\n\tconst owned = databases.filter((d) => d.ownerName === roleName);\n\tif (owned.length === 1) return owned[0].name;\n\n\tthrow new PlatformError(\n\t\tErrorCode.AmbiguousBranchAuth,\n\t\t[\n\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has ${databases.length} databases; cannot auto-pick.`,\n\t\t\t`Pass \\`databaseName\\` explicitly. Available: ${databases.map((d) => d.name).join(\", \")}.`,\n\t\t].join(\" \"),\n\t\t{\n\t\t\tdetails: {\n\t\t\t\tbranchId: branch.id,\n\t\t\t\tavailableDatabases: databases.map((d) => d.name),\n\t\t\t},\n\t\t},\n\t);\n}\n\n// ───────────────────────── parseEnv ─────────────────────────\n\n/**\n * Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from\n * `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.\n *\n * `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include\n * URL-illegal characters in the password (rare but legal in Neon's connection-string\n * format) fail the WHATWG `URL` parse, so we settle for \"non-empty string\".\n */\nconst postgresEnvSchema = z.object({\n\tDATABASE_URL: z\n\t\t.string({ message: \"DATABASE_URL is missing\" })\n\t\t.min(1, \"DATABASE_URL must not be empty\"),\n\tDATABASE_URL_UNPOOLED: z\n\t\t.string({ message: \"DATABASE_URL_UNPOOLED is missing\" })\n\t\t.min(1, \"DATABASE_URL_UNPOOLED must not be empty\"),\n});\n\nconst authEnvSchema = z.object({\n\tNEON_AUTH_BASE_URL: z\n\t\t.string({ message: \"NEON_AUTH_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_BASE_URL must not be empty\"),\n\tNEON_AUTH_JWKS_URL: z\n\t\t.string({ message: \"NEON_AUTH_JWKS_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_JWKS_URL must not be empty\"),\n});\n\nconst dataApiEnvSchema = z.object({\n\tNEON_DATA_API_URL: z\n\t\t.string({ message: \"NEON_DATA_API_URL is missing\" })\n\t\t.min(1, \"NEON_DATA_API_URL must not be empty\"),\n});\n\n/** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */\nfunction isServiceEnabledInput(\n\ttoggle: ServiceToggleInput | undefined,\n): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/**\n * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env`, validates\n * the required Neon env vars with zod, and returns the same {@link NeonEnv} shape — so the\n * rest of your app touches `env.postgres.databaseUrl` instead of stringly-typed\n * `process.env.DATABASE_URL` lookups.\n *\n * Designed for the **\"env-vars-already-injected\"** path:\n * - You wrapped your dev command with `neon-env run -- <cmd>` or `neon dev`.\n * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.\n * - You are **inside a deployed Neon Function**, whose env was uploaded at `config apply`.\n *\n * Unlike the old API, `parseEnv` does **not** take a branch name: the secret set is now\n * static (top-level `config.auth` / `config.dataApi`), so it reads those directly without\n * evaluating the per-branch closure.\n *\n * The second argument is a **scope**:\n * - omitted — *external* scope (app bootstrap, build scripts, your dev machine). Returns\n * `{ postgres, auth?, dataApi? }`.\n * - a **function slug** (a key of `config.preview.functions`) — *function* scope: you are\n * running inside that function. Returns the same branch secrets **plus** a typed\n * `function` namespace with the function's declared env-var keys.\n *\n * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env\n * isn't fully populated, with a fix hint pointing back at `neon dev` / `neon-env run`.\n *\n * ```ts\n * import config from \"../neon\";\n * import { parseEnv } from \"@neondatabase/env/v1\";\n *\n * // External (app / build):\n * const env = parseEnv(config);\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n *\n * // Inside the \"hello\" function:\n * const env = parseEnv(config, \"hello\");\n * env.function.resendApiKey; // typed from hello's declared env keys\n * ```\n */\nexport function parseEnv<const C extends Config>(config: C): NeonEnv<C>;\nexport function parseEnv<\n\tconst C extends Config,\n\tconst S extends FunctionSlugOf<C>,\n>(config: C, scope: S): NeonEnv<C> & NeonFunctionEnv<C, S>;\nexport function parseEnv(config: Config, scope?: string): unknown {\n\tconst source = process.env;\n\tconst issues: string[] = [];\n\tconst result: Record<string, unknown> = {};\n\n\tconst pg = postgresEnvSchema.safeParse({\n\t\tDATABASE_URL: source.DATABASE_URL,\n\t\tDATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED,\n\t});\n\tif (pg.success) {\n\t\tresult.postgres = {\n\t\t\tdatabaseUrl: pg.data.DATABASE_URL,\n\t\t\tdatabaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED,\n\t\t} satisfies NeonPostgresEnv;\n\t} else {\n\t\tfor (const issue of pg.error.issues) issues.push(issue.message);\n\t}\n\n\tif (isServiceEnabledInput(config.auth)) {\n\t\tconst auth = authEnvSchema.safeParse({\n\t\t\tNEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,\n\t\t\tNEON_AUTH_JWKS_URL: source.NEON_AUTH_JWKS_URL,\n\t\t});\n\t\tif (auth.success) {\n\t\t\tresult.auth = {\n\t\t\t\tbaseUrl: auth.data.NEON_AUTH_BASE_URL,\n\t\t\t\tjwksUrl: auth.data.NEON_AUTH_JWKS_URL,\n\t\t\t} satisfies NeonAuthEnv;\n\t\t} else {\n\t\t\tfor (const issue of auth.error.issues) issues.push(issue.message);\n\t\t}\n\t}\n\n\tif (isServiceEnabledInput(config.dataApi)) {\n\t\tconst dataApi = dataApiEnvSchema.safeParse({\n\t\t\tNEON_DATA_API_URL: source.NEON_DATA_API_URL,\n\t\t});\n\t\tif (dataApi.success) {\n\t\t\tresult.dataApi = {\n\t\t\t\turl: dataApi.data.NEON_DATA_API_URL,\n\t\t\t} satisfies NeonDataApiEnv;\n\t\t} else {\n\t\t\tfor (const issue of dataApi.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (scope !== undefined) {\n\t\tconst fn = config.preview?.functions?.[scope];\n\t\tif (!fn) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.EnvNotInjected,\n\t\t\t\t[\n\t\t\t\t\t`parseEnv: no function \"${scope}\" is declared in this policy's preview.functions.`,\n\t\t\t\t\t\"Pass a declared function slug (or omit the scope to read external env).\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t{ details: { scope } },\n\t\t\t);\n\t\t}\n\t\tconst envOut: Record<string, string> = {};\n\t\tfor (const key of Object.keys(fn.env ?? {})) {\n\t\t\tconst value = source[key];\n\t\t\t// Only a truly *unset* var is \"not injected\". Function env values carry no\n\t\t\t// non-empty constraint (unlike DATABASE_URL / NEON_AUTH_BASE_URL), so a\n\t\t\t// deliberately empty value is a present, valid value and is passed through.\n\t\t\tif (value === undefined) {\n\t\t\t\tissues.push(`${key} is missing (function \"${scope}\")`);\n\t\t\t} else {\n\t\t\t\tenvOut[key] = value;\n\t\t\t}\n\t\t}\n\t\tresult.function = envOut;\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\" - for the `function` namespace: deploy the function (`neon deploy` / `config apply`) so its env is uploaded.\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\n\treturn result;\n}\n\n// ───────────────────────── env-var mapping helpers ─────────────────────────\n\n/**\n * Project a fully-resolved {@link NeonEnv} into the OS-level `{ KEY: value }` pairs used\n * for cross-process transport. Named after the web-platform `.entries()` convention\n * (`URLSearchParams` / `Headers` / `FormData`); returns a `Record` rather than an\n * iterator of tuples since that's the shape env injection needs (wrap with\n * `Object.entries(...)` if you want literal `[key, value]` pairs). Used by `neon-env run`\n * to inject the vars into a subprocess's `process.env`.\n *\n * Walks the value at runtime so it works for any `NeonEnv<C>` regardless of which\n * conditional namespaces are present.\n */\nexport function toEntries(env: NeonEnv<Config>): Record<string, string> {\n\tconst out: Record<string, string> = {\n\t\t[NEON_ENV_VAR_KEYS.postgres.databaseUrl]: env.postgres.databaseUrl,\n\t\t[NEON_ENV_VAR_KEYS.postgres.databaseUrlUnpooled]:\n\t\t\tenv.postgres.databaseUrlUnpooled,\n\t};\n\tconst withAuth = env as { auth?: NeonAuthEnv };\n\tif (withAuth.auth) {\n\t\tout[NEON_ENV_VAR_KEYS.auth.baseUrl] = withAuth.auth.baseUrl;\n\t\tout[NEON_ENV_VAR_KEYS.auth.jwksUrl] = withAuth.auth.jwksUrl;\n\t}\n\tconst withDataApi = env as { dataApi?: NeonDataApiEnv };\n\tif (withDataApi.dataApi) {\n\t\tout[NEON_ENV_VAR_KEYS.dataApi.url] = withDataApi.dataApi.url;\n\t}\n\treturn out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,MAAM,0BAA0B;;;;;;;;AAShC,MAAM,0BAA+C,IAAI,IAAI;CAC5D;CACA;CACA;AACD,CAAC;AAED,MAAa,oBAAoB;CAChC,UAAU;EACT,aAAa;EACb,qBAAqB;CACtB;CACA,MAAM;EACL,SAAS;EACT,SAAS;CACV;CACA,SAAS,EACR,KAAK,oBACN;AACD;;;;;;;;;;;;;;;;;;;;;;;;AA6LA,eAAsB,SACrB,QACA,SACsB;CACtB,MAAM,MAAM,QAAQ,OAAO,qBAAqB,OAAO;CACvD,MAAM,YAAY,QAAQ;CAE1B,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS;CACjD,IAAI,SAAS,WAAW,GACvB,MAAM,IAAI,cACT,UAAU,gBACV,CACC,qBAAqB,UAAU,oBAC/B,wFACD,EAAE,KAAK,GAAG,GACV,EAAE,SAAS,EAAE,UAAU,EAAE,CAC1B;CAGD,MAAM,SAAS,cAAc,QAAQ,UAAU,QAAQ;CACvD,MAAM,UAAU,cAAc,QAAQ;EACrC,MAAM,OAAO;EACb,IAAI,OAAO;EACX,QAAQ;EACR,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;EACvD,WAAW,OAAO;EAClB,aAAa,OAAO;EACpB,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;CAC3D,CAAC;CAED,MAAM,CAAC,OAAO,aAAa,MAAM,QAAQ,IAAI,CAC5C,IAAI,gBAAgB,WAAW,OAAO,EAAE,GACxC,IAAI,oBAAoB,WAAW,OAAO,EAAE,CAC7C,CAAC;CAED,MAAM,WAAW,aAAa,OAAO,QAAQ,QAAQ,QAAQ;CAC7D,MAAM,eAAe,iBACpB,WACA,QACA,UACA,QAAQ,YACT;CAMA,MAAM,YAAY,QAAQ;CAC1B,MAAM,eAAe,QAAQ;CAE7B,MAAM,CAAC,QAAQ,UAAU,cAAc,mBAAmB,MAAM,QAAQ,IACvE;EACC,IAAI,iBAAiB,WAAW;GAC/B,UAAU,OAAO;GACjB;GACA;GACA,QAAQ;EACT,CAAC;EACD,IAAI,iBAAiB,WAAW;GAC/B,UAAU,OAAO;GACjB;GACA;GACA,QAAQ;EACT,CAAC;EACD,YACG,IAAI,YAAY,WAAW,OAAO,EAAE,IACpC,QAAQ,QAAQ,IAAI;EACvB,eACG,IAAI,eAAe,WAAW,OAAO,IAAI,YAAY,IACrD,QAAQ,QAAQ,IAAI;CACxB,CACD;CAEA,MAAM,SAAkC,EACvC,UAAU;EACT,aAAa,OAAO;EACpB,qBAAqB,SAAS;CAC/B,EACD;CAEA,IAAI,WAAW;EACd,IAAI,CAAC,cACJ,MAAM,IAAI,cACT,UAAU,UACV,CACC,0FAA0F,OAAO,KAAK,IAAI,OAAO,GAAG,KACpH,wJACD,EAAE,KAAK,GAAG,GACV,EACC,SAAS;GAAE;GAAW,UAAU,OAAO;EAAG,EAC3C,CACD;EAED,MAAM,YAAY,QAAQ,OAAO,QAAQ;EAGzC,OAAO,OAAO;GAAE,SAFA,mBAAmB,aAAa,SAAS,SAEnC;GAAG,SADT,mBAAmB,aAAa,SAAS,SAC1B;EAAE;CAClC;CAEA,IAAI,cAAc;EACjB,IAAI,CAAC,iBACJ,MAAM,IAAI,cACT,UAAU,UACV,CACC,4FAA4F,OAAO,KAAK,IAAI,OAAO,GAAG,aAAa,aAAa,IAChJ,wIACD,EAAE,KAAK,GAAG,GACV,EACC,SAAS;GACR;GACA,UAAU,OAAO;GACjB;EACD,EACD,CACD;EAED,OAAO,UAAU,EAAE,KAAK,gBAAgB,IAAI;CAC7C;CAEA,OAAO;AACR;;;;;;;AAQA,SAAS,mBACR,iBACA,QACS;CACT,IAAI,mBAAmB,oBAAoB,IAAI,OAAO;CACtD,OAAO,OAAO,kBAAkB,KAAK,YAAY;AAClD;;;;;;AAOA,SAAS,mBACR,iBACA,QACS;CACT,IAAI,mBAAmB,oBAAoB,IAAI,OAAO;CACtD,OAAO,OAAO,kBAAkB,KAAK,YAAY;AAClD;AAEA,SAAS,qBAAqB,SAAmC;CAChE,OAAO,yBAAyB,YAAY;EAC3C,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;EACnD,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;CACvD,CAAC;AACF;AAEA,SAAS,cACR,UACA,UACqB;CACrB,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,OAAO,QAAQ;CACpD,IAAI,OAAO,OAAO;CAClB,MAAM,IAAI,cACT,UAAU,gBACV,CACC,uBAAuB,KAAK,UAAU,QAAQ,EAAE,yBAChD,sBAAsB,SAAS,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,IAAI,EAAE,EAC7E,EAAE,KAAK,GAAG,GACV,EACC,SAAS;EACR;EACA,WAAW,SAAS,KAAK,MAAM,EAAE,EAAE;CACpC,EACD,CACD;AACD;AAEA,SAAS,aACR,OACA,QACA,WACS;CACT,IAAI,WAAW;EACd,IAAI,CAAC,MAAM,MAAM,MAAM,EAAE,SAAS,SAAS,GAC1C,MAAM,IAAI,cACT,UAAU,gBACV,CACC,mBAAmB,UAAU,wBAAwB,OAAO,KAAK,IAAI,OAAO,GAAG,KAC/E,mBAAmB,MAAM,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,SAAS,EACpE,EAAE,KAAK,GAAG,GACV,EACC,SAAS;GACR,UAAU,OAAO;GACjB,UAAU;GACV,gBAAgB,MAAM,KAAK,MAAM,EAAE,IAAI;EACxC,EACD,CACD;EAED,OAAO;CACR;CACA,IAAI,MAAM,WAAW,GACpB,MAAM,IAAI,cACT,UAAU,gBACV,CACC,oBAAoB,OAAO,KAAK,IAAI,OAAO,GAAG,kBAC9C,gEACD,EAAE,KAAK,GAAG,GACV,EAAE,SAAS,EAAE,UAAU,OAAO,GAAG,EAAE,CACpC;CAED,IAAI,MAAM,WAAW,GAAG,OAAO,MAAM,GAAG;CAQxC,MAAM,QAAQ,MAAM,MAAM,MAAM,EAAE,SAAS,uBAAuB;CAClE,IAAI,OAAO,OAAO,MAAM;CAExB,MAAM,WAAW,MAAM,QAAQ,MAAM,CAAC,wBAAwB,IAAI,EAAE,IAAI,CAAC;CACzE,IAAI,SAAS,WAAW,GAAG,OAAO,SAAS,GAAG;CAE9C,MAAM,IAAI,cACT,UAAU,qBACV,CACC,oBAAoB,OAAO,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,OAAO,sBAAsB,wBAAwB,uBACjH,4CAA4C,MAAM,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,EACjF,EAAE,KAAK,GAAG,GACV,EACC,SAAS;EACR,UAAU,OAAO;EACjB,gBAAgB,MAAM,KAAK,MAAM,EAAE,IAAI;CACxC,EACD,CACD;AACD;AAEA,SAAS,iBACR,WACA,QACA,UACA,WACS;CACT,IAAI,WAAW;EACd,IAAI,CAAC,UAAU,MAAM,MAAM,EAAE,SAAS,SAAS,GAC9C,MAAM,IAAI,cACT,UAAU,gBACV,CACC,uBAAuB,UAAU,wBAAwB,OAAO,KAAK,IAAI,OAAO,GAAG,KACnF,uBAAuB,UAAU,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,SAAS,EAC5E,EAAE,KAAK,GAAG,GACV,EACC,SAAS;GACR,UAAU,OAAO;GACjB,cAAc;GACd,oBAAoB,UAAU,KAAK,MAAM,EAAE,IAAI;EAChD,EACD,CACD;EAED,OAAO;CACR;CACA,IAAI,UAAU,WAAW,GACxB,MAAM,IAAI,cACT,UAAU,gBACV,CACC,oBAAoB,OAAO,KAAK,IAAI,OAAO,GAAG,sBAC9C,oEACD,EAAE,KAAK,GAAG,GACV,EAAE,SAAS,EAAE,UAAU,OAAO,GAAG,EAAE,CACpC;CAED,IAAI,UAAU,WAAW,GAAG,OAAO,UAAU,GAAG;CAGhD,MAAM,QAAQ,UAAU,QAAQ,MAAM,EAAE,cAAc,QAAQ;CAC9D,IAAI,MAAM,WAAW,GAAG,OAAO,MAAM,GAAG;CAExC,MAAM,IAAI,cACT,UAAU,qBACV,CACC,oBAAoB,OAAO,KAAK,IAAI,OAAO,GAAG,QAAQ,UAAU,OAAO,gCACvE,gDAAgD,UAAU,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,EACzF,EAAE,KAAK,GAAG,GACV,EACC,SAAS;EACR,UAAU,OAAO;EACjB,oBAAoB,UAAU,KAAK,MAAM,EAAE,IAAI;CAChD,EACD,CACD;AACD;;;;;;;;;AAYA,MAAM,oBAAoB,EAAE,OAAO;CAClC,cAAc,EACZ,OAAO,EAAE,SAAS,0BAA0B,CAAC,EAC7C,IAAI,GAAG,gCAAgC;CACzC,uBAAuB,EACrB,OAAO,EAAE,SAAS,mCAAmC,CAAC,EACtD,IAAI,GAAG,yCAAyC;AACnD,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO;CAC9B,oBAAoB,EAClB,OAAO,EAAE,SAAS,gCAAgC,CAAC,EACnD,IAAI,GAAG,sCAAsC;CAC/C,oBAAoB,EAClB,OAAO,EAAE,SAAS,gCAAgC,CAAC,EACnD,IAAI,GAAG,sCAAsC;AAChD,CAAC;AAED,MAAM,mBAAmB,EAAE,OAAO,EACjC,mBAAmB,EACjB,OAAO,EAAE,SAAS,+BAA+B,CAAC,EAClD,IAAI,GAAG,qCAAqC,EAC/C,CAAC;;AAGD,SAAS,sBACR,QACU;CACV,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,IAAI,OAAO,WAAW,WAAW,OAAO;CACxC,OAAO,OAAO,YAAY;AAC3B;AA6CA,SAAgB,SAAS,QAAgB,OAAyB;CACjE,MAAM,SAAS,QAAQ;CACvB,MAAM,SAAmB,CAAC;CAC1B,MAAM,SAAkC,CAAC;CAEzC,MAAM,KAAK,kBAAkB,UAAU;EACtC,cAAc,OAAO;EACrB,uBAAuB,OAAO;CAC/B,CAAC;CACD,IAAI,GAAG,SACN,OAAO,WAAW;EACjB,aAAa,GAAG,KAAK;EACrB,qBAAqB,GAAG,KAAK;CAC9B;MAEA,KAAK,MAAM,SAAS,GAAG,MAAM,QAAQ,OAAO,KAAK,MAAM,OAAO;CAG/D,IAAI,sBAAsB,OAAO,IAAI,GAAG;EACvC,MAAM,OAAO,cAAc,UAAU;GACpC,oBAAoB,OAAO;GAC3B,oBAAoB,OAAO;EAC5B,CAAC;EACD,IAAI,KAAK,SACR,OAAO,OAAO;GACb,SAAS,KAAK,KAAK;GACnB,SAAS,KAAK,KAAK;EACpB;OAEA,KAAK,MAAM,SAAS,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,OAAO;CAElE;CAEA,IAAI,sBAAsB,OAAO,OAAO,GAAG;EAC1C,MAAM,UAAU,iBAAiB,UAAU,EAC1C,mBAAmB,OAAO,kBAC3B,CAAC;EACD,IAAI,QAAQ,SACX,OAAO,UAAU,EAChB,KAAK,QAAQ,KAAK,kBACnB;OAEA,KAAK,MAAM,SAAS,QAAQ,MAAM,QACjC,OAAO,KAAK,MAAM,OAAO;CAE5B;CAEA,IAAI,UAAU,KAAA,GAAW;EACxB,MAAM,KAAK,OAAO,SAAS,YAAY;EACvC,IAAI,CAAC,IACJ,MAAM,IAAI,cACT,UAAU,gBACV,CACC,0BAA0B,MAAM,oDAChC,yEACD,EAAE,KAAK,IAAI,GACX,EAAE,SAAS,EAAE,MAAM,EAAE,CACtB;EAED,MAAM,SAAiC,CAAC;EACxC,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,CAAC,GAAG;GAC5C,MAAM,QAAQ,OAAO;GAIrB,IAAI,UAAU,KAAA,GACb,OAAO,KAAK,GAAG,IAAI,yBAAyB,MAAM,GAAG;QAErD,OAAO,OAAO;EAEhB;EACA,OAAO,WAAW;CACnB;CAEA,IAAI,OAAO,SAAS,GACnB,MAAM,IAAI,cACT,UAAU,gBACV;EACC;EACA,GAAG,OAAO,KAAK,MAAM,OAAO,GAAG;EAC/B;EACA;EACA;EACA;EACA;CACD,EAAE,KAAK,IAAI,GACX,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,CAChC;CAGD,OAAO;AACR;;;;;;;;;;;;AAeA,SAAgB,UAAU,KAA8C;CACvE,MAAM,MAA8B;GAClC,kBAAkB,SAAS,cAAc,IAAI,SAAS;GACtD,kBAAkB,SAAS,sBAC3B,IAAI,SAAS;CACf;CACA,MAAM,WAAW;CACjB,IAAI,SAAS,MAAM;EAClB,IAAI,kBAAkB,KAAK,WAAW,SAAS,KAAK;EACpD,IAAI,kBAAkB,KAAK,WAAW,SAAS,KAAK;CACrD;CACA,MAAM,cAAc;CACpB,IAAI,YAAY,SACf,IAAI,kBAAkB,QAAQ,OAAO,YAAY,QAAQ;CAE1D,OAAO;AACR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neondatabase/env",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Resolve and inject Neon connection strings for the branch selected by your neon.ts policy. fetchEnv / parseEnv plus a `neon-env` CLI with `run` and `export`.",
5
5
  "keywords": [
6
6
  "neon",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "zod": "^4.4.3",
57
57
  "yargs": "^18.0.0",
58
- "@neondatabase/config": "0.4.1"
58
+ "@neondatabase/config": "0.4.2"
59
59
  },
60
60
  "engines": {
61
61
  "node": ">=22"