@neondatabase/env 0.0.0 → 0.1.1

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.
Files changed (46) hide show
  1. package/LICENSE.md +178 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +61 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config/dist/lib/neon-api.d.ts +264 -0
  6. package/dist/config/dist/lib/neon-api.d.ts.map +1 -0
  7. package/dist/config/dist/lib/types.d.ts +209 -0
  8. package/dist/config/dist/lib/types.d.ts.map +1 -0
  9. package/dist/config/dist/v1.d.ts +4 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +3 -0
  12. package/dist/lib/cli/commands.d.ts +46 -0
  13. package/dist/lib/cli/commands.d.ts.map +1 -0
  14. package/dist/lib/cli/commands.js +181 -0
  15. package/dist/lib/cli/commands.js.map +1 -0
  16. package/dist/lib/cli/resolve-context.d.ts +35 -0
  17. package/dist/lib/cli/resolve-context.d.ts.map +1 -0
  18. package/dist/lib/cli/resolve-context.js +90 -0
  19. package/dist/lib/cli/resolve-context.js.map +1 -0
  20. package/dist/lib/env.d.ts +194 -0
  21. package/dist/lib/env.d.ts.map +1 -0
  22. package/dist/lib/env.js +263 -0
  23. package/dist/lib/env.js.map +1 -0
  24. package/dist/v1.d.ts +2 -0
  25. package/dist/v1.js +2 -0
  26. package/package.json +72 -21
  27. package/.env.example +0 -5
  28. package/e2e/env.e2e.test.ts +0 -36
  29. package/e2e/helpers.ts +0 -188
  30. package/e2e/load-env.ts +0 -29
  31. package/e2e/setup.ts +0 -24
  32. package/src/cli.ts +0 -107
  33. package/src/index.ts +0 -5
  34. package/src/lib/cli/commands.test.ts +0 -101
  35. package/src/lib/cli/commands.ts +0 -267
  36. package/src/lib/cli/resolve-context.test.ts +0 -242
  37. package/src/lib/cli/resolve-context.ts +0 -142
  38. package/src/lib/env.test.ts +0 -172
  39. package/src/lib/env.ts +0 -610
  40. package/src/lib/fake-neon-api.ts +0 -782
  41. package/src/lib/test-utils.ts +0 -83
  42. package/src/v1.ts +0 -32
  43. package/tsconfig.json +0 -4
  44. package/tsdown.config.ts +0 -20
  45. package/vitest.config.ts +0 -19
  46. package/vitest.e2e.config.ts +0 -29
@@ -0,0 +1,194 @@
1
+ import { BranchConfig, Config } from "../config/dist/lib/types.js";
2
+ import { NeonApi } from "../config/dist/lib/neon-api.js";
3
+ import "../config/dist/v1.js";
4
+
5
+ //#region src/lib/env.d.ts
6
+
7
+ /**
8
+ * Mapping between the {@link NeonEnv} property paths and the OS-level env-var keys used
9
+ * for cross-process transport (via `.env` files, `env run -- <cmd>`, or anything else
10
+ * that talks to `process.env`).
11
+ *
12
+ * Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the
13
+ * camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used
14
+ * by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /
15
+ * {@link dataApiEnvSchema}.
16
+ */
17
+ declare const NEON_ENV_VAR_KEYS: {
18
+ readonly postgres: {
19
+ readonly databaseUrl: "DATABASE_URL";
20
+ readonly databaseUrlUnpooled: "DATABASE_URL_UNPOOLED";
21
+ };
22
+ readonly auth: {
23
+ readonly baseUrl: "NEON_AUTH_BASE_URL";
24
+ };
25
+ readonly dataApi: {
26
+ readonly url: "NEON_DATA_API_URL";
27
+ };
28
+ };
29
+ /** Per-namespace inner shapes. Exposed so consumers can name the parts independently. */
30
+ interface NeonPostgresEnv {
31
+ /**
32
+ * Pooled connection string (via Neon's PgBouncer pooler). The right default for
33
+ * serverless drivers (`@neondatabase/serverless`, edge runtimes, Postgres.js, …).
34
+ */
35
+ databaseUrl: string;
36
+ /**
37
+ * Direct (unpooled) connection string. Use this when you need session-level
38
+ * features (`LISTEN`/`NOTIFY`, prepared statements across calls, transactions
39
+ * spanning round-trips) that PgBouncer's transaction-mode pooling drops.
40
+ */
41
+ databaseUrlUnpooled: string;
42
+ }
43
+ /**
44
+ * Bits of a Neon Auth integration for the resolved branch. Only present on `NeonEnv`
45
+ * when the branch policy enables `auth`.
46
+ *
47
+ * Neon Auth exposes a single `baseUrl` that doubles as the publishable client identifier
48
+ * — the rest of the surface (project id, JWKS URL, …) is derived from it at runtime by
49
+ * the Neon Auth SDK. `fetchEnv` reads it from the live integration; `parseEnv` reads it
50
+ * from `process.env` (`NEON_AUTH_BASE_URL`).
51
+ */
52
+ interface NeonAuthEnv {
53
+ baseUrl: string;
54
+ }
55
+ /** Bits of a Neon Data API integration. Only present when the branch policy enables it. */
56
+ interface NeonDataApiEnv {
57
+ url: string;
58
+ }
59
+ /**
60
+ * Empty record alias used as the "false" branch of the conditional namespace adds below.
61
+ * `Record<never, never>` is the no-op for intersection — the cleaner alternative to `{}`,
62
+ * which biome rejects (it means "any non-null", not "empty object").
63
+ */
64
+ type NoNamespace = Record<never, never>;
65
+ type BranchConfigOf<C extends Config> = ReturnType<C> extends BranchConfig ? ReturnType<C> : BranchConfig;
66
+ type ServiceToggleOf<Cfg, Key extends "auth" | "dataApi"> = Cfg extends unknown ? Key extends keyof Cfg ? Cfg[Key] : never : never;
67
+ type HasEnabledService<Cfg, Key extends "auth" | "dataApi"> = [Exclude<ServiceToggleOf<Cfg, Key>, undefined | {
68
+ enabled: false;
69
+ }>] extends [never] ? false : true;
70
+ type IsDefaultConfig<C extends Config> = Config extends C ? true : false;
71
+ /**
72
+ * Static, namespaced shape of `fetchEnv` / `parseEnv`'s return value. Generic over the
73
+ * {@link Config} so the type system knows which optional namespaces are present.
74
+ *
75
+ * - `postgres` is always present.
76
+ * - `auth` is added iff the config return type has an `auth` namespace that is not
77
+ * explicitly disabled.
78
+ * - `dataApi` is added iff the config return type has a `dataApi` namespace that is not
79
+ * explicitly disabled.
80
+ */
81
+ type NeonEnv<C extends Config = Config> = {
82
+ postgres: NeonPostgresEnv;
83
+ } & (IsDefaultConfig<C> extends true ? NoNamespace : HasEnabledService<BranchConfigOf<C>, "auth"> extends true ? {
84
+ auth: NeonAuthEnv;
85
+ } : NoNamespace) & (IsDefaultConfig<C> extends true ? NoNamespace : HasEnabledService<BranchConfigOf<C>, "dataApi"> extends true ? {
86
+ dataApi: NeonDataApiEnv;
87
+ } : NoNamespace);
88
+ interface FetchEnvOptions {
89
+ /**
90
+ * Neon project id. **Required** — the management API addresses branches through their
91
+ * project. Resolve it in your CLI (e.g. neonctl) and pass it in.
92
+ */
93
+ projectId: string;
94
+ /** Neon branch id (`br-…`). **Required.** Resolve names to ids before calling. */
95
+ branchId: string;
96
+ /**
97
+ * Neon API key. Resolved via the standard chain (option → `NEON_API_KEY` →
98
+ * `~/.config/neonctl/credentials.json`) when omitted. Ignored when a custom `api`
99
+ * is supplied.
100
+ */
101
+ apiKey?: string;
102
+ /**
103
+ * Inject a custom NeonApi adapter. Primarily used by tests; production callers can rely
104
+ * on the default real adapter built from `apiKey`.
105
+ */
106
+ api?: NeonApi;
107
+ /**
108
+ * Role name to fetch credentials for. When omitted, the only role on the branch is
109
+ * auto-picked; throws {@link PlatformError} with `PLATFORM_AMBIGUOUS_BRANCH_AUTH` if
110
+ * the branch has more than one role.
111
+ */
112
+ roleName?: string;
113
+ /**
114
+ * Database name. When omitted, the only database on the branch is auto-picked; throws
115
+ * {@link PlatformError} with `PLATFORM_AMBIGUOUS_BRANCH_AUTH` if the branch has more
116
+ * than one database.
117
+ */
118
+ databaseName?: string;
119
+ /**
120
+ * Env source used for one-time Auth keys that cannot be refetched after integration
121
+ * creation. Defaults to `process.env`; callers may layer values from `.env.local`.
122
+ */
123
+ env?: NodeJS.ProcessEnv;
124
+ }
125
+ /**
126
+ * Resolve the project + branch this process should target, then fetch live Neon
127
+ * connection strings for that branch over the network. Async — calls the Neon API.
128
+ *
129
+ * Use this from build scripts and the `neon-env run` command, where top-level await is
130
+ * fine. For application code that needs a synchronous bootstrap (most frameworks: Drizzle
131
+ * config, Next.js, Vite, etc.), inject env vars via `neon-env run -- <cmd>` and use
132
+ * {@link parseEnv} instead — same {@link NeonEnv} shape, but a sync call against
133
+ * `process.env`.
134
+ *
135
+ * Filesystem- and env-agnostic: pass `projectId` and the target `branchId` explicitly
136
+ * (resolve them in your CLI, e.g. neonctl).
137
+ *
138
+ * ```ts
139
+ * import config from "../neon";
140
+ * import { fetchEnv } from "@neondatabase/env/v1";
141
+ *
142
+ * const env = await fetchEnv(config, { projectId: "patient-art-12345", branchId: "br-…" });
143
+ * const db = drizzle(neon(env.postgres.databaseUrl), { schema });
144
+ * ```
145
+ *
146
+ * The package does **not** mutate `process.env` or the filesystem itself.
147
+ */
148
+ declare function fetchEnv<const C extends Config>(config: C, options: FetchEnvOptions): Promise<NeonEnv<C>>;
149
+ /**
150
+ * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env` (or
151
+ * `options.env`), validates the required Neon env vars with zod, and returns the same
152
+ * {@link NeonEnv} shape — so the rest of your app touches `env.postgres.databaseUrl`
153
+ * instead of stringly-typed `process.env.DATABASE_URL` lookups.
154
+ *
155
+ * Designed for the **"env-vars-already-injected"** path:
156
+ * - You wrapped your dev command with `neon-env run -- <cmd>`.
157
+ * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.
158
+ *
159
+ * Takes a branch **name** (not an id like the API-backed `fetchEnv` / config operations):
160
+ * `parseEnv` makes no Neon API call, so the only thing it needs the branch for is
161
+ * evaluating your `neon.ts` policy, which switches on `branch.name`. With no network round
162
+ * trip there's no way to turn a `br-…` id into a name, so the name is passed directly.
163
+ * Pass it explicitly so the result is deterministic and not coupled to any `NEON_*` env
164
+ * var. (The `neon-env` CLI injects `NEON_BRANCH_NAME`; pass that through, or default to
165
+ * your main branch name.) Prefer `fetchEnv` when runtime code needs the exact live branch.
166
+ *
167
+ * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env
168
+ * isn't fully populated, with a fix hint pointing back at `neon-env run`.
169
+ *
170
+ * ```ts
171
+ * import config from "../neon";
172
+ * import { parseEnv } from "@neondatabase/env/v1";
173
+ *
174
+ * const env = parseEnv(config, process.env.NEON_BRANCH_NAME ?? "main");
175
+ * const db = drizzle(neon(env.postgres.databaseUrl), { schema });
176
+ * // env.auth is statically typed when the config return type has auth: {} or auth.enabled: true.
177
+ * ```
178
+ */
179
+ declare function parseEnv<const C extends Config>(config: C, branchName: string): NeonEnv<C>;
180
+ /**
181
+ * Project a fully-resolved {@link NeonEnv} into the OS-level `{ KEY: value }` pairs used
182
+ * for cross-process transport. Named after the web-platform `.entries()` convention
183
+ * (`URLSearchParams` / `Headers` / `FormData`); returns a `Record` rather than an
184
+ * iterator of tuples since that's the shape env injection needs (wrap with
185
+ * `Object.entries(...)` if you want literal `[key, value]` pairs). Used by `neon-env run`
186
+ * to inject the vars into a subprocess's `process.env`.
187
+ *
188
+ * Walks the value at runtime so it works for any `NeonEnv<C>` regardless of which
189
+ * conditional namespaces are present.
190
+ */
191
+ declare function toEntries(env: NeonEnv<Config>): Record<string, string>;
192
+ //#endregion
193
+ export { FetchEnvOptions, NEON_ENV_VAR_KEYS, NeonAuthEnv, NeonDataApiEnv, NeonEnv, NeonPostgresEnv, fetchEnv, parseEnv, toEntries };
194
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","names":[],"sources":["../../src/lib/env.ts"],"mappings":";;;;;;;;;;AAwBA;AAcA;AAuBA;AAKA;AAEC;AAOwB;AAEN,cArDN,iBAqDM,EAAA;WAAW,QAAA,EAAA;IAAqB,SAAA,WAAA,EAAA,cAAA;IAAX,SAAA,mBAAA,EAAA,uBAAA;;WAC1B,IAAA,EAAA;IAAX,SAAA,OAAA,EAAA,oBAAA;;EACY,SAAA,OAAA,EAAA;IAEV,SAAA,GAAA,EAAe,mBAAA;EAAA,CAAA;;;AACC,UA5CJ,eAAA,CA4CI;;;AACV;AAAA;EAIW,WAAA,EAAA,MAAA;;;;;AACd;EAKH,mBAAe,EAAA,MAAA;;;;;AAAqC;AAYzD;;;;;AAEqB,UA9CJ,WAAA,CA8CI;SAAhB,EAAA,MAAA;;;AAEgB,UA3CJ,cAAA,CA2CI;KAAlB,EAAA,MAAA;;;;;;;KAlCE,WAAA,GAAc,MAuCG,CAAA,KAAA,EAAA,KAAA,CAAA;KArCjB,cAqCD,CAAA,UArC0B,MAqC1B,CAAA,GArCoC,UAqCpC,CArC+C,CAqC/C,CAAA,SArC0D,YAqC1D,GApCD,UAoCC,CApCU,CAoCV,CAAA,GAnCD,YAmCC;KAjCC,eAkCW,CAAA,GAAA,EAAA,YAAA,MAAA,GAAA,SAAA,CAAA,GAlC4C,GAkC5C,SAAA,OAAA,GAjCb,GAiCa,SAAA,MAjCK,GAiCL,GAhCZ,GAgCY,CAhCR,GAgCQ,CAAA,GAAA,KAAA,GAAA,KAAA;KA5BX,iBA6BA,CAAA,GAAA,EAAA,YAAA,MAAA,GAAA,SAAA,CAAA,GAAA,CA5BJ,OA4Be,CA5BP,eA4BO,CA5BS,GA4BT,EA5Bc,GA4Bd,CAAA,EAAA,SAAA,GAAA;EAEC,OAAA,EAAA,KAAe;AAAA,CAAA,CAAA,UAkBzB,CAAA,KAAA,CAAA,GAAA,KAAA,GAAA,IAAA;KA3CF,eA4DS,CAAA,UA5DiB,MA4DjB,CAAA,GA5D2B,MA4D3B,SA5D0C,CA4D1C,GAAA,IAAA,GAAA,KAAA;AAAU;AA0BxB;;;;;;;;AAGU;AAyUM,KAtZJ,OAsZY,CAAA,UAtZM,MAsZN,GAtZe,MAsZf,CAAA,GAAA;EAAA,QAAA,EArZb,eAqZa;KApZnB,eAoZoC,CApZpB,CAoZoB,CAAA,SAAA,IAAA,GAnZtC,WAmZsC,GAlZtC,iBAkZsC,CAlZpB,cAkZoB,CAlZL,CAkZK,CAAA,EAAA,MAAA,CAAA,SAAA,IAAA,GAAA;MAChC,EAlZG,WAkZH;IAjZL,WAmZO,CAAA,GAAA,CAlZT,eAkZS,CAlZO,CAkZP,CAAA,SAAA,IAAA,GAjZP,WAiZO,GAhZP,iBAgZO,CAhZW,cAgZX,CAhZ0B,CAgZ1B,CAAA,EAAA,SAAA,CAAA,SAAA,IAAA,GAAA;SAAR,EA/Ya,cA+Yb;AAAO,CAAA,GA9YL,WA8YK,CAAA;AAgFM,UA5dC,eAAA,CA4dQ;EAAA;;;;EAA8B,SAAA,EAAA,MAAA;;;;;;;;;;;;;QA1chD;;;;;;;;;;;;;;;;;QAiBA,MAAA,CAAO;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BQ,yBAAyB,gBACtC,YACC,kBACP,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyUH,yBAAyB,gBAChC,wBAEN,QAAQ;;;;;;;;;;;;iBAgFK,SAAA,MAAe,QAAQ,UAAU"}
@@ -0,0 +1,263 @@
1
+ import { ErrorCode, PlatformError, createNeonApiFromOptions, resolveConfig } from "@neondatabase/config/v1";
2
+ import { z } from "zod";
3
+ //#region src/lib/env.ts
4
+ /**
5
+ * Mapping between the {@link NeonEnv} property paths and the OS-level env-var keys used
6
+ * for cross-process transport (via `.env` files, `env run -- <cmd>`, or anything else
7
+ * that talks to `process.env`).
8
+ *
9
+ * Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the
10
+ * camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used
11
+ * by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /
12
+ * {@link dataApiEnvSchema}.
13
+ */
14
+ const NEON_ENV_VAR_KEYS = {
15
+ postgres: {
16
+ databaseUrl: "DATABASE_URL",
17
+ databaseUrlUnpooled: "DATABASE_URL_UNPOOLED"
18
+ },
19
+ auth: { baseUrl: "NEON_AUTH_BASE_URL" },
20
+ dataApi: { url: "NEON_DATA_API_URL" }
21
+ };
22
+ /**
23
+ * Resolve the project + branch this process should target, then fetch live Neon
24
+ * connection strings for that branch over the network. Async — calls the Neon API.
25
+ *
26
+ * Use this from build scripts and the `neon-env run` command, where top-level await is
27
+ * fine. For application code that needs a synchronous bootstrap (most frameworks: Drizzle
28
+ * config, Next.js, Vite, etc.), inject env vars via `neon-env run -- <cmd>` and use
29
+ * {@link parseEnv} instead — same {@link NeonEnv} shape, but a sync call against
30
+ * `process.env`.
31
+ *
32
+ * Filesystem- and env-agnostic: pass `projectId` and the target `branchId` explicitly
33
+ * (resolve them in your CLI, e.g. neonctl).
34
+ *
35
+ * ```ts
36
+ * import config from "../neon";
37
+ * import { fetchEnv } from "@neondatabase/env/v1";
38
+ *
39
+ * const env = await fetchEnv(config, { projectId: "patient-art-12345", branchId: "br-…" });
40
+ * const db = drizzle(neon(env.postgres.databaseUrl), { schema });
41
+ * ```
42
+ *
43
+ * The package does **not** mutate `process.env` or the filesystem itself.
44
+ */
45
+ async function fetchEnv(config, options) {
46
+ const api = options.api ?? createApiFromOptions(options);
47
+ const projectId = options.projectId;
48
+ const branches = await api.listBranches(projectId);
49
+ if (branches.length === 0) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: project ${projectId} has no branches.`, "Deploy your neon.ts policy (or create a branch) first, or pick a different project id."].join(" "), { details: { projectId } });
50
+ const branch = resolveBranch(options.branchId, branches);
51
+ const desired = resolveConfig(config, {
52
+ name: branch.name,
53
+ id: branch.id,
54
+ exists: true,
55
+ ...branch.parentId ? { parentId: branch.parentId } : {},
56
+ isDefault: branch.isDefault,
57
+ isProtected: branch.protected,
58
+ ...branch.expiresAt ? { expiresAt: branch.expiresAt } : {}
59
+ });
60
+ const [roles, databases] = await Promise.all([api.listBranchRoles(projectId, branch.id), api.listBranchDatabases(projectId, branch.id)]);
61
+ const roleName = pickRoleName(roles, branch, options.roleName);
62
+ const databaseName = pickDatabaseName(databases, branch, roleName, options.databaseName);
63
+ const wantsAuth = desired.authEnabled;
64
+ const wantsDataApi = desired.dataApiEnabled;
65
+ const [pooled, unpooled, authSnapshot, dataApiSnapshot] = await Promise.all([
66
+ api.getConnectionUri(projectId, {
67
+ branchId: branch.id,
68
+ databaseName,
69
+ roleName,
70
+ pooled: true
71
+ }),
72
+ api.getConnectionUri(projectId, {
73
+ branchId: branch.id,
74
+ databaseName,
75
+ roleName,
76
+ pooled: false
77
+ }),
78
+ wantsAuth ? api.getNeonAuth(projectId, branch.id) : Promise.resolve(null),
79
+ wantsDataApi ? api.getNeonDataApi(projectId, branch.id, databaseName) : Promise.resolve(null)
80
+ ]);
81
+ const result = { postgres: {
82
+ databaseUrl: pooled.uri,
83
+ databaseUrlUnpooled: unpooled.uri
84
+ } };
85
+ if (wantsAuth) {
86
+ if (!authSnapshot) throw new PlatformError(ErrorCode.NotFound, [`fetchEnv: branch policy enables auth but no Neon Auth integration is enabled on branch ${branch.name} (${branch.id}).`, "Enable it via `apply(config, { projectId, branchId })` (or `npx neonctl …`), in the Neon Console — then re-run fetchEnv. Or return auth.enabled=false."].join(" "), { details: {
87
+ projectId,
88
+ branchId: branch.id
89
+ } });
90
+ result.auth = { baseUrl: resolveAuthBaseUrl(authSnapshot.baseUrl, options.env ?? process.env) };
91
+ }
92
+ if (wantsDataApi) {
93
+ 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: {
94
+ projectId,
95
+ branchId: branch.id,
96
+ databaseName
97
+ } });
98
+ result.dataApi = { url: dataApiSnapshot.url };
99
+ }
100
+ return result;
101
+ }
102
+ /**
103
+ * Resolve the Neon Auth base URL to surface in `env.auth`. Prefer the value returned by
104
+ * the integration (`getNeonAuth` includes it); fall back to whatever is already in the
105
+ * caller's env source so older integrations created before `base_url` was returned still
106
+ * round-trip through `env run`.
107
+ */
108
+ function resolveAuthBaseUrl(snapshotBaseUrl, source) {
109
+ if (snapshotBaseUrl && snapshotBaseUrl !== "") return snapshotBaseUrl;
110
+ return source[NEON_ENV_VAR_KEYS.auth.baseUrl] ?? "";
111
+ }
112
+ function createApiFromOptions(options) {
113
+ return createNeonApiFromOptions("fetchEnv", options.apiKey ? { apiKey: options.apiKey } : {});
114
+ }
115
+ function resolveBranch(branchId, branches) {
116
+ const match = branches.find((b) => b.id === branchId);
117
+ if (match) return match;
118
+ throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: branch id ${JSON.stringify(branchId)} not found on project.`, `Existing branches: ${branches.map((b) => `${b.name} (${b.id})`).join(", ")}.`].join(" "), { details: {
119
+ branchId,
120
+ available: branches.map((b) => b.id)
121
+ } });
122
+ }
123
+ function pickRoleName(roles, branch, requested) {
124
+ if (requested) {
125
+ if (!roles.some((r) => r.name === requested)) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: role "${requested}" not found on branch ${branch.name} (${branch.id}).`, `Existing roles: ${roles.map((r) => r.name).join(", ") || "(none)"}.`].join(" "), { details: {
126
+ branchId: branch.id,
127
+ roleName: requested,
128
+ availableRoles: roles.map((r) => r.name)
129
+ } });
130
+ return requested;
131
+ }
132
+ 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 } });
133
+ if (roles.length === 1) return roles[0].name;
134
+ throw new PlatformError(ErrorCode.AmbiguousBranchAuth, [`fetchEnv: branch ${branch.name} (${branch.id}) has ${roles.length} roles; cannot auto-pick.`, `Pass \`roleName\` explicitly. Available: ${roles.map((r) => r.name).join(", ")}.`].join(" "), { details: {
135
+ branchId: branch.id,
136
+ availableRoles: roles.map((r) => r.name)
137
+ } });
138
+ }
139
+ function pickDatabaseName(databases, branch, roleName, requested) {
140
+ if (requested) {
141
+ if (!databases.some((d) => d.name === requested)) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: database "${requested}" not found on branch ${branch.name} (${branch.id}).`, `Existing databases: ${databases.map((d) => d.name).join(", ") || "(none)"}.`].join(" "), { details: {
142
+ branchId: branch.id,
143
+ databaseName: requested,
144
+ availableDatabases: databases.map((d) => d.name)
145
+ } });
146
+ return requested;
147
+ }
148
+ if (databases.length === 0) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: branch ${branch.name} (${branch.id}) has no databases.`, "Create one via the Neon console or pass `databaseName` explicitly."].join(" "), { details: { branchId: branch.id } });
149
+ if (databases.length === 1) return databases[0].name;
150
+ const owned = databases.filter((d) => d.ownerName === roleName);
151
+ if (owned.length === 1) return owned[0].name;
152
+ throw new PlatformError(ErrorCode.AmbiguousBranchAuth, [`fetchEnv: branch ${branch.name} (${branch.id}) has ${databases.length} databases; cannot auto-pick.`, `Pass \`databaseName\` explicitly. Available: ${databases.map((d) => d.name).join(", ")}.`].join(" "), { details: {
153
+ branchId: branch.id,
154
+ availableDatabases: databases.map((d) => d.name)
155
+ } });
156
+ }
157
+ /**
158
+ * Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from
159
+ * `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.
160
+ *
161
+ * `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include
162
+ * URL-illegal characters in the password (rare but legal in Neon's connection-string
163
+ * format) fail the WHATWG `URL` parse, so we settle for "non-empty string".
164
+ */
165
+ const postgresEnvSchema = z.object({
166
+ DATABASE_URL: z.string({ message: "DATABASE_URL is missing" }).min(1, "DATABASE_URL must not be empty"),
167
+ DATABASE_URL_UNPOOLED: z.string({ message: "DATABASE_URL_UNPOOLED is missing" }).min(1, "DATABASE_URL_UNPOOLED must not be empty")
168
+ });
169
+ 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") });
170
+ 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") });
171
+ /**
172
+ * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env` (or
173
+ * `options.env`), validates the required Neon env vars with zod, and returns the same
174
+ * {@link NeonEnv} shape — so the rest of your app touches `env.postgres.databaseUrl`
175
+ * instead of stringly-typed `process.env.DATABASE_URL` lookups.
176
+ *
177
+ * Designed for the **"env-vars-already-injected"** path:
178
+ * - You wrapped your dev command with `neon-env run -- <cmd>`.
179
+ * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.
180
+ *
181
+ * Takes a branch **name** (not an id like the API-backed `fetchEnv` / config operations):
182
+ * `parseEnv` makes no Neon API call, so the only thing it needs the branch for is
183
+ * evaluating your `neon.ts` policy, which switches on `branch.name`. With no network round
184
+ * trip there's no way to turn a `br-…` id into a name, so the name is passed directly.
185
+ * Pass it explicitly so the result is deterministic and not coupled to any `NEON_*` env
186
+ * var. (The `neon-env` CLI injects `NEON_BRANCH_NAME`; pass that through, or default to
187
+ * your main branch name.) Prefer `fetchEnv` when runtime code needs the exact live branch.
188
+ *
189
+ * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env
190
+ * isn't fully populated, with a fix hint pointing back at `neon-env run`.
191
+ *
192
+ * ```ts
193
+ * import config from "../neon";
194
+ * import { parseEnv } from "@neondatabase/env/v1";
195
+ *
196
+ * const env = parseEnv(config, process.env.NEON_BRANCH_NAME ?? "main");
197
+ * const db = drizzle(neon(env.postgres.databaseUrl), { schema });
198
+ * // env.auth is statically typed when the config return type has auth: {} or auth.enabled: true.
199
+ * ```
200
+ */
201
+ function parseEnv(config, branchName) {
202
+ const source = process.env;
203
+ const issues = [];
204
+ const result = {};
205
+ const desired = resolveConfig(config, {
206
+ name: branchName,
207
+ exists: true
208
+ });
209
+ const pg = postgresEnvSchema.safeParse({
210
+ DATABASE_URL: source.DATABASE_URL,
211
+ DATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED
212
+ });
213
+ if (pg.success) result.postgres = {
214
+ databaseUrl: pg.data.DATABASE_URL,
215
+ databaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED
216
+ };
217
+ else for (const issue of pg.error.issues) issues.push(issue.message);
218
+ if (desired.authEnabled) {
219
+ const auth = authEnvSchema.safeParse({ NEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL });
220
+ if (auth.success) result.auth = { baseUrl: auth.data.NEON_AUTH_BASE_URL };
221
+ else for (const issue of auth.error.issues) issues.push(issue.message);
222
+ }
223
+ if (desired.dataApiEnabled) {
224
+ const dataApi = dataApiEnvSchema.safeParse({ NEON_DATA_API_URL: source.NEON_DATA_API_URL });
225
+ if (dataApi.success) result.dataApi = { url: dataApi.data.NEON_DATA_API_URL };
226
+ else for (const issue of dataApi.error.issues) issues.push(issue.message);
227
+ }
228
+ if (issues.length > 0) throw new PlatformError(ErrorCode.EnvNotInjected, [
229
+ "parseEnv: the required Neon env variables are not present in process.env.",
230
+ ...issues.map((i) => ` - ${i}`),
231
+ "Inject them via one of:",
232
+ " - `neon-env run -- <your dev command>` (wraps the command with the vars injected)",
233
+ " - your hosting platform's Neon integration (Vercel, Fly, Railway, …)",
234
+ "Or switch the call to `await fetchEnv(config)` if you're in a context that can do async I/O."
235
+ ].join("\n"), { details: { missing: issues } });
236
+ return result;
237
+ }
238
+ /**
239
+ * Project a fully-resolved {@link NeonEnv} into the OS-level `{ KEY: value }` pairs used
240
+ * for cross-process transport. Named after the web-platform `.entries()` convention
241
+ * (`URLSearchParams` / `Headers` / `FormData`); returns a `Record` rather than an
242
+ * iterator of tuples since that's the shape env injection needs (wrap with
243
+ * `Object.entries(...)` if you want literal `[key, value]` pairs). Used by `neon-env run`
244
+ * to inject the vars into a subprocess's `process.env`.
245
+ *
246
+ * Walks the value at runtime so it works for any `NeonEnv<C>` regardless of which
247
+ * conditional namespaces are present.
248
+ */
249
+ function toEntries(env) {
250
+ const out = {
251
+ [NEON_ENV_VAR_KEYS.postgres.databaseUrl]: env.postgres.databaseUrl,
252
+ [NEON_ENV_VAR_KEYS.postgres.databaseUrlUnpooled]: env.postgres.databaseUrlUnpooled
253
+ };
254
+ const withAuth = env;
255
+ if (withAuth.auth) out[NEON_ENV_VAR_KEYS.auth.baseUrl] = withAuth.auth.baseUrl;
256
+ const withDataApi = env;
257
+ if (withDataApi.dataApi) out[NEON_ENV_VAR_KEYS.dataApi.url] = withDataApi.dataApi.url;
258
+ return out;
259
+ }
260
+ //#endregion
261
+ export { NEON_ENV_VAR_KEYS, fetchEnv, parseEnv, toEntries };
262
+
263
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","names":[],"sources":["../../src/lib/env.ts"],"sourcesContent":["import {\n\ttype BranchConfig,\n\ttype Config,\n\tcreateNeonApiFromOptions,\n\tErrorCode,\n\ttype NeonApi,\n\ttype NeonBranchSnapshot,\n\ttype NeonDatabaseSnapshot,\n\ttype NeonRoleSnapshot,\n\tPlatformError,\n\tresolveConfig,\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 */\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\ntype BranchConfigOf<C extends Config> = ReturnType<C> extends BranchConfig\n\t? ReturnType<C>\n\t: BranchConfig;\n\ntype ServiceToggleOf<Cfg, Key extends \"auth\" | \"dataApi\"> = Cfg extends unknown\n\t? Key extends keyof Cfg\n\t\t? Cfg[Key]\n\t\t: never\n\t: never;\n\ntype HasEnabledService<Cfg, Key extends \"auth\" | \"dataApi\"> = [\n\tExclude<ServiceToggleOf<Cfg, Key>, undefined | { enabled: false }>,\n] extends [never]\n\t? false\n\t: true;\n\ntype IsDefaultConfig<C extends Config> = Config extends C ? true : 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 * - `postgres` is always present.\n * - `auth` is added iff the config return type has an `auth` namespace that is not\n * explicitly disabled.\n * - `dataApi` is added iff the config return type has a `dataApi` namespace that is not\n * explicitly disabled.\n */\nexport type NeonEnv<C extends Config = Config> = {\n\tpostgres: NeonPostgresEnv;\n} & (IsDefaultConfig<C> extends true\n\t? NoNamespace\n\t: HasEnabledService<BranchConfigOf<C>, \"auth\"> extends true\n\t\t? { auth: NeonAuthEnv }\n\t\t: NoNamespace) &\n\t(IsDefaultConfig<C> extends true\n\t\t? NoNamespace\n\t\t: HasEnabledService<BranchConfigOf<C>, \"dataApi\"> extends true\n\t\t\t? { dataApi: NeonDataApiEnv }\n\t\t\t: NoNamespace);\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 only role on the branch is\n\t * auto-picked; throws {@link PlatformError} with `PLATFORM_AMBIGUOUS_BRANCH_AUTH` if\n\t * the branch has more than one role.\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\tthrow new PlatformError(\n\t\tErrorCode.AmbiguousBranchAuth,\n\t\t[\n\t\t\t`fetchEnv: branch ${branch.name} (${branch.id}) has ${roles.length} roles; 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/**\n * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env` (or\n * `options.env`), validates the required Neon env vars with zod, and returns the same\n * {@link NeonEnv} shape — so the rest of your app touches `env.postgres.databaseUrl`\n * instead of stringly-typed `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>`.\n * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.\n *\n * Takes a branch **name** (not an id like the API-backed `fetchEnv` / config operations):\n * `parseEnv` makes no Neon API call, so the only thing it needs the branch for is\n * evaluating your `neon.ts` policy, which switches on `branch.name`. With no network round\n * trip there's no way to turn a `br-…` id into a name, so the name is passed directly.\n * Pass it explicitly so the result is deterministic and not coupled to any `NEON_*` env\n * var. (The `neon-env` CLI injects `NEON_BRANCH_NAME`; pass that through, or default to\n * your main branch name.) Prefer `fetchEnv` when runtime code needs the exact live branch.\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-env run`.\n *\n * ```ts\n * import config from \"../neon\";\n * import { parseEnv } from \"@neondatabase/env/v1\";\n *\n * const env = parseEnv(config, process.env.NEON_BRANCH_NAME ?? \"main\");\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n * // env.auth is statically typed when the config return type has auth: {} or auth.enabled: true.\n * ```\n */\nexport function parseEnv<const C extends Config>(\n\tconfig: C,\n\tbranchName: string,\n): NeonEnv<C> {\n\tconst source = process.env;\n\tconst issues: string[] = [];\n\tconst result: Record<string, unknown> = {};\n\tconst desired = resolveConfig(config, {\n\t\tname: branchName,\n\t\texists: true,\n\t});\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 (desired.authEnabled) {\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 (desired.dataApiEnabled) {\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 (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-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\"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 as NeonEnv<C>;\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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"}
package/dist/v1.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { FetchEnvOptions, NEON_ENV_VAR_KEYS, NeonAuthEnv, NeonDataApiEnv, NeonEnv, NeonPostgresEnv, fetchEnv, parseEnv, toEntries } from "./lib/env.js";
2
+ export { type FetchEnvOptions, NEON_ENV_VAR_KEYS, type NeonAuthEnv, type NeonDataApiEnv, type NeonEnv, type NeonPostgresEnv, fetchEnv, parseEnv, toEntries };
package/dist/v1.js ADDED
@@ -0,0 +1,2 @@
1
+ import { NEON_ENV_VAR_KEYS, fetchEnv, parseEnv, toEntries } from "./lib/env.js";
2
+ export { NEON_ENV_VAR_KEYS, fetchEnv, parseEnv, toEntries };
package/package.json CHANGED
@@ -1,22 +1,73 @@
1
1
  {
2
- "name": "@neondatabase/env",
3
- "version": "0.0.0",
4
- "description": "Resolve and inject Neon connection strings for the branch selected by your neon.ts policy. fetchEnv / parseEnv plus a single `env run -- <cmd>` CLI.",
5
- "keywords": [
6
- "neon",
7
- "database",
8
- "postgres",
9
- "env",
10
- "dotenv",
11
- "platform"
12
- ],
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/neondatabase/neon-pkgs.git"
16
- },
17
- "license": "Apache-2.0",
18
- "author": {
19
- "name": "Neon",
20
- "url": "https://neon.com"
21
- }
22
- }
2
+ "name": "@neondatabase/env",
3
+ "version": "0.1.1",
4
+ "description": "Resolve and inject Neon connection strings for the branch selected by your neon.ts policy. fetchEnv / parseEnv plus a single `env run -- <cmd>` CLI.",
5
+ "keywords": [
6
+ "neon",
7
+ "database",
8
+ "postgres",
9
+ "env",
10
+ "dotenv",
11
+ "platform"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/neondatabase/neon-pkgs.git"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "author": {
19
+ "name": "Neon",
20
+ "url": "https://neon.com"
21
+ },
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "bin": {
26
+ "neon-env": "dist/cli.js"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./v1": {
35
+ "types": "./dist/v1.d.ts",
36
+ "import": "./dist/v1.js",
37
+ "default": "./dist/v1.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "README.md",
42
+ "dist/",
43
+ "package.json"
44
+ ],
45
+ "devDependencies": {
46
+ "@neondatabase/api-client": "^2.7.1",
47
+ "@types/node": "^20.19.0",
48
+ "@types/yargs": "^17.0.35",
49
+ "@vitest/coverage-v8": "3.0.9",
50
+ "console-fail-test": "0.5.0",
51
+ "tsdown": "^0.14.1",
52
+ "typescript": "^5.8.2",
53
+ "vitest": "^3.0.9"
54
+ },
55
+ "dependencies": {
56
+ "zod": "^4.4.3",
57
+ "yargs": "^18.0.0",
58
+ "@neondatabase/config": "0.2.0"
59
+ },
60
+ "engines": {
61
+ "node": ">=22"
62
+ },
63
+ "publishConfig": {
64
+ "provenance": false
65
+ },
66
+ "scripts": {
67
+ "build": "tsc --noEmit && tsdown",
68
+ "test": "vitest --passWithNoTests",
69
+ "test:ci": "vitest run --passWithNoTests",
70
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
71
+ "tsc": "tsc"
72
+ }
73
+ }
package/.env.example DELETED
@@ -1,5 +0,0 @@
1
- # Copy to `.env` and fill in real values. `.env` itself is gitignored.
2
- # Required to run `pnpm --filter @neondatabase/env test:e2e`.
3
- NEON_API_KEY=
4
- # Optional. When set, e2e tests scope project creation/listing to this org.
5
- NEON_ORG_ID=