@neondatabase/config 0.8.1 → 0.9.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.
- package/README.md +9 -7
- package/dist/lib/define-config.d.ts +1 -1
- package/dist/lib/define-config.js +1 -1
- package/dist/lib/define-config.js.map +1 -1
- package/dist/lib/diff.js +1 -1
- package/dist/lib/diff.js.map +1 -1
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/errors.js +2 -2
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/loader.js +1 -1
- package/dist/lib/loader.js.map +1 -1
- package/dist/lib/neon-api-real.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/v1.js +4 -4
- package/dist/v1.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @neon/config
|
|
2
2
|
|
|
3
3
|
Config-as-Code for the Neon Platform. A repo-local `neon.ts` exports a TypeScript policy function describing a branch's desired state. This package exposes **functions** to inspect, diff, and deploy that policy against the Neon API.
|
|
4
4
|
|
|
@@ -7,14 +7,16 @@ Config-as-Code for the Neon Platform. A repo-local `neon.ts` exports a TypeScrip
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @
|
|
10
|
+
npm install @neon/config
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
> **Requirements:** Node.js >= 20.19.
|
|
14
|
+
|
|
13
15
|
## Define a policy
|
|
14
16
|
|
|
15
17
|
```ts
|
|
16
18
|
// neon.ts
|
|
17
|
-
import { defineConfig } from "@
|
|
19
|
+
import { defineConfig } from "@neon/config/v1";
|
|
18
20
|
|
|
19
21
|
export default defineConfig({
|
|
20
22
|
// Static: what *exists* on every branch. GA service toggles drive the typed env.
|
|
@@ -36,7 +38,7 @@ export default defineConfig({
|
|
|
36
38
|
|
|
37
39
|
A policy is split into a **static** existential set and a **dynamic** `branch` closure:
|
|
38
40
|
|
|
39
|
-
- **Static top-level** — `auth` / `dataApi` (GA service toggles) and the beta `preview` block (`aiGateway`, `functions` keyed by slug, `buckets` keyed by name). Because this is static, the secret set is known at the type level, so `parseEnv` / `fetchEnv` from `@
|
|
41
|
+
- **Static top-level** — `auth` / `dataApi` (GA service toggles) and the beta `preview` block (`aiGateway`, `functions` keyed by slug, `buckets` keyed by name). Because this is static, the secret set is known at the type level, so `parseEnv` / `fetchEnv` from `@neon/env` return an exact `NeonEnv`.
|
|
40
42
|
- **`branch` closure** — receives a **read-only descriptor** (`BranchTarget`) of the branch being evaluated (`name`, `id`, `exists`, `isDefault`, `isProtected`, `parentId`, `expiresAt`) and returns per-branch *tuning*: `parent`, `ttl`, `protected`, `postgres.computeSettings`, and per-function `runtime`. Function memory is fixed at `2048` MiB for now and is not user-configurable. It runs both against existing branches and during pre-create evaluation (`exists: false`). It **cannot** change which services or functions exist — that is what keeps the static secret set sound.
|
|
41
43
|
|
|
42
44
|
Service toggles accept `true` / `{}` / `{ enabled: true }` (enabled) and `false` / `{ enabled: false }` (disabled). Function slugs (record keys) must match `^[a-z0-9]{1,20}$`.
|
|
@@ -90,7 +92,7 @@ The three operations mirror the Terraform mental model: **`inspect`** (read live
|
|
|
90
92
|
|
|
91
93
|
```ts
|
|
92
94
|
import config from "../neon";
|
|
93
|
-
import { inspect, plan, apply } from "@
|
|
95
|
+
import { inspect, plan, apply } from "@neon/config/v1";
|
|
94
96
|
|
|
95
97
|
const target = { projectId: "patient-art-12345", branchId: "main" };
|
|
96
98
|
|
|
@@ -131,7 +133,7 @@ import {
|
|
|
131
133
|
ErrorCode,
|
|
132
134
|
errors,
|
|
133
135
|
schemas,
|
|
134
|
-
} from "@
|
|
136
|
+
} from "@neon/config/v1";
|
|
135
137
|
```
|
|
136
138
|
|
|
137
139
|
## Safety Rules
|
|
@@ -143,4 +145,4 @@ import {
|
|
|
143
145
|
|
|
144
146
|
## Env vars
|
|
145
147
|
|
|
146
|
-
Connection-string resolution/injection lives in the companion package [`@
|
|
148
|
+
Connection-string resolution/injection lives in the companion package [`@neon/env`](../env), which depends on this package for the `Config` type and the Neon API client.
|
|
@@ -70,7 +70,7 @@ type PreviewAutocomplete<Preview> = (Preview extends {
|
|
|
70
70
|
*
|
|
71
71
|
* Used at the top of `neon.ts`:
|
|
72
72
|
* ```ts
|
|
73
|
-
* import { defineConfig } from "@
|
|
73
|
+
* import { defineConfig } from "@neon/config/v1";
|
|
74
74
|
*
|
|
75
75
|
* export default defineConfig({
|
|
76
76
|
* auth: true,
|
|
@@ -10,7 +10,7 @@ const REGION_PREFIX = /^(aws|azure|gcp)-/;
|
|
|
10
10
|
*
|
|
11
11
|
* Used at the top of `neon.ts`:
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { defineConfig } from "@
|
|
13
|
+
* import { defineConfig } from "@neon/config/v1";
|
|
14
14
|
*
|
|
15
15
|
* export default defineConfig({
|
|
16
16
|
* auth: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-config.js","names":[],"sources":["../../src/lib/define-config.ts"],"sourcesContent":["import { parseBranchTtl } from \"./duration.js\";\nimport { ConfigValidationError } from \"./errors.js\";\nimport {\n\tbranchTuningSchema,\n\tconfigInputSchema,\n\tformatZodIssues,\n} from \"./schema.js\";\nimport type {\n\tBranchTarget,\n\tBranchTuning,\n\tBranchTuningFn,\n\tBucketDef,\n\tConfig,\n\tDataApiInput,\n\tDataApiSettings,\n\tFunctionDef,\n\tFunctionTuning,\n\tPreviewInput,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceEnabled,\n\tServiceToggleInput,\n} from \"./types.js\";\n\n/** Default deploy parameters applied to functions that omit them in `neon.ts`. */\nconst DEFAULT_FUNCTION_RUNTIME = \"nodejs24\" as const;\n\nconst REGION_PREFIX = /^(aws|azure|gcp)-/;\n\n/**\n * Whether a `dataApi` toggle is **enabled and verified by Neon Auth** at the type level: it is\n * on (see {@link ServiceEnabled}) and not the explicit `authProvider: \"external\"` variant\n * (so the default / `\"neon\"` provider). This is the case that requires top-level Neon Auth.\n */\ntype DataApiUsesNeonAuth<DataApi> = ServiceEnabled<DataApi> extends true\n\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t? false\n\t\t: true\n\t: false;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `dataApi` when a Neon-Auth Data\n * API is declared without Neon Auth enabled (see {@link DataApiField}). TypeScript prints the\n * offending value against this string literal — `Type 'true' is not assignable to type\n * '…requires `auth: true`…'` — which points straight at the fix, instead of the opaque\n * `Type 'true' is not assignable to type 'never'` an intersection guard produces.\n *\n * It documents **both** fixes: enabling Neon Auth (`auth: true`), and running the Data API\n * *without* Neon Auth by verifying a third-party IdP (`authProvider: 'external'` + `jwksUrl`).\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type NeonAuthRequiredHint =\n\t\"`dataApi` with Neon Auth (the default `authProvider: 'neon'`) requires Neon Auth, so add `auth: true`. To enable the Data API WITHOUT Neon Auth, verify a third-party IdP instead: `dataApi: { authProvider: 'external', jwksUrl: 'https://your-idp/.well-known/jwks.json' }`\";\n\n/**\n * Static cross-field guard for {@link defineConfig}, expressed as the **type of the `dataApi`\n * field** rather than an intersected requirement on `auth`.\n *\n * - A Neon-Auth Data API (`authProvider: \"neon\"`, the default) with top-level `auth` enabled,\n * or any external Data API: the field keeps its normal `DataApi & DataApiInput` type (the\n * `& DataApiInput` preserves member autocomplete; the `const DataApi` still types the\n * returned {@link Config}).\n * - A Neon-Auth Data API **without** `auth` enabled: the field's expected type collapses to\n * the {@link NeonAuthRequiredHint} message, so the author sees the rule (and the two fixes)\n * right on the `dataApi` value.\n *\n * The runtime `superRefine` in {@link configInputSchema} enforces the same invariant for\n * non-typed (plain-JS) callers, so the behavior is identical — only the type-level message\n * changes.\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type DataApiField<Auth, DataApi> =\n\tDataApiUsesNeonAuth<DataApi> extends true\n\t\t? ServiceEnabled<Auth> extends true\n\t\t\t? DataApi & DataApiInput\n\t\t\t: NeonAuthRequiredHint\n\t\t: DataApi & DataApiInput;\n\n/**\n * Autocomplete bridge for the nested `preview.functions` / `preview.buckets` slug objects.\n *\n * {@link PreviewInput} types those records with a string index signature\n * (`Record<string, FunctionDef>` / `Record<string, BucketDef>`). When `defineConfig` infers\n * `const Preview`, every authored slug becomes a **named** property on the inferred literal\n * (e.g. `{ hello: { name; source } }`), and a named property **shadows** the index signature\n * when the editor computes the contextual type of that slug's value — so the rest of\n * {@link FunctionDef} / {@link BucketDef} (`env`, `dev`, `access`, …) never surfaces as\n * completions inside `hello: { … }` / `uploads: { … }`.\n *\n * Re-declaring each inferred slug's value as `FunctionDef` / `BucketDef` (a *named* member, via\n * a mapped type over the already-inferred keys) puts those members back onto the contextual\n * type without going through an index signature, which restores autocomplete. Intersected with\n * `Preview & PreviewInput` it neither widens what is accepted (the values were already\n * `FunctionDef` / `BucketDef`) nor perturbs the inferred `const Preview` — so slug inference for\n * `BranchTuningFn<Preview>` and the returned {@link Config} is unchanged.\n */\ntype PreviewAutocomplete<Preview> = (Preview extends { functions: infer F }\n\t? { functions: { [Slug in keyof F]: FunctionDef } }\n\t: unknown) &\n\t(Preview extends { buckets: infer B }\n\t\t? { buckets: { [Name in keyof B]: BucketDef } }\n\t\t: unknown);\n\n/**\n * Validate and freeze a Neon Platform branch policy.\n *\n * Used at the top of `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neondatabase/config/v1\";\n *\n * export default defineConfig({\n * auth: true,\n * preview: {\n * functions: {\n * hello: { name: \"Hello\", source: \"./functions/hello.ts\", dev: { port: 8787 } },\n * },\n * },\n * branch: (branch) => ({ protected: branch.name === \"main\" }),\n * });\n * ```\n *\n * The policy is split into a **static** existential set (top-level `auth` / `dataApi`\n * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The\n * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`\n * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function\n * deploy settings), never change what exists.\n *\n * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch\n * being decided for (not a live handle); switch on its facts (`branch.name`,\n * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two\n * modes: against an existing branch (fields populated from Neon) and during pre-create\n * evaluation (`exists: false`, `id` undefined).\n *\n * Pure: no I/O, no side effects. The static parts are validated here; the closure's output\n * is validated every time it is evaluated so errors point at the concrete branch target.\n */\nexport function defineConfig<\n\tconst Auth extends ServiceToggleInput | undefined = undefined,\n\tconst DataApi extends DataApiInput | undefined = undefined,\n\tconst Preview extends PreviewInput | undefined = undefined,\n>(input: {\n\t// Each field is intersected with its concrete interface (not just typed as the bare\n\t// generic). The generic alone — e.g. `preview?: Preview` — gives editors no members to\n\t// complete against in the object-literal position (they see `{} | undefined`), so you\n\t// lose hints for `aiGateway` / `functions` / `buckets`. `& PreviewInput` restores the\n\t// full shape for autocomplete while still inferring the `const` literal that types the\n\t// `branch` closure's slugs (BranchTuningFn<Preview>) and the returned Config.\n\tauth?: Auth & ServiceToggleInput;\n\t// The `dataApi` field carries the Neon-Auth cross-field guard at the type level (see\n\t// `DataApiField`): a Neon-Auth Data API without `auth` enabled surfaces a readable hint\n\t// as the field's expected type instead of collapsing the value to `never`.\n\tdataApi?: DataApiField<Auth, DataApi>;\n\t// `& PreviewInput` restores top-level member hints (aiGateway/functions/buckets);\n\t// `& PreviewAutocomplete<Preview>` restores hints *inside* each function/bucket slug\n\t// object (see `PreviewAutocomplete`), which the bare index signature otherwise hides.\n\tpreview?: Preview & PreviewInput & PreviewAutocomplete<Preview>;\n\tbranch?: BranchTuningFn<Preview>;\n}): Config<Auth, DataApi, Preview> {\n\tif (typeof input === \"function\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig now expects an object, not a function: `export default defineConfig({ auth: true, preview: { … }, branch: (branch) => ({ … }) })`.\",\n\t\t\t\"The static services/preview set moved to the top level; per-branch logic moved into the `branch` closure.\",\n\t\t]);\n\t}\n\tif (input === null || typeof input !== \"object\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig expects a configuration object: `export default defineConfig({ … })`.\",\n\t\t]);\n\t}\n\n\tconst parsed = configInputSchema.safeParse(input);\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\n\treturn Object.freeze({ ...input }) as Config<Auth, DataApi, Preview>;\n}\n\n/**\n * Evaluate a branch policy for a specific branch target and return a normalized config.\n *\n * Merges the static existential set (services + preview functions/buckets) with the\n * per-branch tuning returned by the `branch` closure into the same {@link\n * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.\n */\nexport function resolveConfig(\n\tconfig: Config,\n\tbranch: BranchTarget,\n): ResolvedBranchConfig {\n\tconst tuning = evaluateBranchTuning(config.branch, branch);\n\n\tconst resolved: ResolvedBranchConfig = {\n\t\tauthEnabled: isServiceEnabled(config.auth),\n\t\tdataApiEnabled: isDataApiEnabled(config.dataApi),\n\t};\n\tconst dataApi = resolveDataApi(config.dataApi);\n\tif (dataApi) resolved.dataApi = dataApi;\n\tif (tuning.parent !== undefined) resolved.parent = tuning.parent;\n\tif (tuning.ttl !== undefined) {\n\t\t// `branchTuningSchema` already validated `ttl` with the same `parseBranchTtl`, so\n\t\t// this only converts the validated value to seconds — it cannot fail here.\n\t\tconst parsedTtl = parseBranchTtl(tuning.ttl);\n\t\tif (!(\"error\" in parsedTtl)) resolved.ttlSeconds = parsedTtl.seconds;\n\t}\n\tif (tuning.protected !== undefined) resolved.protected = tuning.protected;\n\tif (tuning.postgres?.computeSettings) {\n\t\tresolved.postgres = {\n\t\t\tcomputeSettings: { ...tuning.postgres.computeSettings },\n\t\t};\n\t}\n\n\tconst preview = resolvePreviewConfig(config.preview, tuning);\n\tif (preview) resolved.preview = preview;\n\n\treturn resolved;\n}\n\n/**\n * Run the `branch` closure (when present) for the target and validate its output. The\n * closure is optional — a fully static policy resolves with empty tuning.\n */\nfunction evaluateBranchTuning(\n\tbranchFn: BranchTuningFn | undefined,\n\ttarget: BranchTarget,\n): BranchTuning {\n\tif (!branchFn) return {};\n\tlet raw: unknown;\n\ttry {\n\t\traw = branchFn(Object.freeze({ ...target }));\n\t} catch (cause) {\n\t\tthrow new ConfigValidationError([\n\t\t\t`Branch policy threw while evaluating branch \"${target.name}\".`,\n\t\t\t(cause as Error)?.message ?? String(cause),\n\t\t]);\n\t}\n\tconst parsed = branchTuningSchema.safeParse(raw ?? {});\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\treturn parsed.data as BranchTuning;\n}\n\nfunction isServiceEnabled(toggle: ServiceToggleInput | undefined): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/** Whether a {@link DataApiInput} is enabled (present object/`true` unless `enabled: false`). */\nfunction isDataApiEnabled(input: DataApiInput | undefined): boolean {\n\tif (input === undefined) return false;\n\tif (typeof input === \"boolean\") return input;\n\treturn input.enabled !== false;\n}\n\n/**\n * Normalize a {@link DataApiInput} into a {@link ResolvedDataApiConfig}, or `undefined` when\n * the Data API is not enabled. `authProvider` defaults to `\"neon\"`; the external-IdP wiring\n * is carried through only for the `\"external\"` provider; `settings` is copied with its\n * `undefined` entries dropped so diffing only considers fields the policy actually set.\n */\nfunction resolveDataApi(\n\tinput: DataApiInput | undefined,\n): ResolvedDataApiConfig | undefined {\n\tif (!isDataApiEnabled(input)) return undefined;\n\tif (typeof input !== \"object\") {\n\t\t// Bare `true`: enabled with Neon Auth and all-default settings.\n\t\treturn { authProvider: \"neon\" };\n\t}\n\tconst authProvider = input.authProvider ?? \"neon\";\n\tconst resolved: ResolvedDataApiConfig = { authProvider };\n\tif (authProvider === \"external\") {\n\t\tif (input.jwksUrl !== undefined) resolved.jwksUrl = input.jwksUrl;\n\t\tif (input.providerName !== undefined)\n\t\t\tresolved.providerName = input.providerName;\n\t\tif (input.jwtAudience !== undefined)\n\t\t\tresolved.jwtAudience = input.jwtAudience;\n\t}\n\tconst settings = normalizeDataApiSettings(input.settings);\n\tif (settings) resolved.settings = settings;\n\treturn resolved;\n}\n\n/** Copy a {@link DataApiSettings}, dropping `undefined` entries; `undefined` when empty. */\nfunction normalizeDataApiSettings(\n\tsettings: DataApiSettings | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\t(out as Record<string, unknown>)[key] = value;\n\t\t}\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Normalize the static {@link PreviewInput} (merged with per-branch function tuning) into a\n * {@link ResolvedPreviewConfig}. Returns `undefined` when the policy declares no `preview`\n * block so the field can be omitted entirely. Function slugs / bucket names come from the\n * record keys.\n */\nfunction resolvePreviewConfig(\n\tpreview: PreviewInput | undefined,\n\ttuning: BranchTuning,\n): ResolvedPreviewConfig | undefined {\n\tif (!preview) return undefined;\n\tconst fnTuning = tuning.preview?.functions ?? {};\n\tconst functions: ResolvedFunctionConfig[] = Object.entries(\n\t\tpreview.functions ?? {},\n\t).map(([slug, def]) =>\n\t\tresolveFunctionConfig(slug, def, fnTuning[slug] ?? {}),\n\t);\n\tconst buckets = Object.entries(preview.buckets ?? {}).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\taccess: def.access ?? \"private\",\n\t\t}),\n\t);\n\treturn {\n\t\tfunctions,\n\t\tbuckets,\n\t\taiGatewayEnabled: isServiceEnabled(preview.aiGateway),\n\t};\n}\n\nfunction resolveFunctionConfig(\n\tslug: string,\n\tdef: FunctionDef,\n\ttuning: FunctionTuning,\n): ResolvedFunctionConfig {\n\treturn {\n\t\tslug,\n\t\tname: def.name,\n\t\tsource: def.source,\n\t\tenv: { ...(def.env ?? {}) },\n\t\truntime: tuning.runtime ?? DEFAULT_FUNCTION_RUNTIME,\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t};\n}\n\n/**\n * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes\n * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the\n * NeonApi adapter.\n */\nexport function normalizeRegion(region: string): string {\n\tif (REGION_PREFIX.test(region)) return region;\n\treturn `aws-${region}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"define-config.js","names":[],"sources":["../../src/lib/define-config.ts"],"sourcesContent":["import { parseBranchTtl } from \"./duration.js\";\nimport { ConfigValidationError } from \"./errors.js\";\nimport {\n\tbranchTuningSchema,\n\tconfigInputSchema,\n\tformatZodIssues,\n} from \"./schema.js\";\nimport type {\n\tBranchTarget,\n\tBranchTuning,\n\tBranchTuningFn,\n\tBucketDef,\n\tConfig,\n\tDataApiInput,\n\tDataApiSettings,\n\tFunctionDef,\n\tFunctionTuning,\n\tPreviewInput,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceEnabled,\n\tServiceToggleInput,\n} from \"./types.js\";\n\n/** Default deploy parameters applied to functions that omit them in `neon.ts`. */\nconst DEFAULT_FUNCTION_RUNTIME = \"nodejs24\" as const;\n\nconst REGION_PREFIX = /^(aws|azure|gcp)-/;\n\n/**\n * Whether a `dataApi` toggle is **enabled and verified by Neon Auth** at the type level: it is\n * on (see {@link ServiceEnabled}) and not the explicit `authProvider: \"external\"` variant\n * (so the default / `\"neon\"` provider). This is the case that requires top-level Neon Auth.\n */\ntype DataApiUsesNeonAuth<DataApi> = ServiceEnabled<DataApi> extends true\n\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t? false\n\t\t: true\n\t: false;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `dataApi` when a Neon-Auth Data\n * API is declared without Neon Auth enabled (see {@link DataApiField}). TypeScript prints the\n * offending value against this string literal — `Type 'true' is not assignable to type\n * '…requires `auth: true`…'` — which points straight at the fix, instead of the opaque\n * `Type 'true' is not assignable to type 'never'` an intersection guard produces.\n *\n * It documents **both** fixes: enabling Neon Auth (`auth: true`), and running the Data API\n * *without* Neon Auth by verifying a third-party IdP (`authProvider: 'external'` + `jwksUrl`).\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type NeonAuthRequiredHint =\n\t\"`dataApi` with Neon Auth (the default `authProvider: 'neon'`) requires Neon Auth, so add `auth: true`. To enable the Data API WITHOUT Neon Auth, verify a third-party IdP instead: `dataApi: { authProvider: 'external', jwksUrl: 'https://your-idp/.well-known/jwks.json' }`\";\n\n/**\n * Static cross-field guard for {@link defineConfig}, expressed as the **type of the `dataApi`\n * field** rather than an intersected requirement on `auth`.\n *\n * - A Neon-Auth Data API (`authProvider: \"neon\"`, the default) with top-level `auth` enabled,\n * or any external Data API: the field keeps its normal `DataApi & DataApiInput` type (the\n * `& DataApiInput` preserves member autocomplete; the `const DataApi` still types the\n * returned {@link Config}).\n * - A Neon-Auth Data API **without** `auth` enabled: the field's expected type collapses to\n * the {@link NeonAuthRequiredHint} message, so the author sees the rule (and the two fixes)\n * right on the `dataApi` value.\n *\n * The runtime `superRefine` in {@link configInputSchema} enforces the same invariant for\n * non-typed (plain-JS) callers, so the behavior is identical — only the type-level message\n * changes.\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type DataApiField<Auth, DataApi> =\n\tDataApiUsesNeonAuth<DataApi> extends true\n\t\t? ServiceEnabled<Auth> extends true\n\t\t\t? DataApi & DataApiInput\n\t\t\t: NeonAuthRequiredHint\n\t\t: DataApi & DataApiInput;\n\n/**\n * Autocomplete bridge for the nested `preview.functions` / `preview.buckets` slug objects.\n *\n * {@link PreviewInput} types those records with a string index signature\n * (`Record<string, FunctionDef>` / `Record<string, BucketDef>`). When `defineConfig` infers\n * `const Preview`, every authored slug becomes a **named** property on the inferred literal\n * (e.g. `{ hello: { name; source } }`), and a named property **shadows** the index signature\n * when the editor computes the contextual type of that slug's value — so the rest of\n * {@link FunctionDef} / {@link BucketDef} (`env`, `dev`, `access`, …) never surfaces as\n * completions inside `hello: { … }` / `uploads: { … }`.\n *\n * Re-declaring each inferred slug's value as `FunctionDef` / `BucketDef` (a *named* member, via\n * a mapped type over the already-inferred keys) puts those members back onto the contextual\n * type without going through an index signature, which restores autocomplete. Intersected with\n * `Preview & PreviewInput` it neither widens what is accepted (the values were already\n * `FunctionDef` / `BucketDef`) nor perturbs the inferred `const Preview` — so slug inference for\n * `BranchTuningFn<Preview>` and the returned {@link Config} is unchanged.\n */\ntype PreviewAutocomplete<Preview> = (Preview extends { functions: infer F }\n\t? { functions: { [Slug in keyof F]: FunctionDef } }\n\t: unknown) &\n\t(Preview extends { buckets: infer B }\n\t\t? { buckets: { [Name in keyof B]: BucketDef } }\n\t\t: unknown);\n\n/**\n * Validate and freeze a Neon Platform branch policy.\n *\n * Used at the top of `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neon/config/v1\";\n *\n * export default defineConfig({\n * auth: true,\n * preview: {\n * functions: {\n * hello: { name: \"Hello\", source: \"./functions/hello.ts\", dev: { port: 8787 } },\n * },\n * },\n * branch: (branch) => ({ protected: branch.name === \"main\" }),\n * });\n * ```\n *\n * The policy is split into a **static** existential set (top-level `auth` / `dataApi`\n * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The\n * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`\n * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function\n * deploy settings), never change what exists.\n *\n * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch\n * being decided for (not a live handle); switch on its facts (`branch.name`,\n * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two\n * modes: against an existing branch (fields populated from Neon) and during pre-create\n * evaluation (`exists: false`, `id` undefined).\n *\n * Pure: no I/O, no side effects. The static parts are validated here; the closure's output\n * is validated every time it is evaluated so errors point at the concrete branch target.\n */\nexport function defineConfig<\n\tconst Auth extends ServiceToggleInput | undefined = undefined,\n\tconst DataApi extends DataApiInput | undefined = undefined,\n\tconst Preview extends PreviewInput | undefined = undefined,\n>(input: {\n\t// Each field is intersected with its concrete interface (not just typed as the bare\n\t// generic). The generic alone — e.g. `preview?: Preview` — gives editors no members to\n\t// complete against in the object-literal position (they see `{} | undefined`), so you\n\t// lose hints for `aiGateway` / `functions` / `buckets`. `& PreviewInput` restores the\n\t// full shape for autocomplete while still inferring the `const` literal that types the\n\t// `branch` closure's slugs (BranchTuningFn<Preview>) and the returned Config.\n\tauth?: Auth & ServiceToggleInput;\n\t// The `dataApi` field carries the Neon-Auth cross-field guard at the type level (see\n\t// `DataApiField`): a Neon-Auth Data API without `auth` enabled surfaces a readable hint\n\t// as the field's expected type instead of collapsing the value to `never`.\n\tdataApi?: DataApiField<Auth, DataApi>;\n\t// `& PreviewInput` restores top-level member hints (aiGateway/functions/buckets);\n\t// `& PreviewAutocomplete<Preview>` restores hints *inside* each function/bucket slug\n\t// object (see `PreviewAutocomplete`), which the bare index signature otherwise hides.\n\tpreview?: Preview & PreviewInput & PreviewAutocomplete<Preview>;\n\tbranch?: BranchTuningFn<Preview>;\n}): Config<Auth, DataApi, Preview> {\n\tif (typeof input === \"function\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig now expects an object, not a function: `export default defineConfig({ auth: true, preview: { … }, branch: (branch) => ({ … }) })`.\",\n\t\t\t\"The static services/preview set moved to the top level; per-branch logic moved into the `branch` closure.\",\n\t\t]);\n\t}\n\tif (input === null || typeof input !== \"object\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig expects a configuration object: `export default defineConfig({ … })`.\",\n\t\t]);\n\t}\n\n\tconst parsed = configInputSchema.safeParse(input);\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\n\treturn Object.freeze({ ...input }) as Config<Auth, DataApi, Preview>;\n}\n\n/**\n * Evaluate a branch policy for a specific branch target and return a normalized config.\n *\n * Merges the static existential set (services + preview functions/buckets) with the\n * per-branch tuning returned by the `branch` closure into the same {@link\n * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.\n */\nexport function resolveConfig(\n\tconfig: Config,\n\tbranch: BranchTarget,\n): ResolvedBranchConfig {\n\tconst tuning = evaluateBranchTuning(config.branch, branch);\n\n\tconst resolved: ResolvedBranchConfig = {\n\t\tauthEnabled: isServiceEnabled(config.auth),\n\t\tdataApiEnabled: isDataApiEnabled(config.dataApi),\n\t};\n\tconst dataApi = resolveDataApi(config.dataApi);\n\tif (dataApi) resolved.dataApi = dataApi;\n\tif (tuning.parent !== undefined) resolved.parent = tuning.parent;\n\tif (tuning.ttl !== undefined) {\n\t\t// `branchTuningSchema` already validated `ttl` with the same `parseBranchTtl`, so\n\t\t// this only converts the validated value to seconds — it cannot fail here.\n\t\tconst parsedTtl = parseBranchTtl(tuning.ttl);\n\t\tif (!(\"error\" in parsedTtl)) resolved.ttlSeconds = parsedTtl.seconds;\n\t}\n\tif (tuning.protected !== undefined) resolved.protected = tuning.protected;\n\tif (tuning.postgres?.computeSettings) {\n\t\tresolved.postgres = {\n\t\t\tcomputeSettings: { ...tuning.postgres.computeSettings },\n\t\t};\n\t}\n\n\tconst preview = resolvePreviewConfig(config.preview, tuning);\n\tif (preview) resolved.preview = preview;\n\n\treturn resolved;\n}\n\n/**\n * Run the `branch` closure (when present) for the target and validate its output. The\n * closure is optional — a fully static policy resolves with empty tuning.\n */\nfunction evaluateBranchTuning(\n\tbranchFn: BranchTuningFn | undefined,\n\ttarget: BranchTarget,\n): BranchTuning {\n\tif (!branchFn) return {};\n\tlet raw: unknown;\n\ttry {\n\t\traw = branchFn(Object.freeze({ ...target }));\n\t} catch (cause) {\n\t\tthrow new ConfigValidationError([\n\t\t\t`Branch policy threw while evaluating branch \"${target.name}\".`,\n\t\t\t(cause as Error)?.message ?? String(cause),\n\t\t]);\n\t}\n\tconst parsed = branchTuningSchema.safeParse(raw ?? {});\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\treturn parsed.data as BranchTuning;\n}\n\nfunction isServiceEnabled(toggle: ServiceToggleInput | undefined): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/** Whether a {@link DataApiInput} is enabled (present object/`true` unless `enabled: false`). */\nfunction isDataApiEnabled(input: DataApiInput | undefined): boolean {\n\tif (input === undefined) return false;\n\tif (typeof input === \"boolean\") return input;\n\treturn input.enabled !== false;\n}\n\n/**\n * Normalize a {@link DataApiInput} into a {@link ResolvedDataApiConfig}, or `undefined` when\n * the Data API is not enabled. `authProvider` defaults to `\"neon\"`; the external-IdP wiring\n * is carried through only for the `\"external\"` provider; `settings` is copied with its\n * `undefined` entries dropped so diffing only considers fields the policy actually set.\n */\nfunction resolveDataApi(\n\tinput: DataApiInput | undefined,\n): ResolvedDataApiConfig | undefined {\n\tif (!isDataApiEnabled(input)) return undefined;\n\tif (typeof input !== \"object\") {\n\t\t// Bare `true`: enabled with Neon Auth and all-default settings.\n\t\treturn { authProvider: \"neon\" };\n\t}\n\tconst authProvider = input.authProvider ?? \"neon\";\n\tconst resolved: ResolvedDataApiConfig = { authProvider };\n\tif (authProvider === \"external\") {\n\t\tif (input.jwksUrl !== undefined) resolved.jwksUrl = input.jwksUrl;\n\t\tif (input.providerName !== undefined)\n\t\t\tresolved.providerName = input.providerName;\n\t\tif (input.jwtAudience !== undefined)\n\t\t\tresolved.jwtAudience = input.jwtAudience;\n\t}\n\tconst settings = normalizeDataApiSettings(input.settings);\n\tif (settings) resolved.settings = settings;\n\treturn resolved;\n}\n\n/** Copy a {@link DataApiSettings}, dropping `undefined` entries; `undefined` when empty. */\nfunction normalizeDataApiSettings(\n\tsettings: DataApiSettings | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\t(out as Record<string, unknown>)[key] = value;\n\t\t}\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Normalize the static {@link PreviewInput} (merged with per-branch function tuning) into a\n * {@link ResolvedPreviewConfig}. Returns `undefined` when the policy declares no `preview`\n * block so the field can be omitted entirely. Function slugs / bucket names come from the\n * record keys.\n */\nfunction resolvePreviewConfig(\n\tpreview: PreviewInput | undefined,\n\ttuning: BranchTuning,\n): ResolvedPreviewConfig | undefined {\n\tif (!preview) return undefined;\n\tconst fnTuning = tuning.preview?.functions ?? {};\n\tconst functions: ResolvedFunctionConfig[] = Object.entries(\n\t\tpreview.functions ?? {},\n\t).map(([slug, def]) =>\n\t\tresolveFunctionConfig(slug, def, fnTuning[slug] ?? {}),\n\t);\n\tconst buckets = Object.entries(preview.buckets ?? {}).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\taccess: def.access ?? \"private\",\n\t\t}),\n\t);\n\treturn {\n\t\tfunctions,\n\t\tbuckets,\n\t\taiGatewayEnabled: isServiceEnabled(preview.aiGateway),\n\t};\n}\n\nfunction resolveFunctionConfig(\n\tslug: string,\n\tdef: FunctionDef,\n\ttuning: FunctionTuning,\n): ResolvedFunctionConfig {\n\treturn {\n\t\tslug,\n\t\tname: def.name,\n\t\tsource: def.source,\n\t\tenv: { ...(def.env ?? {}) },\n\t\truntime: tuning.runtime ?? DEFAULT_FUNCTION_RUNTIME,\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t};\n}\n\n/**\n * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes\n * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the\n * NeonApi adapter.\n */\nexport function normalizeRegion(region: string): string {\n\tif (REGION_PREFIX.test(region)) return region;\n\treturn `aws-${region}`;\n}\n"],"mappings":"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"}
|
package/dist/lib/diff.js
CHANGED
|
@@ -38,7 +38,7 @@ function diffConfig(config, remote, options) {
|
|
|
38
38
|
* The AI Gateway is intentionally NOT planned here: it is always available on a branch
|
|
39
39
|
* (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan
|
|
40
40
|
* step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway
|
|
41
|
-
* env vars (`@
|
|
41
|
+
* env vars (`@neon/env`). There is nothing to create, and nothing to probe.
|
|
42
42
|
*
|
|
43
43
|
* Functions are always (re-)deployed: deployments are versioned and the newest becomes
|
|
44
44
|
* active, so each push ships the current source. There is no separate create step — Neon
|
package/dist/lib/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","names":[],"sources":["../../src/lib/diff.ts"],"sourcesContent":["import type {\n\tEnableDataApiInput,\n\tNeonBranchSnapshot,\n\tNeonBucketSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n} from \"./types.js\";\n\n/**\n * A planned action to perform a single mutation against the Neon API. The diff engine\n * produces a list of these for `pushConfig` to execute (or report).\n */\nexport type PlanStep =\n\t| {\n\t\t\tkind: \"update-branch-ttl\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\texpiresAt: string | null;\n\t }\n\t| {\n\t\t\tkind: \"update-branch-protected\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tprotected: boolean;\n\t }\n\t| {\n\t\t\tkind: \"update-endpoint\";\n\t\t\tprojectId: string;\n\t\t\tbranchName: string;\n\t\t\tendpointId: string;\n\t\t\tsettings: ComputeSettings;\n\t }\n\t| {\n\t\t\tkind: \"enable-auth\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName?: string;\n\t }\n\t| {\n\t\t\tkind: \"enable-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\t/** Create-time auth wiring + initial settings from the policy. */\n\t\t\tinput?: EnableDataApiInput;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Reconcile the runtime settings of an already-enabled Data API integration.\n\t\t\t * Only `settings` are mutable post-create, so this is the lone Data API\n\t\t\t * *update* step — and it is an override (requires `updateExisting`).\n\t\t\t */\n\t\t\tkind: \"update-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\tsettings: DataApiSettings;\n\t }\n\t| {\n\t\t\tkind: \"create-bucket\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tbucketName: string;\n\t\t\taccessLevel: BucketAccessLevel;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Deploy code to a function. Planned for every desired function — deployments are\n\t\t\t * versioned and the newest becomes active, so a push ships the current source each\n\t\t\t * time. Neon has no separate \"create function\" endpoint: the first deployment to a\n\t\t\t * slug creates the function. `functionExists` therefore only drives whether this\n\t\t\t * surfaces as a `create` (first deploy) or an `update` (re-deploy).\n\t\t\t */\n\t\t\tkind: \"deploy-function\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfn: ResolvedFunctionConfig;\n\t\t\t/** Whether the function already existed remotely when the plan was computed. */\n\t\t\tfunctionExists: boolean;\n\t };\n\nexport interface RemoteServiceState {\n\tdatabaseName: string;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Current Data API runtime settings, when the integration is enabled and the API reports\n\t * them (SubZero only). `null`/absent means \"not reported\" — settings drift can't be\n\t * computed, so no update step is planned.\n\t */\n\tdataApiSettings?: DataApiSettings | null;\n}\n\n/**\n * Snapshot of the branch's current Preview-feature state. Absent (`undefined`) when the\n * policy has no `preview` block — `pushConfig` only fetches this when needed.\n */\nexport interface RemotePreviewState {\n\tbuckets: NeonBucketSnapshot[];\n\tfunctions: NeonFunctionSnapshot[];\n}\n\nexport interface RemoteState {\n\tprojectId: string;\n\tbranch: NeonBranchSnapshot;\n\tendpoint?: NeonEndpointSnapshot;\n\tservices: RemoteServiceState;\n\tpreview?: RemotePreviewState;\n}\n\nexport interface DiffOptions {\n\t/**\n\t * Apply mutable drift on the selected branch as plan steps instead of conflicts.\n\t * Default: `false`.\n\t */\n\tupdateExisting: boolean;\n}\n\nexport interface DiffResult {\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\n/**\n * Diff desired branch policy against the selected remote branch. Pure function.\n */\nexport function diffConfig(\n\tconfig: ResolvedBranchConfig,\n\tremote: RemoteState,\n\toptions: DiffOptions,\n): DiffResult {\n\tconst conflicts: ConflictReport[] = [];\n\tconst plan: PlanStep[] = [];\n\tdiffBranchConfig({ config, remote, options, plan, conflicts });\n\tdiffServices({ config, remote, options, plan, conflicts });\n\tdiffPreview({ config, remote, plan });\n\treturn { plan, conflicts };\n}\n\n/**\n * Plan Preview features (functions, buckets). Like {@link diffServices}, this is\n * **additive**: it creates desired buckets and (re-)deploys functions, but never deletes\n * them. Teardown is destructive, so it stays explicit/manual — matching the existing\n * auth / dataApi behaviour.\n *\n * The AI Gateway is intentionally NOT planned here: it is always available on a branch\n * (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan\n * step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway\n * env vars (`@neondatabase/env`). There is nothing to create, and nothing to probe.\n *\n * Functions are always (re-)deployed: deployments are versioned and the newest becomes\n * active, so each push ships the current source. There is no separate create step — Neon\n * creates the function on its first deployment — so a single `deploy-function` step is\n * emitted per desired function, carrying `functionExists` so the apply can report it as a\n * create (first deploy) or an update (re-deploy).\n */\nfunction diffPreview(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\tplan: PlanStep[];\n}): void {\n\tconst { config, remote, plan } = args;\n\tconst preview = config.preview;\n\tif (!preview) return;\n\t// `remote.preview` is only fetched when the policy has a preview block; treat a missing\n\t// snapshot as \"nothing exists yet\" so the diff is still well-defined.\n\tconst state: RemotePreviewState = remote.preview ?? {\n\t\tbuckets: [],\n\t\tfunctions: [],\n\t};\n\n\tfor (const bucket of preview.buckets) {\n\t\tif (state.buckets.some((b) => b.name === bucket.name)) continue;\n\t\tplan.push({\n\t\t\tkind: \"create-bucket\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tbucketName: bucket.name,\n\t\t\taccessLevel: bucket.access,\n\t\t});\n\t}\n\n\tfor (const fn of preview.functions) {\n\t\tconst exists = state.functions.some((f) => f.slug === fn.slug);\n\t\t// Neon creates the function on its first deployment (there is no separate create\n\t\t// endpoint), so always emit a single deploy step and let `functionExists` decide\n\t\t// whether it is reported as a create or an update.\n\t\tplan.push({\n\t\t\tkind: \"deploy-function\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tfn,\n\t\t\tfunctionExists: exists,\n\t\t});\n\t}\n}\n\n/**\n * Plan branch-scoped integrations. Enabling is additive (no existing resource to override).\n * The Data API is the one integration that also has a reconcilable *update*: its runtime\n * `settings` can drift once enabled, and reconciling them is an override (gated on\n * `updateExisting`, like compute/TTL/protected). The auth provider / JWKS wiring is fixed at\n * enable time, so it is never updated here. Disabling stays explicit/manual (destructive).\n */\nfunction diffServices(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tif (config.authEnabled && !state.authEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-auth\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t};\n\t\tif (state.databaseName) step.databaseName = state.databaseName;\n\t\tplan.push(step);\n\t}\n\tif (config.dataApiEnabled) {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t}\n}\n\n/**\n * Plan the Data API: a first-time **enable** (carrying the create-time auth wiring +\n * settings), or — when it already exists — a **settings update** if the policy's settings\n * drift from the remote. The update is an override: applied as a plan step under\n * `updateExisting`, otherwise reported as a conflict.\n */\nfunction diffDataApi(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tconst desired = config.dataApi;\n\n\tif (!state.dataApiEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t};\n\t\tconst input = desired ? enableInputFromResolved(desired) : undefined;\n\t\tif (input) step.input = input;\n\t\tplan.push(step);\n\t\treturn;\n\t}\n\n\t// Already enabled: the only reconcilable change is its runtime settings.\n\tconst desiredSettings = desired?.settings;\n\tif (!desiredSettings) return;\n\tif (!dataApiSettingsDiffer(desiredSettings, state.dataApiSettings)) return;\n\n\tif (options.updateExisting) {\n\t\tplan.push({\n\t\t\tkind: \"update-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t\tsettings: desiredSettings,\n\t\t});\n\t} else {\n\t\tconflicts.push({\n\t\t\tkind: \"branch\",\n\t\t\tidentifier: remote.branch.name,\n\t\t\tfield: \"dataApi.settings\",\n\t\t\tcurrent: state.dataApiSettings ?? undefined,\n\t\t\tdesired: desiredSettings,\n\t\t\treason: \"Existing Data API has different settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t});\n\t}\n}\n\n/** Build the create-time {@link EnableDataApiInput} from a resolved Data API config. */\nfunction enableInputFromResolved(\n\tresolved: ResolvedDataApiConfig,\n): EnableDataApiInput {\n\tconst input: EnableDataApiInput = { authProvider: resolved.authProvider };\n\tif (resolved.jwksUrl !== undefined) input.jwksUrl = resolved.jwksUrl;\n\tif (resolved.providerName !== undefined)\n\t\tinput.providerName = resolved.providerName;\n\tif (resolved.jwtAudience !== undefined)\n\t\tinput.jwtAudience = resolved.jwtAudience;\n\tif (resolved.settings) input.settings = resolved.settings;\n\treturn input;\n}\n\n/** The camelCase keys of {@link DataApiSettings}, used to compare desired vs remote settings. */\nconst DATA_API_SETTING_KEYS = [\n\t\"dbAggregatesEnabled\",\n\t\"dbAnonRole\",\n\t\"dbExtraSearchPath\",\n\t\"dbMaxRows\",\n\t\"dbSchemas\",\n\t\"jwtRoleClaimKey\",\n\t\"jwtCacheMaxLifetime\",\n\t\"openapiMode\",\n\t\"serverCorsAllowedOrigins\",\n\t\"serverTimingEnabled\",\n] as const satisfies ReadonlyArray<keyof DataApiSettings>;\n\n/**\n * Whether the policy's Data API `settings` differ from the remote ones. Only the keys the\n * policy actually set are compared (so unset fields never count as drift). When the remote\n * settings are not reported (`null`/absent — non-SubZero), drift can't be computed and this\n * returns `false` so no spurious update is planned.\n */\nfunction dataApiSettingsDiffer(\n\tdesired: DataApiSettings,\n\tcurrent: DataApiSettings | null | undefined,\n): boolean {\n\tif (!current) return false;\n\tfor (const key of DATA_API_SETTING_KEYS) {\n\t\tif (desired[key] === undefined) continue;\n\t\tif (JSON.stringify(desired[key]) !== JSON.stringify(current[key])) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface BranchConfigArgs {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\nfunction diffBranchConfig(args: BranchConfigArgs): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst branchName = remote.branch.name;\n\tconst computeSettings = config.postgres?.computeSettings;\n\n\tif (computeSettings) {\n\t\tconst endpoint = remote.endpoint;\n\t\tif (!endpoint) {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"endpoint\",\n\t\t\t\tcurrent: undefined,\n\t\t\t\tdesired: computeSettings,\n\t\t\t\treason: \"Branch has no read-write endpoint; cannot apply compute settings.\",\n\t\t\t});\n\t\t} else {\n\t\t\tconst drift = computeDriftBetween(computeSettings, endpoint);\n\t\t\tif (drift) {\n\t\t\t\tif (options.updateExisting) {\n\t\t\t\t\tplan.push({\n\t\t\t\t\t\tkind: \"update-endpoint\",\n\t\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\t\tbranchName,\n\t\t\t\t\t\tendpointId: endpoint.id,\n\t\t\t\t\t\tsettings: computeSettings,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\t\tcurrent: drift.current,\n\t\t\t\t\t\tdesired: drift.desired,\n\t\t\t\t\t\treason: \"Existing branch has different compute settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (\n\t\tconfig.protected !== undefined &&\n\t\tconfig.protected !== remote.branch.protected\n\t) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"update-branch-protected\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName,\n\t\t\t\tprotected: config.protected,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"protected\",\n\t\t\t\tcurrent: remote.branch.protected,\n\t\t\t\tdesired: config.protected,\n\t\t\t\treason: \"Existing branch has a different `protected` flag. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (config.ttlSeconds !== undefined) {\n\t\tconst current = remote.branch.expiresAt\n\t\t\t? Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.round(\n\t\t\t\t\t\t(Date.parse(remote.branch.expiresAt) - Date.now()) /\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tif (\n\t\t\tcurrent === undefined ||\n\t\t\tMath.abs(current - config.ttlSeconds) > 30\n\t\t) {\n\t\t\tconst expiresAt = new Date(\n\t\t\t\tDate.now() + config.ttlSeconds * 1000,\n\t\t\t).toISOString();\n\t\t\tif (options.updateExisting) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-branch-ttl\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName,\n\t\t\t\t\texpiresAt,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconflicts.push({\n\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\tfield: \"ttl\",\n\t\t\t\t\tcurrent: remote.branch.expiresAt,\n\t\t\t\t\tdesired: expiresAt,\n\t\t\t\t\treason: \"Existing branch has a different TTL. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction computeDriftBetween(\n\tdesired: ComputeSettings,\n\tendpoint: NeonEndpointSnapshot,\n): {\n\tcurrent: Partial<ComputeSettings>;\n\tdesired: Partial<ComputeSettings>;\n} | null {\n\tconst currentDrift: Partial<ComputeSettings> = {};\n\tconst desiredDrift: Partial<ComputeSettings> = {};\n\tlet drift = false;\n\n\tif (\n\t\tdesired.autoscalingLimitMinCu !== undefined &&\n\t\tdesired.autoscalingLimitMinCu !== endpoint.autoscalingLimitMinCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;\n\t\tdesiredDrift.autoscalingLimitMinCu = desired.autoscalingLimitMinCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.autoscalingLimitMaxCu !== undefined &&\n\t\tdesired.autoscalingLimitMaxCu !== endpoint.autoscalingLimitMaxCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;\n\t\tdesiredDrift.autoscalingLimitMaxCu = desired.autoscalingLimitMaxCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.suspendTimeout !== undefined &&\n\t\tdesired.suspendTimeout !== endpoint.suspendTimeout\n\t) {\n\t\tcurrentDrift.suspendTimeout = endpoint.suspendTimeout;\n\t\tdesiredDrift.suspendTimeout = desired.suspendTimeout;\n\t\tdrift = true;\n\t}\n\treturn drift ? { current: currentDrift, desired: desiredDrift } : null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diff.js","names":[],"sources":["../../src/lib/diff.ts"],"sourcesContent":["import type {\n\tEnableDataApiInput,\n\tNeonBranchSnapshot,\n\tNeonBucketSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n} from \"./types.js\";\n\n/**\n * A planned action to perform a single mutation against the Neon API. The diff engine\n * produces a list of these for `pushConfig` to execute (or report).\n */\nexport type PlanStep =\n\t| {\n\t\t\tkind: \"update-branch-ttl\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\texpiresAt: string | null;\n\t }\n\t| {\n\t\t\tkind: \"update-branch-protected\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tprotected: boolean;\n\t }\n\t| {\n\t\t\tkind: \"update-endpoint\";\n\t\t\tprojectId: string;\n\t\t\tbranchName: string;\n\t\t\tendpointId: string;\n\t\t\tsettings: ComputeSettings;\n\t }\n\t| {\n\t\t\tkind: \"enable-auth\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName?: string;\n\t }\n\t| {\n\t\t\tkind: \"enable-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\t/** Create-time auth wiring + initial settings from the policy. */\n\t\t\tinput?: EnableDataApiInput;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Reconcile the runtime settings of an already-enabled Data API integration.\n\t\t\t * Only `settings` are mutable post-create, so this is the lone Data API\n\t\t\t * *update* step — and it is an override (requires `updateExisting`).\n\t\t\t */\n\t\t\tkind: \"update-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\tsettings: DataApiSettings;\n\t }\n\t| {\n\t\t\tkind: \"create-bucket\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tbucketName: string;\n\t\t\taccessLevel: BucketAccessLevel;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Deploy code to a function. Planned for every desired function — deployments are\n\t\t\t * versioned and the newest becomes active, so a push ships the current source each\n\t\t\t * time. Neon has no separate \"create function\" endpoint: the first deployment to a\n\t\t\t * slug creates the function. `functionExists` therefore only drives whether this\n\t\t\t * surfaces as a `create` (first deploy) or an `update` (re-deploy).\n\t\t\t */\n\t\t\tkind: \"deploy-function\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfn: ResolvedFunctionConfig;\n\t\t\t/** Whether the function already existed remotely when the plan was computed. */\n\t\t\tfunctionExists: boolean;\n\t };\n\nexport interface RemoteServiceState {\n\tdatabaseName: string;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Current Data API runtime settings, when the integration is enabled and the API reports\n\t * them (SubZero only). `null`/absent means \"not reported\" — settings drift can't be\n\t * computed, so no update step is planned.\n\t */\n\tdataApiSettings?: DataApiSettings | null;\n}\n\n/**\n * Snapshot of the branch's current Preview-feature state. Absent (`undefined`) when the\n * policy has no `preview` block — `pushConfig` only fetches this when needed.\n */\nexport interface RemotePreviewState {\n\tbuckets: NeonBucketSnapshot[];\n\tfunctions: NeonFunctionSnapshot[];\n}\n\nexport interface RemoteState {\n\tprojectId: string;\n\tbranch: NeonBranchSnapshot;\n\tendpoint?: NeonEndpointSnapshot;\n\tservices: RemoteServiceState;\n\tpreview?: RemotePreviewState;\n}\n\nexport interface DiffOptions {\n\t/**\n\t * Apply mutable drift on the selected branch as plan steps instead of conflicts.\n\t * Default: `false`.\n\t */\n\tupdateExisting: boolean;\n}\n\nexport interface DiffResult {\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\n/**\n * Diff desired branch policy against the selected remote branch. Pure function.\n */\nexport function diffConfig(\n\tconfig: ResolvedBranchConfig,\n\tremote: RemoteState,\n\toptions: DiffOptions,\n): DiffResult {\n\tconst conflicts: ConflictReport[] = [];\n\tconst plan: PlanStep[] = [];\n\tdiffBranchConfig({ config, remote, options, plan, conflicts });\n\tdiffServices({ config, remote, options, plan, conflicts });\n\tdiffPreview({ config, remote, plan });\n\treturn { plan, conflicts };\n}\n\n/**\n * Plan Preview features (functions, buckets). Like {@link diffServices}, this is\n * **additive**: it creates desired buckets and (re-)deploys functions, but never deletes\n * them. Teardown is destructive, so it stays explicit/manual — matching the existing\n * auth / dataApi behaviour.\n *\n * The AI Gateway is intentionally NOT planned here: it is always available on a branch\n * (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan\n * step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway\n * env vars (`@neon/env`). There is nothing to create, and nothing to probe.\n *\n * Functions are always (re-)deployed: deployments are versioned and the newest becomes\n * active, so each push ships the current source. There is no separate create step — Neon\n * creates the function on its first deployment — so a single `deploy-function` step is\n * emitted per desired function, carrying `functionExists` so the apply can report it as a\n * create (first deploy) or an update (re-deploy).\n */\nfunction diffPreview(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\tplan: PlanStep[];\n}): void {\n\tconst { config, remote, plan } = args;\n\tconst preview = config.preview;\n\tif (!preview) return;\n\t// `remote.preview` is only fetched when the policy has a preview block; treat a missing\n\t// snapshot as \"nothing exists yet\" so the diff is still well-defined.\n\tconst state: RemotePreviewState = remote.preview ?? {\n\t\tbuckets: [],\n\t\tfunctions: [],\n\t};\n\n\tfor (const bucket of preview.buckets) {\n\t\tif (state.buckets.some((b) => b.name === bucket.name)) continue;\n\t\tplan.push({\n\t\t\tkind: \"create-bucket\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tbucketName: bucket.name,\n\t\t\taccessLevel: bucket.access,\n\t\t});\n\t}\n\n\tfor (const fn of preview.functions) {\n\t\tconst exists = state.functions.some((f) => f.slug === fn.slug);\n\t\t// Neon creates the function on its first deployment (there is no separate create\n\t\t// endpoint), so always emit a single deploy step and let `functionExists` decide\n\t\t// whether it is reported as a create or an update.\n\t\tplan.push({\n\t\t\tkind: \"deploy-function\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tfn,\n\t\t\tfunctionExists: exists,\n\t\t});\n\t}\n}\n\n/**\n * Plan branch-scoped integrations. Enabling is additive (no existing resource to override).\n * The Data API is the one integration that also has a reconcilable *update*: its runtime\n * `settings` can drift once enabled, and reconciling them is an override (gated on\n * `updateExisting`, like compute/TTL/protected). The auth provider / JWKS wiring is fixed at\n * enable time, so it is never updated here. Disabling stays explicit/manual (destructive).\n */\nfunction diffServices(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tif (config.authEnabled && !state.authEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-auth\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t};\n\t\tif (state.databaseName) step.databaseName = state.databaseName;\n\t\tplan.push(step);\n\t}\n\tif (config.dataApiEnabled) {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t}\n}\n\n/**\n * Plan the Data API: a first-time **enable** (carrying the create-time auth wiring +\n * settings), or — when it already exists — a **settings update** if the policy's settings\n * drift from the remote. The update is an override: applied as a plan step under\n * `updateExisting`, otherwise reported as a conflict.\n */\nfunction diffDataApi(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tconst desired = config.dataApi;\n\n\tif (!state.dataApiEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t};\n\t\tconst input = desired ? enableInputFromResolved(desired) : undefined;\n\t\tif (input) step.input = input;\n\t\tplan.push(step);\n\t\treturn;\n\t}\n\n\t// Already enabled: the only reconcilable change is its runtime settings.\n\tconst desiredSettings = desired?.settings;\n\tif (!desiredSettings) return;\n\tif (!dataApiSettingsDiffer(desiredSettings, state.dataApiSettings)) return;\n\n\tif (options.updateExisting) {\n\t\tplan.push({\n\t\t\tkind: \"update-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t\tsettings: desiredSettings,\n\t\t});\n\t} else {\n\t\tconflicts.push({\n\t\t\tkind: \"branch\",\n\t\t\tidentifier: remote.branch.name,\n\t\t\tfield: \"dataApi.settings\",\n\t\t\tcurrent: state.dataApiSettings ?? undefined,\n\t\t\tdesired: desiredSettings,\n\t\t\treason: \"Existing Data API has different settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t});\n\t}\n}\n\n/** Build the create-time {@link EnableDataApiInput} from a resolved Data API config. */\nfunction enableInputFromResolved(\n\tresolved: ResolvedDataApiConfig,\n): EnableDataApiInput {\n\tconst input: EnableDataApiInput = { authProvider: resolved.authProvider };\n\tif (resolved.jwksUrl !== undefined) input.jwksUrl = resolved.jwksUrl;\n\tif (resolved.providerName !== undefined)\n\t\tinput.providerName = resolved.providerName;\n\tif (resolved.jwtAudience !== undefined)\n\t\tinput.jwtAudience = resolved.jwtAudience;\n\tif (resolved.settings) input.settings = resolved.settings;\n\treturn input;\n}\n\n/** The camelCase keys of {@link DataApiSettings}, used to compare desired vs remote settings. */\nconst DATA_API_SETTING_KEYS = [\n\t\"dbAggregatesEnabled\",\n\t\"dbAnonRole\",\n\t\"dbExtraSearchPath\",\n\t\"dbMaxRows\",\n\t\"dbSchemas\",\n\t\"jwtRoleClaimKey\",\n\t\"jwtCacheMaxLifetime\",\n\t\"openapiMode\",\n\t\"serverCorsAllowedOrigins\",\n\t\"serverTimingEnabled\",\n] as const satisfies ReadonlyArray<keyof DataApiSettings>;\n\n/**\n * Whether the policy's Data API `settings` differ from the remote ones. Only the keys the\n * policy actually set are compared (so unset fields never count as drift). When the remote\n * settings are not reported (`null`/absent — non-SubZero), drift can't be computed and this\n * returns `false` so no spurious update is planned.\n */\nfunction dataApiSettingsDiffer(\n\tdesired: DataApiSettings,\n\tcurrent: DataApiSettings | null | undefined,\n): boolean {\n\tif (!current) return false;\n\tfor (const key of DATA_API_SETTING_KEYS) {\n\t\tif (desired[key] === undefined) continue;\n\t\tif (JSON.stringify(desired[key]) !== JSON.stringify(current[key])) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface BranchConfigArgs {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\nfunction diffBranchConfig(args: BranchConfigArgs): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst branchName = remote.branch.name;\n\tconst computeSettings = config.postgres?.computeSettings;\n\n\tif (computeSettings) {\n\t\tconst endpoint = remote.endpoint;\n\t\tif (!endpoint) {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"endpoint\",\n\t\t\t\tcurrent: undefined,\n\t\t\t\tdesired: computeSettings,\n\t\t\t\treason: \"Branch has no read-write endpoint; cannot apply compute settings.\",\n\t\t\t});\n\t\t} else {\n\t\t\tconst drift = computeDriftBetween(computeSettings, endpoint);\n\t\t\tif (drift) {\n\t\t\t\tif (options.updateExisting) {\n\t\t\t\t\tplan.push({\n\t\t\t\t\t\tkind: \"update-endpoint\",\n\t\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\t\tbranchName,\n\t\t\t\t\t\tendpointId: endpoint.id,\n\t\t\t\t\t\tsettings: computeSettings,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\t\tcurrent: drift.current,\n\t\t\t\t\t\tdesired: drift.desired,\n\t\t\t\t\t\treason: \"Existing branch has different compute settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (\n\t\tconfig.protected !== undefined &&\n\t\tconfig.protected !== remote.branch.protected\n\t) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"update-branch-protected\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName,\n\t\t\t\tprotected: config.protected,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"protected\",\n\t\t\t\tcurrent: remote.branch.protected,\n\t\t\t\tdesired: config.protected,\n\t\t\t\treason: \"Existing branch has a different `protected` flag. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (config.ttlSeconds !== undefined) {\n\t\tconst current = remote.branch.expiresAt\n\t\t\t? Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.round(\n\t\t\t\t\t\t(Date.parse(remote.branch.expiresAt) - Date.now()) /\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tif (\n\t\t\tcurrent === undefined ||\n\t\t\tMath.abs(current - config.ttlSeconds) > 30\n\t\t) {\n\t\t\tconst expiresAt = new Date(\n\t\t\t\tDate.now() + config.ttlSeconds * 1000,\n\t\t\t).toISOString();\n\t\t\tif (options.updateExisting) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-branch-ttl\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName,\n\t\t\t\t\texpiresAt,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconflicts.push({\n\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\tfield: \"ttl\",\n\t\t\t\t\tcurrent: remote.branch.expiresAt,\n\t\t\t\t\tdesired: expiresAt,\n\t\t\t\t\treason: \"Existing branch has a different TTL. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction computeDriftBetween(\n\tdesired: ComputeSettings,\n\tendpoint: NeonEndpointSnapshot,\n): {\n\tcurrent: Partial<ComputeSettings>;\n\tdesired: Partial<ComputeSettings>;\n} | null {\n\tconst currentDrift: Partial<ComputeSettings> = {};\n\tconst desiredDrift: Partial<ComputeSettings> = {};\n\tlet drift = false;\n\n\tif (\n\t\tdesired.autoscalingLimitMinCu !== undefined &&\n\t\tdesired.autoscalingLimitMinCu !== endpoint.autoscalingLimitMinCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;\n\t\tdesiredDrift.autoscalingLimitMinCu = desired.autoscalingLimitMinCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.autoscalingLimitMaxCu !== undefined &&\n\t\tdesired.autoscalingLimitMaxCu !== endpoint.autoscalingLimitMaxCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;\n\t\tdesiredDrift.autoscalingLimitMaxCu = desired.autoscalingLimitMaxCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.suspendTimeout !== undefined &&\n\t\tdesired.suspendTimeout !== endpoint.suspendTimeout\n\t) {\n\t\tcurrentDrift.suspendTimeout = endpoint.suspendTimeout;\n\t\tdesiredDrift.suspendTimeout = desired.suspendTimeout;\n\t\tdrift = true;\n\t}\n\treturn drift ? { current: currentDrift, desired: desiredDrift } : null;\n}\n"],"mappings":"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"}
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ declare const ErrorCode: {
|
|
|
47
47
|
};
|
|
48
48
|
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
49
49
|
/**
|
|
50
|
-
* Base class for all errors thrown by `@
|
|
50
|
+
* Base class for all errors thrown by `@neon/config`. Always extend this so callers
|
|
51
51
|
* can catch every package-thrown error with a single `instanceof` check.
|
|
52
52
|
*
|
|
53
53
|
* Optional `details` carries structured context that the CLI prints under `--debug` and
|
package/dist/lib/errors.js
CHANGED
|
@@ -44,7 +44,7 @@ const ErrorCode = {
|
|
|
44
44
|
};
|
|
45
45
|
const ISSUE_URL = "https://github.com/neondatabase/neon-pkgs/issues/new";
|
|
46
46
|
/**
|
|
47
|
-
* Base class for all errors thrown by `@
|
|
47
|
+
* Base class for all errors thrown by `@neon/config`. Always extend this so callers
|
|
48
48
|
* can catch every package-thrown error with a single `instanceof` check.
|
|
49
49
|
*
|
|
50
50
|
* Optional `details` carries structured context that the CLI prints under `--debug` and
|
|
@@ -83,7 +83,7 @@ function isPlatformError(value) {
|
|
|
83
83
|
* is supposed to fix something on their end.
|
|
84
84
|
*/
|
|
85
85
|
function bugReportFooter() {
|
|
86
|
-
return `\nThis indicates a bug in @
|
|
86
|
+
return `\nThis indicates a bug in @neon/config. Please file an issue: ${ISSUE_URL}`;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
89
|
* Thrown by {@link defineConfig} when the user-provided configuration object is invalid.
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neon/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neon/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
|
package/dist/lib/loader.js
CHANGED
|
@@ -84,7 +84,7 @@ async function importModule(absPath) {
|
|
|
84
84
|
const lower = absPath.toLowerCase();
|
|
85
85
|
if (!(lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts"))) return import(pathToFileURL(absPath).href);
|
|
86
86
|
const createJiti = extractCreateJiti(await import("jiti"));
|
|
87
|
-
if (!createJiti) throw new ConfigLoadError(["jiti is required to load TypeScript config files but could not be initialised.", "Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @
|
|
87
|
+
if (!createJiti) throw new ConfigLoadError(["jiti is required to load TypeScript config files but could not be initialised.", "Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neon/config."].join(" "));
|
|
88
88
|
return createJiti(pathToFileURL(absPath).href, {
|
|
89
89
|
interopDefault: true,
|
|
90
90
|
moduleCache: false
|
package/dist/lib/loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError, isPlatformError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\t// `defineConfig()` runs at module-eval time, so a config the user got *wrong*\n\t\t// (a bad function slug, an unknown key, an invalid duration, …) throws a\n\t\t// PlatformError from inside this import. That error already pinpoints the exact\n\t\t// field and reason, so surface it verbatim — burying it under the generic\n\t\t// \"this is usually a TypeScript syntax error\" hint sent users hunting for a\n\t\t// syntax bug that isn't there. The hint is reserved for genuine evaluation\n\t\t// failures (syntax errors, missing deps, thrown runtime exceptions).\n\t\tif (isPlatformError(cause)) {\n\t\t\tthrow cause;\n\t\t}\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError, isPlatformError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\t// `defineConfig()` runs at module-eval time, so a config the user got *wrong*\n\t\t// (a bad function slug, an unknown key, an invalid duration, …) throws a\n\t\t// PlatformError from inside this import. That error already pinpoints the exact\n\t\t// field and reason, so surface it verbatim — burying it under the generic\n\t\t// \"this is usually a TypeScript syntax error\" hint sent users hunting for a\n\t\t// syntax bug that isn't there. The hint is reserved for genuine evaluation\n\t\t// failures (syntax errors, missing deps, thrown runtime exceptions).\n\t\tif (isPlatformError(cause)) {\n\t\t\tthrow cause;\n\t\t}\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neon/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neon-api-real.js","names":["rawListProjects","rawGetProject","rawCreateProject","rawUpdateProject","rawListProjectBranches","rawCreateProjectBranch","rawUpdateProjectBranch","rawListProjectEndpoints","rawUpdateProjectEndpoint","rawListProjectBranchRoles","rawListProjectBranchDatabases","rawGetConnectionUri","rawGetNeonAuth","rawGetProjectBranchDataApi","rawCreateProjectBranchDataApi","rawUpdateProjectBranchDataApi"],"sources":["../../src/lib/neon-api-real.ts"],"sourcesContent":["import type {\n\tDataApiSettings as ApiDataApiSettings,\n\tBranch,\n\tBranchCreateRequest,\n\tBranchCreateRequestEndpointOptions,\n\tBranchUpdateRequest,\n\tDataApiCreateRequest,\n\tDataApiReponse,\n\tDatabase,\n\tDefaultEndpointSettings,\n\tEndpoint,\n\tEndpointUpdateRequest,\n\tPgVersion,\n\tProject,\n\tProjectCreateRequest,\n\tProjectListItem,\n\tProjectUpdateRequest,\n\tRole,\n} from \"@neon/sdk\";\nimport { createNeonClient } from \"@neon/sdk\";\nimport {\n\tcreateProject as rawCreateProject,\n\tcreateProjectBranch as rawCreateProjectBranch,\n\tcreateProjectBranchDataApi as rawCreateProjectBranchDataApi,\n\tgetConnectionUri as rawGetConnectionUri,\n\tgetNeonAuth as rawGetNeonAuth,\n\tgetProject as rawGetProject,\n\tgetProjectBranchDataApi as rawGetProjectBranchDataApi,\n\tlistProjectBranchDatabases as rawListProjectBranchDatabases,\n\tlistProjectBranches as rawListProjectBranches,\n\tlistProjectBranchRoles as rawListProjectBranchRoles,\n\tlistProjectEndpoints as rawListProjectEndpoints,\n\tlistProjects as rawListProjects,\n\tupdateProject as rawUpdateProject,\n\tupdateProjectBranch as rawUpdateProjectBranch,\n\tupdateProjectBranchDataApi as rawUpdateProjectBranchDataApi,\n\tupdateProjectEndpoint as rawUpdateProjectEndpoint,\n} from \"@neon/sdk/raw\";\nimport { z } from \"zod\";\nimport { formatSuspendTimeout, parseSuspendTimeout } from \"./duration.js\";\nimport { ErrorCode, PlatformError } from \"./errors.js\";\nimport type {\n\tCreateBranchInput,\n\tCreateBucketInput,\n\tCreateCredentialInput,\n\tCreateProjectInput,\n\tDeployFunctionInput,\n\tEnableDataApiInput,\n\tGetConnectionUriInput,\n\tNeonApi,\n\tNeonAuthSnapshot,\n\tNeonBranchSnapshot,\n\tNeonBranchStorageSnapshot,\n\tNeonBucketSnapshot,\n\tNeonCredentialMeta,\n\tNeonCredentialSecret,\n\tNeonDataApiSnapshot,\n\tNeonDatabaseSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionDeploymentSnapshot,\n\tNeonFunctionSnapshot,\n\tNeonProjectSnapshot,\n\tNeonRoleSnapshot,\n\tUpdateBranchInput,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tDataApiSettings,\n} from \"./types.js\";\nimport { wrapNeonError } from \"./wrap-neon-error.js\";\n\ntype ApiClient = ReturnType<typeof createNeonClient>[\"client\"];\nconst DEFAULT_NEON_API_BASE_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Unwrap a `@neon/sdk` raw `{ data, error, response }` result into the bare body, throwing\n * on a non-2xx response. The thrown shape (`{ response: { status, data } }`) deliberately\n * matches what the package's REST fallbacks already throw, so {@link wrapNeonError} and the\n * 423 {@link retryOnLocked} retry keep working unchanged across both transports.\n */\nfunction unwrap<T>(result: {\n\tdata?: T;\n\terror?: unknown;\n\tresponse?: Response;\n}): T {\n\tconst response = result.response;\n\tif (!response || !response.ok) {\n\t\tthrow {\n\t\t\tresponse: {\n\t\t\t\tstatus: response?.status,\n\t\t\t\tdata: result.error,\n\t\t\t},\n\t\t};\n\t}\n\treturn result.data as T;\n}\n\nconst neonAuthResponseSchema = z.object({\n\tauth_provider_project_id: z.string(),\n\tpub_client_key: z.string().optional(),\n\tsecret_server_key: z.string().optional(),\n\tjwks_url: z.string(),\n\tbase_url: z.string().optional(),\n});\n\n// ─── Data API mapping (camelCase neon.ts ↔ snake_case Neon API) ───────────────\n\n/** Map our camelCase {@link DataApiSettings} onto the Neon API's snake_case `DataAPISettings`. */\nfunction dataApiSettingsToApi(settings: DataApiSettings): ApiDataApiSettings {\n\tconst out: ApiDataApiSettings = {};\n\tif (settings.dbAggregatesEnabled !== undefined)\n\t\tout.db_aggregates_enabled = settings.dbAggregatesEnabled;\n\tif (settings.dbAnonRole !== undefined)\n\t\tout.db_anon_role = settings.dbAnonRole;\n\tif (settings.dbExtraSearchPath !== undefined)\n\t\tout.db_extra_search_path = settings.dbExtraSearchPath;\n\tif (settings.dbMaxRows !== undefined) out.db_max_rows = settings.dbMaxRows;\n\tif (settings.dbSchemas !== undefined) out.db_schemas = settings.dbSchemas;\n\tif (settings.jwtRoleClaimKey !== undefined)\n\t\tout.jwt_role_claim_key = settings.jwtRoleClaimKey;\n\tif (settings.jwtCacheMaxLifetime !== undefined)\n\t\tout.jwt_cache_max_lifetime = settings.jwtCacheMaxLifetime;\n\tif (settings.openapiMode !== undefined)\n\t\tout.openapi_mode = settings.openapiMode;\n\tif (settings.serverCorsAllowedOrigins !== undefined)\n\t\tout.server_cors_allowed_origins = settings.serverCorsAllowedOrigins;\n\tif (settings.serverTimingEnabled !== undefined)\n\t\tout.server_timing_enabled = settings.serverTimingEnabled;\n\treturn out;\n}\n\n/** Narrow the API's free-form `openapi_mode` string to our literal union (else drop it). */\nfunction normalizeOpenapiMode(\n\tvalue: string,\n): DataApiSettings[\"openapiMode\"] | undefined {\n\treturn value === \"ignore-privileges\" || value === \"disabled\"\n\t\t? value\n\t\t: undefined;\n}\n\n/** Map the Neon API's snake_case `DataAPISettings` back to our camelCase {@link DataApiSettings}. */\nfunction dataApiSettingsFromApi(\n\tsettings: ApiDataApiSettings | null | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tif (settings.db_aggregates_enabled !== undefined)\n\t\tout.dbAggregatesEnabled = settings.db_aggregates_enabled;\n\tif (settings.db_anon_role !== undefined)\n\t\tout.dbAnonRole = settings.db_anon_role;\n\tif (settings.db_extra_search_path !== undefined)\n\t\tout.dbExtraSearchPath = settings.db_extra_search_path;\n\tif (settings.db_max_rows !== undefined)\n\t\tout.dbMaxRows = settings.db_max_rows;\n\tif (settings.db_schemas !== undefined) out.dbSchemas = settings.db_schemas;\n\tif (settings.jwt_role_claim_key !== undefined)\n\t\tout.jwtRoleClaimKey = settings.jwt_role_claim_key;\n\tif (settings.jwt_cache_max_lifetime !== undefined)\n\t\tout.jwtCacheMaxLifetime = settings.jwt_cache_max_lifetime;\n\tif (settings.openapi_mode !== undefined) {\n\t\tconst mode = normalizeOpenapiMode(settings.openapi_mode);\n\t\tif (mode !== undefined) out.openapiMode = mode;\n\t}\n\tif (settings.server_cors_allowed_origins !== undefined)\n\t\tout.serverCorsAllowedOrigins = settings.server_cors_allowed_origins;\n\tif (settings.server_timing_enabled !== undefined)\n\t\tout.serverTimingEnabled = settings.server_timing_enabled;\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/** Build the Neon API `DataAPICreateRequest` from our {@link EnableDataApiInput}. */\nfunction dataApiCreateRequest(\n\tinput: EnableDataApiInput | undefined,\n): DataApiCreateRequest {\n\tconst req: DataApiCreateRequest = {};\n\tif (!input) return req;\n\tif (input.authProvider !== undefined)\n\t\treq.auth_provider =\n\t\t\tinput.authProvider === \"neon\" ? \"neon_auth\" : \"external\";\n\tif (input.jwksUrl !== undefined) req.jwks_url = input.jwksUrl;\n\tif (input.providerName !== undefined)\n\t\treq.provider_name = input.providerName;\n\tif (input.jwtAudience !== undefined) req.jwt_audience = input.jwtAudience;\n\tif (input.settings) {\n\t\tconst settings = dataApiSettingsToApi(input.settings);\n\t\tif (Object.keys(settings).length > 0) req.settings = settings;\n\t}\n\treturn req;\n}\n\n/** Map a `DataAPIReponse` (GET) onto our {@link NeonDataApiSnapshot}. */\nfunction dataApiSnapshotFromResponse(\n\tdata: DataApiReponse,\n): NeonDataApiSnapshot {\n\tconst snapshot: NeonDataApiSnapshot = { url: data.url };\n\tif (data.status !== undefined) snapshot.status = data.status;\n\tconst settings = dataApiSettingsFromApi(data.settings);\n\tif (settings) snapshot.settings = settings;\n\treturn snapshot;\n}\n\n// ─── Preview: buckets ──────────────────────────────────────────────────────\n\nconst bucketSchema = z.object({\n\tname: z.string(),\n\taccess_level: z.string().optional(),\n});\nconst bucketResponseSchema = z.object({ bucket: bucketSchema });\nconst bucketsListResponseSchema = z.object({ buckets: z.array(bucketSchema) });\nconst branchStorageSchema = z.object({\n\tenabled: z.boolean().optional(),\n\ts3_endpoint: z.string(),\n\tregion: z.string(),\n\tforce_path_style: z.boolean(),\n});\n\n// ─── Preview: functions ────────────────────────────────────────────────────\n\nconst functionDeploymentSchema = z.object({\n\tid: z.number(),\n\tstatus: z.string(),\n});\nconst neonFunctionSchema = z.object({\n\tid: z.string(),\n\tslug: z.string(),\n\tname: z.string(),\n\tinvocation_url: z.string(),\n\tactive_deployment: functionDeploymentSchema.optional(),\n});\nconst functionsListResponseSchema = z.object({\n\tfunctions: z.array(neonFunctionSchema),\n});\nconst functionDeploymentResponseSchema = z.object({\n\tdeployment: functionDeploymentSchema,\n});\n\n// ─── Preview: branch-scoped credentials ─────────────────────────────────────\n\nconst credentialScopeSchema = z.enum([\n\t\"storage:read\",\n\t\"storage:write\",\n\t\"ai_gateway:invoke\",\n\t\"functions:invoke\",\n]);\nconst createCredentialResponseSchema = z.object({\n\ttoken_id: z.string(),\n\ttoken_id_short: z.string(),\n\tname: z.string().optional(),\n\tapi_token: z.string(),\n\ts3_secret_access_key: z.string(),\n\tscopes: z.array(credentialScopeSchema),\n\tbranch_id: z.string(),\n\tcreated_at: z.string(),\n\texpires_at: z.string().optional(),\n});\nconst credentialMetaSchema = z.object({\n\ttoken_id: z.string(),\n\ttoken_id_short: z.string(),\n\tname: z.string().optional(),\n\tscopes: z.array(credentialScopeSchema),\n\tprincipal_type: z.enum([\"user\", \"function\"]),\n\tfunction_id: z.string().optional(),\n\tbranch_id: z.string().optional(),\n\tcreated_at: z.string(),\n\tlast_used_at: z.string().optional(),\n\trevoked_at: z.string().optional(),\n\texpires_at: z.string().optional(),\n});\nconst listCredentialsResponseSchema = z.object({\n\tcredentials: z.array(credentialMetaSchema),\n});\n\ninterface CreateNeonAuthRestInput {\n\tauth_provider: \"better_auth\";\n\tdatabase_name?: string;\n}\n\ninterface RestConfig {\n\tapiKey: string;\n\tbaseUrl: string;\n}\n\n/**\n * Adapt `@neon/sdk` (raw layer) to the narrow {@link NeonApi} façade used by the rest of\n * this package. Constructs are restricted to whole-object read/write of just the fields we\n * model in {@link Config}; anything else stays untouched on the remote.\n */\nexport function createRealNeonApi(options: {\n\tapiKey: string;\n\tbaseUrl?: string;\n\t/**\n\t * Tuning knob for the built-in 423 retry. Defaults: ~30s of total wait spread across\n\t * 12 attempts with exponential backoff capped at 5s. Lowering this is mostly useful in\n\t * tests; raising it is rarely needed because Neon operations are usually sub-second.\n\t */\n\tretryOnLocked?: {\n\t\tmaxAttempts?: number;\n\t\tinitialDelayMs?: number;\n\t\tmaxDelayMs?: number;\n\t};\n}): NeonApi {\n\tif (!options.apiKey || options.apiKey.trim() === \"\") {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.MissingApiKey,\n\t\t\t[\n\t\t\t\t\"createRealNeonApi requires a non-empty `apiKey`.\",\n\t\t\t\t\"Generate one at https://console.neon.tech/app/settings/api-keys and pass it as { apiKey: process.env.NEON_API_KEY }.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\n\t// The SDK runs its own retries by default; this adapter does its own 423-aware\n\t// `retryOnLocked`, so disable the SDK's to avoid compounding backoff.\n\tconst client = createNeonClient({\n\t\tapiKey: options.apiKey,\n\t\tretries: 0,\n\t\t...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),\n\t}).client;\n\n\treturn new RealNeonApi(\n\t\tclient,\n\t\t{\n\t\t\tmaxAttempts: options.retryOnLocked?.maxAttempts ?? 12,\n\t\t\tinitialDelayMs: options.retryOnLocked?.initialDelayMs ?? 250,\n\t\t\tmaxDelayMs: options.retryOnLocked?.maxDelayMs ?? 5_000,\n\t\t},\n\t\t{\n\t\t\tapiKey: options.apiKey,\n\t\t\tbaseUrl: options.baseUrl ?? DEFAULT_NEON_API_BASE_URL,\n\t\t},\n\t);\n}\n\ninterface RetryConfig {\n\tmaxAttempts: number;\n\tinitialDelayMs: number;\n\tmaxDelayMs: number;\n}\n\n/**\n * Retry a function whenever it throws an HTTP 423 (Locked) — Neon's signal that a prior\n * mutation on the same resource is still in flight. Uses exponential backoff capped at\n * `maxDelayMs`. Any other error (and the last attempt) propagates.\n *\n * Exported only for tests; production callers go through the wrapped {@link NeonApi}.\n */\nexport async function retryOnLocked<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet delay = config.initialDelayMs;\n\tlet lastError: unknown;\n\tfor (let attempt = 1; attempt <= config.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tlastError = err;\n\t\t\tconst status = readHttpStatusFromError(err);\n\t\t\tif (status !== 423 || attempt === config.maxAttempts) throw err;\n\t\t\tawait sleep(delay);\n\t\t\tdelay = Math.min(delay * 2, config.maxDelayMs);\n\t\t}\n\t}\n\tthrow lastError;\n}\n\nfunction readHttpStatusFromError(err: unknown): number | undefined {\n\tif (err === null || typeof err !== \"object\") return undefined;\n\tconst response = (err as { response?: unknown }).response;\n\tif (response === null || typeof response !== \"object\") return undefined;\n\tconst status = (response as { status?: unknown }).status;\n\treturn typeof status === \"number\" ? status : undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nclass RealNeonApi implements NeonApi {\n\tconstructor(\n\t\tprivate readonly client: ApiClient,\n\t\tprivate readonly retryConfig: RetryConfig,\n\t\tprivate readonly restConfig: RestConfig,\n\t) {}\n\n\tprivate retry<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn retryOnLocked(fn, this.retryConfig);\n\t}\n\n\tprivate async call<T>(\n\t\top: string,\n\t\tfn: () => Promise<T>,\n\t\toptions: { projectId?: string; mutating?: boolean } = {},\n\t): Promise<T> {\n\t\ttry {\n\t\t\treturn options.mutating ? await this.retry(fn) : await fn();\n\t\t} catch (err) {\n\t\t\tconst wrapped = wrapNeonError(\n\t\t\t\terr,\n\t\t\t\toptions.projectId\n\t\t\t\t\t? { op, projectId: options.projectId }\n\t\t\t\t\t: { op },\n\t\t\t);\n\t\t\tthrow wrapped;\n\t\t}\n\t}\n\n\tasync listProjects(filter: {\n\t\torgId?: string;\n\t}): Promise<NeonProjectSnapshot[]> {\n\t\treturn this.call(\n\t\t\tfilter.orgId ? `listProjects(org=${filter.orgId})` : \"listProjects\",\n\t\t\tasync () => {\n\t\t\t\tconst projects: ProjectListItem[] = [];\n\t\t\t\tlet cursor: string | undefined;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst body = unwrap(\n\t\t\t\t\t\tawait rawListProjects({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\t\t...(filter.orgId\n\t\t\t\t\t\t\t\t\t? { org_id: filter.orgId }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(cursor ? { cursor } : {}),\n\t\t\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tprojects.push(...body.projects);\n\t\t\t\t\tconst next = (body as { pagination?: { next?: string } })\n\t\t\t\t\t\t.pagination?.next;\n\t\t\t\t\tif (!next || next === cursor) break;\n\t\t\t\t\tcursor = next;\n\t\t\t\t}\n\t\t\t\treturn projects.map(projectToSnapshot);\n\t\t\t},\n\t\t);\n\t}\n\n\tasync getProject(projectId: string): Promise<NeonProjectSnapshot> {\n\t\treturn this.call(\n\t\t\t`getProject(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst body = unwrap(\n\t\t\t\t\tawait rawGetProject({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(body.project);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync createProject(\n\t\tinput: CreateProjectInput,\n\t): Promise<NeonProjectSnapshot> {\n\t\tconst body: ProjectCreateRequest = {\n\t\t\tproject: {\n\t\t\t\tname: input.name,\n\t\t\t\tregion_id: input.regionId,\n\t\t\t\t...(input.pgVersion !== undefined\n\t\t\t\t\t? { pg_version: input.pgVersion as PgVersion }\n\t\t\t\t\t: {}),\n\t\t\t\t...(input.orgId ? { org_id: input.orgId } : {}),\n\t\t\t\t...(input.defaultEndpointSettings\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdefault_endpoint_settings:\n\t\t\t\t\t\t\t\tcomputeSettingsToDefaults(\n\t\t\t\t\t\t\t\t\tinput.defaultEndpointSettings,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(input.defaultBranchName\n\t\t\t\t\t? { branch: { name: input.defaultBranchName } }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t\treturn this.call(\n\t\t\t`createProject(${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawCreateProject({ client: this.client, body }),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(data.project);\n\t\t\t},\n\t\t\t{ mutating: true },\n\t\t);\n\t}\n\n\tasync updateProject(\n\t\tprojectId: string,\n\t\tinput: { name?: string; defaultEndpointSettings?: ComputeSettings },\n\t): Promise<NeonProjectSnapshot> {\n\t\tconst body: ProjectUpdateRequest = {\n\t\t\tproject: {\n\t\t\t\t...(input.name !== undefined ? { name: input.name } : {}),\n\t\t\t\t...(input.defaultEndpointSettings\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdefault_endpoint_settings:\n\t\t\t\t\t\t\t\tcomputeSettingsToDefaults(\n\t\t\t\t\t\t\t\t\tinput.defaultEndpointSettings,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t\treturn this.call(\n\t\t\t`updateProject(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProject({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(data.project);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listBranches(projectId: string): Promise<NeonBranchSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranches(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst branches: Branch[] = [];\n\t\t\t\tlet cursor: string | undefined;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst body = unwrap(\n\t\t\t\t\t\tawait rawListProjectBranches({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t\t\t\t...(cursor ? { cursor } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbranches.push(...(body.branches as Branch[]));\n\t\t\t\t\tconst next = (body as { pagination?: { next?: string } })\n\t\t\t\t\t\t.pagination?.next;\n\t\t\t\t\tif (!next || next === cursor) break;\n\t\t\t\t\tcursor = next;\n\t\t\t\t}\n\t\t\t\treturn branches.map(branchToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync createBranch(\n\t\tprojectId: string,\n\t\tinput: CreateBranchInput,\n\t): Promise<{\n\t\tbranch: NeonBranchSnapshot;\n\t\tendpoints: NeonEndpointSnapshot[];\n\t}> {\n\t\tconst endpointOptions: BranchCreateRequestEndpointOptions | undefined =\n\t\t\tinput.computeSettings\n\t\t\t\t? {\n\t\t\t\t\t\ttype: \"read_write\",\n\t\t\t\t\t\t...computeSettingsToEndpointOptions(\n\t\t\t\t\t\t\tinput.computeSettings,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: { type: \"read_write\" };\n\n\t\tconst body: BranchCreateRequest = {\n\t\t\tbranch: {\n\t\t\t\tname: input.name,\n\t\t\t\t...(input.parentId ? { parent_id: input.parentId } : {}),\n\t\t\t\t...(input.expiresAt ? { expires_at: input.expiresAt } : {}),\n\t\t\t\t...(input.protected !== undefined\n\t\t\t\t\t? { protected: input.protected }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\tendpoints: [endpointOptions],\n\t\t};\n\t\treturn this.call(\n\t\t\t`createBranch(${projectId}/${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawCreateProjectBranch({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tbranch: branchToSnapshot(data.branch),\n\t\t\t\t\tendpoints: (data.endpoints ?? []).map(endpointToSnapshot),\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync updateBranch(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: UpdateBranchInput,\n\t): Promise<NeonBranchSnapshot> {\n\t\tconst branch: BranchUpdateRequest[\"branch\"] = {};\n\t\tif (input.name !== undefined) branch.name = input.name;\n\t\tif (input.expiresAt !== undefined) branch.expires_at = input.expiresAt;\n\t\tif (input.protected !== undefined) branch.protected = input.protected;\n\t\treturn this.call(\n\t\t\t`updateBranch(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProjectBranch({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t\tbody: { branch },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn branchToSnapshot(data.branch);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listEndpoints(projectId: string): Promise<NeonEndpointSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listEndpoints(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectEndpoints({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.endpoints as Endpoint[]).map(endpointToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync updateEndpoint(\n\t\tprojectId: string,\n\t\tendpointId: string,\n\t\tsettings: ComputeSettings,\n\t): Promise<NeonEndpointSnapshot> {\n\t\tconst endpoint: EndpointUpdateRequest[\"endpoint\"] =\n\t\t\tcomputeSettingsToEndpointOptions(settings);\n\t\treturn this.call(\n\t\t\t`updateEndpoint(${projectId}/${endpointId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProjectEndpoint({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tendpoint_id: endpointId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: { endpoint },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn endpointToSnapshot(data.endpoint);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listBranchRoles(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonRoleSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranchRoles(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectBranchRoles({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.roles as Role[]).map(roleToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync listBranchDatabases(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonDatabaseSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranchDatabases(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectBranchDatabases({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.databases as Database[]).map(databaseToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync getConnectionUri(\n\t\tprojectId: string,\n\t\tinput: GetConnectionUriInput,\n\t): Promise<{ uri: string }> {\n\t\tconst op = `getConnectionUri(${projectId}/${input.databaseName}@${input.roleName}${input.pooled ? \" pooled\" : \"\"})`;\n\t\t// Always send `pooled` explicitly. The Neon API has switched its default\n\t\t// to returning the pooled URI when the parameter is omitted, so we have\n\t\t// to be explicit to get the direct URI back.\n\t\tconst pooled = input.pooled === true;\n\t\treturn this.call(\n\t\t\top,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawGetConnectionUri({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\tdatabase_name: input.databaseName,\n\t\t\t\t\t\t\trole_name: input.roleName,\n\t\t\t\t\t\t\t...(input.branchId\n\t\t\t\t\t\t\t\t? { branch_id: input.branchId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(input.endpointId\n\t\t\t\t\t\t\t\t? { endpoint_id: input.endpointId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\tpooled,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn { uri: data.uri };\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync getNeonAuth(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonAuthSnapshot | null> {\n\t\t// `GET /projects/:pid/branches/:bid/auth` returns 404 when no integration exists.\n\t\t// Surface that as `null` so callers can branch cleanly instead of try/catch.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getNeonAuth(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = unwrap(\n\t\t\t\t\t\tawait rawGetNeonAuth({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\treturn neonAuthResponseToSnapshot(\n\t\t\t\t\t\tneonAuthResponseSchema.parse(data),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync enableNeonAuth(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: { databaseName?: string } = {},\n\t): Promise<NeonAuthSnapshot> {\n\t\t// Idempotent: if an integration already exists on the branch, the POST returns 409\n\t\t// (`Conflict`). We swallow that and re-fetch the existing snapshot so callers can\n\t\t// rely on `enableNeonAuth` to be safe to invoke from any push, including no-ops.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`enableNeonAuth(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\t// The generated `createNeonAuth` doesn't narrow this branch\n\t\t\t\t\t// endpoint to `better_auth`, so post the REST body directly.\n\t\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\t\t`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/auth`,\n\t\t\t\t\t\tcreateNeonAuthRestInput(input),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = neonAuthResponseSchema.parse(data);\n\t\t\t\t\treturn neonAuthResponseToSnapshot(parsed);\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof PlatformError &&\n\t\t\t\terr.code === ErrorCode.Conflict\n\t\t\t) {\n\t\t\t\tconst existing = await this.getNeonAuth(projectId, branchId);\n\t\t\t\tif (existing) return existing;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate async postJson(path: string, body: unknown): Promise<unknown> {\n\t\treturn this.request(\"POST\", path, {\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t}\n\n\tprivate async getJson(path: string): Promise<unknown> {\n\t\treturn this.request(\"GET\", path);\n\t}\n\n\tprivate async deleteJson(path: string): Promise<unknown> {\n\t\treturn this.request(\"DELETE\", path);\n\t}\n\n\t/**\n\t * Upload a built function bundle via `multipart/form-data` to the deploy endpoint\n\t * (`POST .../functions/{slug}/deployments`). Body shape lives in the pure\n\t * {@link buildFunctionDeployForm} helper so it can be unit-tested against the spec.\n\t */\n\tprivate async postMultipart(\n\t\tpath: string,\n\t\tinput: DeployFunctionInput,\n\t): Promise<unknown> {\n\t\treturn this.request(\"POST\", path, {\n\t\t\tbody: buildFunctionDeployForm(input),\n\t\t});\n\t}\n\n\tprivate async request(\n\t\tmethod: \"GET\" | \"POST\" | \"DELETE\",\n\t\tpath: string,\n\t\tinit: { headers?: Record<string, string>; body?: BodyInit } = {},\n\t): Promise<unknown> {\n\t\tconst url = `${this.restConfig.baseUrl.replace(/\\/+$/, \"\")}${path}`;\n\t\tconst res = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.restConfig.apiKey}`,\n\t\t\t\t...(init.headers ?? {}),\n\t\t\t},\n\t\t\t...(init.body !== undefined ? { body: init.body } : {}),\n\t\t});\n\t\tconst data = await readJsonBody(res);\n\t\tif (!res.ok) {\n\t\t\tthrow {\n\t\t\t\tresponse: {\n\t\t\t\t\tstatus: res.status,\n\t\t\t\t\tdata,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn data;\n\t}\n\n\tasync getNeonDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t): Promise<NeonDataApiSnapshot | null> {\n\t\t// Same shape as getNeonAuth — 404 means \"no integration on this branch/db\", which\n\t\t// we translate to `null` for the caller.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getNeonDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\t\tasync () =>\n\t\t\t\t\tdataApiSnapshotFromResponse(\n\t\t\t\t\t\tunwrap(\n\t\t\t\t\t\t\tawait rawGetProjectBranchDataApi({\n\t\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync enableProjectBranchDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t\tinput?: EnableDataApiInput,\n\t): Promise<NeonDataApiSnapshot> {\n\t\t// Idempotent in the same shape as `enableNeonAuth`: if an integration already\n\t\t// exists, the POST returns 409 and we re-fetch the existing snapshot.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`enableProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = unwrap(\n\t\t\t\t\t\tawait rawCreateProjectBranchDataApi({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: dataApiCreateRequest(input),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\t// The create response only carries `url`; settings/status come from a\n\t\t\t\t\t// follow-up GET, which we leave to the caller when it needs them.\n\t\t\t\t\treturn { url: data.url };\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof PlatformError &&\n\t\t\t\terr.code === ErrorCode.Conflict\n\t\t\t) {\n\t\t\t\tconst existing = await this.getNeonDataApi(\n\t\t\t\t\tprojectId,\n\t\t\t\t\tbranchId,\n\t\t\t\t\tdatabaseName,\n\t\t\t\t);\n\t\t\t\tif (existing) return existing;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync updateProjectBranchDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t\tsettings: DataApiSettings,\n\t): Promise<NeonDataApiSnapshot> {\n\t\treturn await this.call(\n\t\t\t`updateProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\tasync () => {\n\t\t\t\tunwrap(\n\t\t\t\t\tawait rawUpdateProjectBranchDataApi({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: { settings: dataApiSettingsToApi(settings) },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\t// The PATCH returns an empty body; re-fetch so the caller sees the\n\t\t\t\t// post-update url/status/settings.\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawGetProjectBranchDataApi({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn dataApiSnapshotFromResponse(data);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\t// ─── Preview: buckets ──────────────────────────────────────────────────────\n\n\tasync listBranchBuckets(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonBucketSnapshot[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listBranchBuckets(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"buckets\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = bucketsListResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.buckets.map(bucketToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Object storage (buckets)\");\n\t\t}\n\t}\n\n\tasync createBranchBucket(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: CreateBucketInput,\n\t): Promise<NeonBucketSnapshot> {\n\t\treturn this.call(\n\t\t\t`createBranchBucket(${projectId}/${branchId}/${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"buckets\"),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: input.name,\n\t\t\t\t\t\t...(input.accessLevel\n\t\t\t\t\t\t\t? { access_level: input.accessLevel }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tconst parsed = bucketResponseSchema.parse(data);\n\t\t\t\treturn bucketToSnapshot(parsed.bucket);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync deleteBranchBucket(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tbucketName: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`deleteBranchBucket(${projectId}/${branchId}/${bucketName})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"buckets\")}/${encodeURIComponent(bucketName)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync getProjectBranchStorage(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonBranchStorageSnapshot | null> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getProjectBranchStorage(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\t`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/storage`,\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = branchStorageSchema.parse(data);\n\t\t\t\t\treturn {\n\t\t\t\t\t\ts3Endpoint: parsed.s3_endpoint,\n\t\t\t\t\t\tregion: parsed.region,\n\t\t\t\t\t\tforcePathStyle: parsed.force_path_style,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\t// 404 BranchStorageNotEnabled → storage not usable on this branch; let the\n\t\t\t// caller decide (fetchEnv throws a clear \"enable storage first\" error).\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow previewUnavailableError(err, \"Object storage\");\n\t\t}\n\t}\n\n\t// ─── Preview: functions ────────────────────────────────────────────────────\n\n\tasync listBranchFunctions(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonFunctionSnapshot[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listBranchFunctions(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"functions\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = functionsListResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.functions.map(functionToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Functions\");\n\t\t}\n\t}\n\n\tasync deleteBranchFunction(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tslug: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`deleteBranchFunction(${projectId}/${branchId}/${slug})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"functions\")}/${encodeURIComponent(slug)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync deployBranchFunction(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tslug: string,\n\t\tinput: DeployFunctionInput,\n\t): Promise<NeonFunctionDeploymentSnapshot> {\n\t\treturn this.call(\n\t\t\t`deployBranchFunction(${projectId}/${branchId}/${slug})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = await this.postMultipart(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"functions\")}/${encodeURIComponent(slug)}/deployments`,\n\t\t\t\t\tinput,\n\t\t\t\t);\n\t\t\t\tconst parsed = functionDeploymentResponseSchema.parse(data);\n\t\t\t\treturn deploymentToSnapshot(parsed.deployment);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\t// ─── Preview: AI Gateway ───────────────────────────────────────────────────\n\t//\n\t// No methods: the AI Gateway is always available on a branch (credential-gated, not\n\t// per-branch provisioned). There is no control-plane enable/disable/status route — the\n\t// gateway is reached at the branch host with a credential carrying `ai_gateway:invoke`.\n\t// `preview.aiGateway` only drives that credential scope and the `OPENAI_*` /\n\t// `NEON_AI_GATEWAY_*` env vars (see `@neondatabase/env`); nothing is provisioned here, so\n\t// `plan` / `apply` never touch an AI Gateway route and can't fail on its availability.\n\n\t// ─── Preview: branch-scoped credentials ──────────────────────────────────\n\n\tasync createCredential(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: CreateCredentialInput,\n\t): Promise<NeonCredentialSecret> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`createCredential(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\t\tcredentialsPath(projectId, branchId),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tscopes: input.scopes,\n\t\t\t\t\t\t\tprincipal_type: input.principalType,\n\t\t\t\t\t\t\t...(input.functionId\n\t\t\t\t\t\t\t\t? { function_id: input.functionId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(input.name ? { name: input.name } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = createCredentialResponseSchema.parse(data);\n\t\t\t\t\treturn createCredentialToSnapshot(parsed);\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Branch credentials\");\n\t\t}\n\t}\n\n\tasync listCredentials(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonCredentialMeta[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listCredentials(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tcredentialsPath(projectId, branchId),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = listCredentialsResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.credentials.map(credentialMetaToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Branch credentials\");\n\t\t}\n\t}\n\n\tasync revokeCredential(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\ttokenId: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`revokeCredential(${projectId}/${branchId}/${tokenId})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${credentialsPath(projectId, branchId)}/${encodeURIComponent(tokenId)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n}\n\nfunction branchPreviewPath(\n\tprojectId: string,\n\tbranchId: string,\n\tresource: \"buckets\" | \"functions\",\n): string {\n\treturn `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/${resource}`;\n}\n\nfunction credentialsPath(projectId: string, branchId: string): string {\n\treturn `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/credentials`;\n}\n\nfunction createCredentialToSnapshot(\n\tdata: z.infer<typeof createCredentialResponseSchema>,\n): NeonCredentialSecret {\n\tconst snapshot: NeonCredentialSecret = {\n\t\ttokenId: data.token_id,\n\t\ttokenIdShort: data.token_id_short,\n\t\tapiToken: data.api_token,\n\t\ts3SecretAccessKey: data.s3_secret_access_key,\n\t\tscopes: data.scopes,\n\t\tbranchId: data.branch_id,\n\t\tcreatedAt: data.created_at,\n\t};\n\tif (data.name !== undefined) snapshot.name = data.name;\n\tif (data.expires_at !== undefined) snapshot.expiresAt = data.expires_at;\n\treturn snapshot;\n}\n\nfunction credentialMetaToSnapshot(\n\tdata: z.infer<typeof credentialMetaSchema>,\n): NeonCredentialMeta {\n\tconst snapshot: NeonCredentialMeta = {\n\t\ttokenId: data.token_id,\n\t\ttokenIdShort: data.token_id_short,\n\t\tscopes: data.scopes,\n\t\tprincipalType: data.principal_type,\n\t\tcreatedAt: data.created_at,\n\t};\n\tif (data.name !== undefined) snapshot.name = data.name;\n\tif (data.function_id !== undefined) snapshot.functionId = data.function_id;\n\tif (data.branch_id !== undefined) snapshot.branchId = data.branch_id;\n\tif (data.last_used_at !== undefined)\n\t\tsnapshot.lastUsedAt = data.last_used_at;\n\tif (data.revoked_at !== undefined) snapshot.revokedAt = data.revoked_at;\n\tif (data.expires_at !== undefined) snapshot.expiresAt = data.expires_at;\n\treturn snapshot;\n}\n\nfunction bucketToSnapshot(\n\tbucket: z.infer<typeof bucketSchema>,\n): NeonBucketSnapshot {\n\treturn {\n\t\tname: bucket.name,\n\t\taccessLevel: normalizeBucketAccessLevel(bucket.access_level),\n\t};\n}\n\n/**\n * The Neon API returns `access_level` as a free-form string (per the API guidelines:\n * responses use plain strings, not enums). Map the known values onto our union and treat\n * anything else as `private` — the safe default for an unrecognised access level.\n */\nfunction normalizeBucketAccessLevel(\n\tvalue: string | undefined,\n): BucketAccessLevel {\n\treturn value === \"public_read\" ? \"public_read\" : \"private\";\n}\n\nfunction functionToSnapshot(\n\tfn: z.infer<typeof neonFunctionSchema>,\n): NeonFunctionSnapshot {\n\tconst snapshot: NeonFunctionSnapshot = {\n\t\tid: fn.id,\n\t\tslug: fn.slug,\n\t\tname: fn.name,\n\t\tinvocationUrl: fn.invocation_url,\n\t};\n\tif (fn.active_deployment) {\n\t\tsnapshot.activeDeploymentId = fn.active_deployment.id;\n\t}\n\treturn snapshot;\n}\n\nfunction deploymentToSnapshot(\n\tdeployment: z.infer<typeof functionDeploymentSchema>,\n): NeonFunctionDeploymentSnapshot {\n\treturn {\n\t\tid: deployment.id,\n\t\tstatus: normalizeDeploymentStatus(deployment.status),\n\t};\n}\n\nfunction normalizeDeploymentStatus(\n\tvalue: string,\n): NeonFunctionDeploymentSnapshot[\"status\"] {\n\tswitch (value) {\n\t\tcase \"pending\":\n\t\tcase \"building\":\n\t\tcase \"completed\":\n\t\tcase \"failed\":\n\t\t\treturn value;\n\t\tdefault:\n\t\t\t// Unknown status from a newer server — surface as `pending` rather than throwing,\n\t\t\t// matching the API guideline that clients treat undocumented enum values leniently.\n\t\t\treturn \"pending\";\n\t}\n}\n\n/**\n * Whether an error from a Preview-feature read means the feature simply isn't available\n * for this project/branch/region (as opposed to a real, transient failure). Neon signals\n * this a few ways: a 404 \"this route does not exist\" (the route isn't deployed at all), or\n * a 503/4xx whose message says the platform feature is \"not available\" / \"not enabled\".\n *\n * Callers do **not** swallow this into an empty result — touching a Preview feature that\n * isn't available is surfaced as a {@link previewUnavailableError} so `plan` / `status` /\n * `pull` (and `neon dev`) fail clearly instead of, say, planning to create resources the\n * API will refuse to create.\n */\nexport function isPreviewFeatureUnavailable(err: unknown): boolean {\n\tif (!(err instanceof PlatformError)) return false;\n\tconst status = err.details.status;\n\tconst message =\n\t\ttypeof err.details.neonMessage === \"string\"\n\t\t\t? err.details.neonMessage.toLowerCase()\n\t\t\t: \"\";\n\tconst mentionsUnavailable =\n\t\tmessage.includes(\"not available\") ||\n\t\tmessage.includes(\"does not exist\") ||\n\t\tmessage.includes(\"not enabled\");\n\treturn (\n\t\tmentionsUnavailable &&\n\t\t(status === 503 || status === 404 || status === 501)\n\t);\n}\n\n/**\n * Reason phrase for the handful of HTTP statuses a Preview-feature read can surface as\n * \"unavailable\". Used to print a short `HTTP <status> <reason>` line (not a stack trace),\n * so the message reads like the API response the user would see in a tool like curl.\n */\nconst HTTP_STATUS_TEXT: Record<number, string> = {\n\t401: \"Unauthorized\",\n\t403: \"Forbidden\",\n\t404: \"Not Found\",\n\t500: \"Internal Server Error\",\n\t501: \"Not Implemented\",\n\t503: \"Service Unavailable\",\n};\n\n/**\n * Per-status guidance for a Preview feature that came back \"unavailable\". A preview can be\n * gated several different ways and the HTTP status is the best signal for which, so we tailor\n * the next step instead of emitting one catch-all — valuable while these features are in\n * preview and rolling out region by region:\n *\n * - 404 / 501 — the route isn't deployed for this project's region (or the account isn't in\n * the private preview): create a project in a region where the preview is enabled, and\n * confirm your account has preview access.\n * - 503 — the route exists but is refusing right now: either the preview is still coming up,\n * or Neon is having a transient incident. Retry; if it persists it's likely an incident.\n * - anything else — generic \"not enabled for your account/region; request access\".\n *\n * Only statuses {@link isPreviewFeatureUnavailable} accepts (404/501/503) actually reach\n * this, so there is intentionally no 401/403 branch — those never classify as \"unavailable\".\n */\nfunction previewUnavailableHint(status: number | undefined): string {\n\tswitch (status) {\n\t\tcase 404:\n\t\tcase 501:\n\t\t\treturn (\n\t\t\t\t\"This usually means the preview isn't available in your project's region yet, or \" +\n\t\t\t\t\"your Neon account isn't in the private preview: create a project in a region where \" +\n\t\t\t\t\"the preview is enabled, and make sure your account has access to the preview.\"\n\t\t\t);\n\t\tcase 503:\n\t\t\treturn (\n\t\t\t\t\"The endpoint is reachable but refused the request — the preview may still be \" +\n\t\t\t\t\"coming up, or Neon may be having a transient incident. Retry shortly; if it keeps \" +\n\t\t\t\t\"failing, check https://neonstatus.com and report it to Neon support.\"\n\t\t\t);\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\"This usually means the preview isn't enabled for your Neon account or the project's \" +\n\t\t\t\t\"region. Request access to the preview, or use a project in a region where it's available.\"\n\t\t\t);\n\t}\n}\n\n/**\n * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is\n * unavailable for the project; otherwise pass the original error through unchanged so a\n * genuine failure (auth, transient 5xx, …) keeps its specific code and message.\n *\n * The message names the failing feature, summarizes the response in one short\n * `HTTP <status> <reason>` line, includes the raw Neon API message + request id (valuable\n * signal while the feature is in preview), gives status-specific guidance (see\n * {@link previewUnavailableHint}), and offers removing the feature from the policy as an\n * escape hatch. `status`/`requestId` are also kept on `details` for programmatic consumers.\n */\nexport function previewUnavailableError(\n\terr: unknown,\n\tfeatureLabel: string,\n): unknown {\n\tif (!isPreviewFeatureUnavailable(err)) return err;\n\tconst details = err instanceof PlatformError ? err.details : {};\n\tconst status =\n\t\ttypeof details.status === \"number\" ? details.status : undefined;\n\tconst neonMessage =\n\t\ttypeof details.neonMessage === \"string\"\n\t\t\t? details.neonMessage\n\t\t\t: undefined;\n\tconst requestId =\n\t\ttypeof details.requestId === \"string\" ? details.requestId : undefined;\n\n\t// One short status line + the raw API message + request id — never a stack trace.\n\tconst statusText = status ? HTTP_STATUS_TEXT[status] : undefined;\n\tconst apiParts = [\n\t\tstatus\n\t\t\t? `HTTP ${status}${statusText ? ` ${statusText}` : \"\"}`\n\t\t\t: undefined,\n\t\tneonMessage ? `Neon API said: \"${neonMessage}\"` : undefined,\n\t\trequestId ? `request id ${requestId}` : undefined,\n\t].filter((part): part is string => part !== undefined);\n\tconst apiContext = apiParts.length > 0 ? ` (${apiParts.join(\"; \")})` : \"\";\n\n\treturn new PlatformError(\n\t\tErrorCode.FeatureUnavailable,\n\t\t[\n\t\t\t`${featureLabel} is a Preview feature and isn't available for this Neon project${apiContext}.`,\n\t\t\tpreviewUnavailableHint(status),\n\t\t\t\"If you don't need it, remove the corresponding feature from the `preview` block of your neon.ts and re-run.\",\n\t\t].join(\" \"),\n\t\t{\n\t\t\tcause: err,\n\t\t\tdetails: {\n\t\t\t\tfeature: featureLabel,\n\t\t\t\t...(status !== undefined ? { status } : {}),\n\t\t\t\t...(requestId !== undefined ? { requestId } : {}),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction neonAuthResponseToSnapshot(\n\tdata: z.infer<typeof neonAuthResponseSchema>,\n): NeonAuthSnapshot {\n\tconst snapshot: NeonAuthSnapshot = {\n\t\tprojectId: data.auth_provider_project_id,\n\t\tjwksUrl: data.jwks_url,\n\t};\n\tif (data.pub_client_key !== undefined) {\n\t\tsnapshot.publishableClientKey = data.pub_client_key;\n\t}\n\tif (data.secret_server_key !== undefined) {\n\t\tsnapshot.secretServerKey = data.secret_server_key;\n\t}\n\tif (data.base_url) snapshot.baseUrl = data.base_url;\n\treturn snapshot;\n}\n\nexport function createNeonAuthRestInput(input: {\n\tdatabaseName?: string;\n}): CreateNeonAuthRestInput {\n\treturn {\n\t\tauth_provider: \"better_auth\",\n\t\t...(input.databaseName ? { database_name: input.databaseName } : {}),\n\t};\n}\n\n/**\n * Build the `multipart/form-data` body for a function deployment, matching the public\n * `FunctionDeployRequest` schema (`POST .../functions/{slug}/deployments`):\n *\n * - `zip` — the bundle as a binary part (named `bundle.zip`).\n * - `runtime` — the function runtime.\n * - `environment` — a single JSON-encoded string→string map (multipart can't carry a typed\n * object part), omitted entirely when there are no env vars.\n *\n * Pure (no I/O) so it can be unit-tested against the spec without stubbing `fetch`.\n */\nexport function buildFunctionDeployForm(input: DeployFunctionInput): FormData {\n\tconst form = new FormData();\n\tform.set(\n\t\t\"zip\",\n\t\tnew Blob([input.bundle as BlobPart], { type: \"application/zip\" }),\n\t\t\"bundle.zip\",\n\t);\n\tform.set(\"runtime\", input.runtime);\n\tif (Object.keys(input.environment).length > 0) {\n\t\tform.set(\"environment\", JSON.stringify(input.environment));\n\t}\n\treturn form;\n}\n\n/**\n * Read a response body as JSON, tolerating non-JSON. Some Neon routes return a plain-text\n * body (e.g. a 404 `\"this route does not exist\"` for a Preview feature not enabled in the\n * project/region). Parsing that with `JSON.parse` used to throw a cryptic\n * `SyntaxError: Unexpected token …`, which — because parsing happens before the `res.ok`\n * check in {@link request} — masked the real HTTP status. We instead return the raw text\n * wrapped as `{ message }` so the status-based error path in `request` / `wrapNeonError`\n * runs and produces a proper {@link PlatformError} (e.g. `NotFound`), and a non-error body\n * that simply isn't JSON degrades to text rather than crashing.\n */\nexport async function readJsonBody(res: Response): Promise<unknown> {\n\tconst text = await res.text();\n\tif (text.trim() === \"\") return {};\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch {\n\t\treturn { message: text.trim() };\n\t}\n}\n\nfunction projectToSnapshot(\n\tproject: Project | ProjectListItem,\n): NeonProjectSnapshot {\n\tconst defaults = project.default_endpoint_settings;\n\tconst snapshot: NeonProjectSnapshot = {\n\t\tid: project.id,\n\t\tname: project.name,\n\t\tregionId: project.region_id,\n\t\tpgVersion: project.pg_version,\n\t};\n\tif (project.org_id) snapshot.orgId = project.org_id;\n\tif (defaults) {\n\t\tconst compute = defaultsToComputeSettings(defaults);\n\t\tif (compute) snapshot.defaultEndpointSettings = compute;\n\t}\n\treturn snapshot;\n}\n\nfunction branchToSnapshot(branch: Branch): NeonBranchSnapshot {\n\tconst snapshot: NeonBranchSnapshot = {\n\t\tid: branch.id,\n\t\tname: branch.name,\n\t\tisDefault: branch.default,\n\t\tprotected: branch.protected === true,\n\t};\n\tif (branch.parent_id) snapshot.parentId = branch.parent_id;\n\tif (branch.expires_at) snapshot.expiresAt = branch.expires_at;\n\treturn snapshot;\n}\n\nfunction endpointToSnapshot(endpoint: Endpoint): NeonEndpointSnapshot {\n\treturn {\n\t\tid: endpoint.id,\n\t\tbranchId: endpoint.branch_id,\n\t\ttype: endpoint.type === \"read_only\" ? \"read_only\" : \"read_write\",\n\t\tautoscalingLimitMinCu:\n\t\t\tendpoint.autoscaling_limit_min_cu as ComputeSettings[\"autoscalingLimitMinCu\"],\n\t\tautoscalingLimitMaxCu:\n\t\t\tendpoint.autoscaling_limit_max_cu as ComputeSettings[\"autoscalingLimitMaxCu\"],\n\t\tsuspendTimeout: formatSuspendTimeout(endpoint.suspend_timeout_seconds),\n\t};\n}\n\nfunction roleToSnapshot(role: Role): NeonRoleSnapshot {\n\treturn {\n\t\tname: role.name,\n\t\tbranchId: role.branch_id,\n\t\tprotected: role.protected ?? false,\n\t};\n}\n\nfunction databaseToSnapshot(database: Database): NeonDatabaseSnapshot {\n\treturn {\n\t\tname: database.name,\n\t\tbranchId: database.branch_id,\n\t\townerName: database.owner_name,\n\t};\n}\n\nfunction computeSettingsToDefaults(\n\tsettings: ComputeSettings,\n): DefaultEndpointSettings {\n\tconst out: DefaultEndpointSettings = {};\n\tif (settings.autoscalingLimitMinCu !== undefined)\n\t\tout.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;\n\tif (settings.autoscalingLimitMaxCu !== undefined)\n\t\tout.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;\n\tif (settings.suspendTimeout !== undefined) {\n\t\tconst parsed = parseSuspendTimeout(settings.suspendTimeout);\n\t\tif (\"error\" in parsed) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.InvalidConfig,\n\t\t\t\t`Invalid suspendTimeout: ${parsed.error}`,\n\t\t\t);\n\t\t}\n\t\tout.suspend_timeout_seconds = parsed.seconds;\n\t}\n\treturn out;\n}\n\nfunction computeSettingsToEndpointOptions(settings: ComputeSettings): {\n\tautoscaling_limit_min_cu?: number;\n\tautoscaling_limit_max_cu?: number;\n\tsuspend_timeout_seconds?: number;\n} {\n\tconst out: {\n\t\tautoscaling_limit_min_cu?: number;\n\t\tautoscaling_limit_max_cu?: number;\n\t\tsuspend_timeout_seconds?: number;\n\t} = {};\n\tif (settings.autoscalingLimitMinCu !== undefined)\n\t\tout.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;\n\tif (settings.autoscalingLimitMaxCu !== undefined)\n\t\tout.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;\n\tif (settings.suspendTimeout !== undefined) {\n\t\tconst parsed = parseSuspendTimeout(settings.suspendTimeout);\n\t\tif (\"error\" in parsed) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.InvalidConfig,\n\t\t\t\t`Invalid suspendTimeout: ${parsed.error}`,\n\t\t\t);\n\t\t}\n\t\tout.suspend_timeout_seconds = parsed.seconds;\n\t}\n\treturn out;\n}\n\nfunction defaultsToComputeSettings(\n\tdefaults: DefaultEndpointSettings,\n): ComputeSettings | undefined {\n\tconst out: ComputeSettings = {};\n\tif (defaults.autoscaling_limit_min_cu !== undefined)\n\t\tout.autoscalingLimitMinCu =\n\t\t\tdefaults.autoscaling_limit_min_cu as ComputeSettings[\"autoscalingLimitMinCu\"];\n\tif (defaults.autoscaling_limit_max_cu !== undefined)\n\t\tout.autoscalingLimitMaxCu =\n\t\t\tdefaults.autoscaling_limit_max_cu as ComputeSettings[\"autoscalingLimitMaxCu\"];\n\tif (defaults.suspend_timeout_seconds !== undefined)\n\t\tout.suspendTimeout = formatSuspendTimeout(\n\t\t\tdefaults.suspend_timeout_seconds,\n\t\t);\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"neon-api-real.js","names":["rawListProjects","rawGetProject","rawCreateProject","rawUpdateProject","rawListProjectBranches","rawCreateProjectBranch","rawUpdateProjectBranch","rawListProjectEndpoints","rawUpdateProjectEndpoint","rawListProjectBranchRoles","rawListProjectBranchDatabases","rawGetConnectionUri","rawGetNeonAuth","rawGetProjectBranchDataApi","rawCreateProjectBranchDataApi","rawUpdateProjectBranchDataApi"],"sources":["../../src/lib/neon-api-real.ts"],"sourcesContent":["import type {\n\tDataApiSettings as ApiDataApiSettings,\n\tBranch,\n\tBranchCreateRequest,\n\tBranchCreateRequestEndpointOptions,\n\tBranchUpdateRequest,\n\tDataApiCreateRequest,\n\tDataApiReponse,\n\tDatabase,\n\tDefaultEndpointSettings,\n\tEndpoint,\n\tEndpointUpdateRequest,\n\tPgVersion,\n\tProject,\n\tProjectCreateRequest,\n\tProjectListItem,\n\tProjectUpdateRequest,\n\tRole,\n} from \"@neon/sdk\";\nimport { createNeonClient } from \"@neon/sdk\";\nimport {\n\tcreateProject as rawCreateProject,\n\tcreateProjectBranch as rawCreateProjectBranch,\n\tcreateProjectBranchDataApi as rawCreateProjectBranchDataApi,\n\tgetConnectionUri as rawGetConnectionUri,\n\tgetNeonAuth as rawGetNeonAuth,\n\tgetProject as rawGetProject,\n\tgetProjectBranchDataApi as rawGetProjectBranchDataApi,\n\tlistProjectBranchDatabases as rawListProjectBranchDatabases,\n\tlistProjectBranches as rawListProjectBranches,\n\tlistProjectBranchRoles as rawListProjectBranchRoles,\n\tlistProjectEndpoints as rawListProjectEndpoints,\n\tlistProjects as rawListProjects,\n\tupdateProject as rawUpdateProject,\n\tupdateProjectBranch as rawUpdateProjectBranch,\n\tupdateProjectBranchDataApi as rawUpdateProjectBranchDataApi,\n\tupdateProjectEndpoint as rawUpdateProjectEndpoint,\n} from \"@neon/sdk/raw\";\nimport { z } from \"zod\";\nimport { formatSuspendTimeout, parseSuspendTimeout } from \"./duration.js\";\nimport { ErrorCode, PlatformError } from \"./errors.js\";\nimport type {\n\tCreateBranchInput,\n\tCreateBucketInput,\n\tCreateCredentialInput,\n\tCreateProjectInput,\n\tDeployFunctionInput,\n\tEnableDataApiInput,\n\tGetConnectionUriInput,\n\tNeonApi,\n\tNeonAuthSnapshot,\n\tNeonBranchSnapshot,\n\tNeonBranchStorageSnapshot,\n\tNeonBucketSnapshot,\n\tNeonCredentialMeta,\n\tNeonCredentialSecret,\n\tNeonDataApiSnapshot,\n\tNeonDatabaseSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionDeploymentSnapshot,\n\tNeonFunctionSnapshot,\n\tNeonProjectSnapshot,\n\tNeonRoleSnapshot,\n\tUpdateBranchInput,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tDataApiSettings,\n} from \"./types.js\";\nimport { wrapNeonError } from \"./wrap-neon-error.js\";\n\ntype ApiClient = ReturnType<typeof createNeonClient>[\"client\"];\nconst DEFAULT_NEON_API_BASE_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Unwrap a `@neon/sdk` raw `{ data, error, response }` result into the bare body, throwing\n * on a non-2xx response. The thrown shape (`{ response: { status, data } }`) deliberately\n * matches what the package's REST fallbacks already throw, so {@link wrapNeonError} and the\n * 423 {@link retryOnLocked} retry keep working unchanged across both transports.\n */\nfunction unwrap<T>(result: {\n\tdata?: T;\n\terror?: unknown;\n\tresponse?: Response;\n}): T {\n\tconst response = result.response;\n\tif (!response || !response.ok) {\n\t\tthrow {\n\t\t\tresponse: {\n\t\t\t\tstatus: response?.status,\n\t\t\t\tdata: result.error,\n\t\t\t},\n\t\t};\n\t}\n\treturn result.data as T;\n}\n\nconst neonAuthResponseSchema = z.object({\n\tauth_provider_project_id: z.string(),\n\tpub_client_key: z.string().optional(),\n\tsecret_server_key: z.string().optional(),\n\tjwks_url: z.string(),\n\tbase_url: z.string().optional(),\n});\n\n// ─── Data API mapping (camelCase neon.ts ↔ snake_case Neon API) ───────────────\n\n/** Map our camelCase {@link DataApiSettings} onto the Neon API's snake_case `DataAPISettings`. */\nfunction dataApiSettingsToApi(settings: DataApiSettings): ApiDataApiSettings {\n\tconst out: ApiDataApiSettings = {};\n\tif (settings.dbAggregatesEnabled !== undefined)\n\t\tout.db_aggregates_enabled = settings.dbAggregatesEnabled;\n\tif (settings.dbAnonRole !== undefined)\n\t\tout.db_anon_role = settings.dbAnonRole;\n\tif (settings.dbExtraSearchPath !== undefined)\n\t\tout.db_extra_search_path = settings.dbExtraSearchPath;\n\tif (settings.dbMaxRows !== undefined) out.db_max_rows = settings.dbMaxRows;\n\tif (settings.dbSchemas !== undefined) out.db_schemas = settings.dbSchemas;\n\tif (settings.jwtRoleClaimKey !== undefined)\n\t\tout.jwt_role_claim_key = settings.jwtRoleClaimKey;\n\tif (settings.jwtCacheMaxLifetime !== undefined)\n\t\tout.jwt_cache_max_lifetime = settings.jwtCacheMaxLifetime;\n\tif (settings.openapiMode !== undefined)\n\t\tout.openapi_mode = settings.openapiMode;\n\tif (settings.serverCorsAllowedOrigins !== undefined)\n\t\tout.server_cors_allowed_origins = settings.serverCorsAllowedOrigins;\n\tif (settings.serverTimingEnabled !== undefined)\n\t\tout.server_timing_enabled = settings.serverTimingEnabled;\n\treturn out;\n}\n\n/** Narrow the API's free-form `openapi_mode` string to our literal union (else drop it). */\nfunction normalizeOpenapiMode(\n\tvalue: string,\n): DataApiSettings[\"openapiMode\"] | undefined {\n\treturn value === \"ignore-privileges\" || value === \"disabled\"\n\t\t? value\n\t\t: undefined;\n}\n\n/** Map the Neon API's snake_case `DataAPISettings` back to our camelCase {@link DataApiSettings}. */\nfunction dataApiSettingsFromApi(\n\tsettings: ApiDataApiSettings | null | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tif (settings.db_aggregates_enabled !== undefined)\n\t\tout.dbAggregatesEnabled = settings.db_aggregates_enabled;\n\tif (settings.db_anon_role !== undefined)\n\t\tout.dbAnonRole = settings.db_anon_role;\n\tif (settings.db_extra_search_path !== undefined)\n\t\tout.dbExtraSearchPath = settings.db_extra_search_path;\n\tif (settings.db_max_rows !== undefined)\n\t\tout.dbMaxRows = settings.db_max_rows;\n\tif (settings.db_schemas !== undefined) out.dbSchemas = settings.db_schemas;\n\tif (settings.jwt_role_claim_key !== undefined)\n\t\tout.jwtRoleClaimKey = settings.jwt_role_claim_key;\n\tif (settings.jwt_cache_max_lifetime !== undefined)\n\t\tout.jwtCacheMaxLifetime = settings.jwt_cache_max_lifetime;\n\tif (settings.openapi_mode !== undefined) {\n\t\tconst mode = normalizeOpenapiMode(settings.openapi_mode);\n\t\tif (mode !== undefined) out.openapiMode = mode;\n\t}\n\tif (settings.server_cors_allowed_origins !== undefined)\n\t\tout.serverCorsAllowedOrigins = settings.server_cors_allowed_origins;\n\tif (settings.server_timing_enabled !== undefined)\n\t\tout.serverTimingEnabled = settings.server_timing_enabled;\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/** Build the Neon API `DataAPICreateRequest` from our {@link EnableDataApiInput}. */\nfunction dataApiCreateRequest(\n\tinput: EnableDataApiInput | undefined,\n): DataApiCreateRequest {\n\tconst req: DataApiCreateRequest = {};\n\tif (!input) return req;\n\tif (input.authProvider !== undefined)\n\t\treq.auth_provider =\n\t\t\tinput.authProvider === \"neon\" ? \"neon_auth\" : \"external\";\n\tif (input.jwksUrl !== undefined) req.jwks_url = input.jwksUrl;\n\tif (input.providerName !== undefined)\n\t\treq.provider_name = input.providerName;\n\tif (input.jwtAudience !== undefined) req.jwt_audience = input.jwtAudience;\n\tif (input.settings) {\n\t\tconst settings = dataApiSettingsToApi(input.settings);\n\t\tif (Object.keys(settings).length > 0) req.settings = settings;\n\t}\n\treturn req;\n}\n\n/** Map a `DataAPIReponse` (GET) onto our {@link NeonDataApiSnapshot}. */\nfunction dataApiSnapshotFromResponse(\n\tdata: DataApiReponse,\n): NeonDataApiSnapshot {\n\tconst snapshot: NeonDataApiSnapshot = { url: data.url };\n\tif (data.status !== undefined) snapshot.status = data.status;\n\tconst settings = dataApiSettingsFromApi(data.settings);\n\tif (settings) snapshot.settings = settings;\n\treturn snapshot;\n}\n\n// ─── Preview: buckets ──────────────────────────────────────────────────────\n\nconst bucketSchema = z.object({\n\tname: z.string(),\n\taccess_level: z.string().optional(),\n});\nconst bucketResponseSchema = z.object({ bucket: bucketSchema });\nconst bucketsListResponseSchema = z.object({ buckets: z.array(bucketSchema) });\nconst branchStorageSchema = z.object({\n\tenabled: z.boolean().optional(),\n\ts3_endpoint: z.string(),\n\tregion: z.string(),\n\tforce_path_style: z.boolean(),\n});\n\n// ─── Preview: functions ────────────────────────────────────────────────────\n\nconst functionDeploymentSchema = z.object({\n\tid: z.number(),\n\tstatus: z.string(),\n});\nconst neonFunctionSchema = z.object({\n\tid: z.string(),\n\tslug: z.string(),\n\tname: z.string(),\n\tinvocation_url: z.string(),\n\tactive_deployment: functionDeploymentSchema.optional(),\n});\nconst functionsListResponseSchema = z.object({\n\tfunctions: z.array(neonFunctionSchema),\n});\nconst functionDeploymentResponseSchema = z.object({\n\tdeployment: functionDeploymentSchema,\n});\n\n// ─── Preview: branch-scoped credentials ─────────────────────────────────────\n\nconst credentialScopeSchema = z.enum([\n\t\"storage:read\",\n\t\"storage:write\",\n\t\"ai_gateway:invoke\",\n\t\"functions:invoke\",\n]);\nconst createCredentialResponseSchema = z.object({\n\ttoken_id: z.string(),\n\ttoken_id_short: z.string(),\n\tname: z.string().optional(),\n\tapi_token: z.string(),\n\ts3_secret_access_key: z.string(),\n\tscopes: z.array(credentialScopeSchema),\n\tbranch_id: z.string(),\n\tcreated_at: z.string(),\n\texpires_at: z.string().optional(),\n});\nconst credentialMetaSchema = z.object({\n\ttoken_id: z.string(),\n\ttoken_id_short: z.string(),\n\tname: z.string().optional(),\n\tscopes: z.array(credentialScopeSchema),\n\tprincipal_type: z.enum([\"user\", \"function\"]),\n\tfunction_id: z.string().optional(),\n\tbranch_id: z.string().optional(),\n\tcreated_at: z.string(),\n\tlast_used_at: z.string().optional(),\n\trevoked_at: z.string().optional(),\n\texpires_at: z.string().optional(),\n});\nconst listCredentialsResponseSchema = z.object({\n\tcredentials: z.array(credentialMetaSchema),\n});\n\ninterface CreateNeonAuthRestInput {\n\tauth_provider: \"better_auth\";\n\tdatabase_name?: string;\n}\n\ninterface RestConfig {\n\tapiKey: string;\n\tbaseUrl: string;\n}\n\n/**\n * Adapt `@neon/sdk` (raw layer) to the narrow {@link NeonApi} façade used by the rest of\n * this package. Constructs are restricted to whole-object read/write of just the fields we\n * model in {@link Config}; anything else stays untouched on the remote.\n */\nexport function createRealNeonApi(options: {\n\tapiKey: string;\n\tbaseUrl?: string;\n\t/**\n\t * Tuning knob for the built-in 423 retry. Defaults: ~30s of total wait spread across\n\t * 12 attempts with exponential backoff capped at 5s. Lowering this is mostly useful in\n\t * tests; raising it is rarely needed because Neon operations are usually sub-second.\n\t */\n\tretryOnLocked?: {\n\t\tmaxAttempts?: number;\n\t\tinitialDelayMs?: number;\n\t\tmaxDelayMs?: number;\n\t};\n}): NeonApi {\n\tif (!options.apiKey || options.apiKey.trim() === \"\") {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.MissingApiKey,\n\t\t\t[\n\t\t\t\t\"createRealNeonApi requires a non-empty `apiKey`.\",\n\t\t\t\t\"Generate one at https://console.neon.tech/app/settings/api-keys and pass it as { apiKey: process.env.NEON_API_KEY }.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\n\t// The SDK runs its own retries by default; this adapter does its own 423-aware\n\t// `retryOnLocked`, so disable the SDK's to avoid compounding backoff.\n\tconst client = createNeonClient({\n\t\tapiKey: options.apiKey,\n\t\tretries: 0,\n\t\t...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),\n\t}).client;\n\n\treturn new RealNeonApi(\n\t\tclient,\n\t\t{\n\t\t\tmaxAttempts: options.retryOnLocked?.maxAttempts ?? 12,\n\t\t\tinitialDelayMs: options.retryOnLocked?.initialDelayMs ?? 250,\n\t\t\tmaxDelayMs: options.retryOnLocked?.maxDelayMs ?? 5_000,\n\t\t},\n\t\t{\n\t\t\tapiKey: options.apiKey,\n\t\t\tbaseUrl: options.baseUrl ?? DEFAULT_NEON_API_BASE_URL,\n\t\t},\n\t);\n}\n\ninterface RetryConfig {\n\tmaxAttempts: number;\n\tinitialDelayMs: number;\n\tmaxDelayMs: number;\n}\n\n/**\n * Retry a function whenever it throws an HTTP 423 (Locked) — Neon's signal that a prior\n * mutation on the same resource is still in flight. Uses exponential backoff capped at\n * `maxDelayMs`. Any other error (and the last attempt) propagates.\n *\n * Exported only for tests; production callers go through the wrapped {@link NeonApi}.\n */\nexport async function retryOnLocked<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet delay = config.initialDelayMs;\n\tlet lastError: unknown;\n\tfor (let attempt = 1; attempt <= config.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tlastError = err;\n\t\t\tconst status = readHttpStatusFromError(err);\n\t\t\tif (status !== 423 || attempt === config.maxAttempts) throw err;\n\t\t\tawait sleep(delay);\n\t\t\tdelay = Math.min(delay * 2, config.maxDelayMs);\n\t\t}\n\t}\n\tthrow lastError;\n}\n\nfunction readHttpStatusFromError(err: unknown): number | undefined {\n\tif (err === null || typeof err !== \"object\") return undefined;\n\tconst response = (err as { response?: unknown }).response;\n\tif (response === null || typeof response !== \"object\") return undefined;\n\tconst status = (response as { status?: unknown }).status;\n\treturn typeof status === \"number\" ? status : undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nclass RealNeonApi implements NeonApi {\n\tconstructor(\n\t\tprivate readonly client: ApiClient,\n\t\tprivate readonly retryConfig: RetryConfig,\n\t\tprivate readonly restConfig: RestConfig,\n\t) {}\n\n\tprivate retry<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn retryOnLocked(fn, this.retryConfig);\n\t}\n\n\tprivate async call<T>(\n\t\top: string,\n\t\tfn: () => Promise<T>,\n\t\toptions: { projectId?: string; mutating?: boolean } = {},\n\t): Promise<T> {\n\t\ttry {\n\t\t\treturn options.mutating ? await this.retry(fn) : await fn();\n\t\t} catch (err) {\n\t\t\tconst wrapped = wrapNeonError(\n\t\t\t\terr,\n\t\t\t\toptions.projectId\n\t\t\t\t\t? { op, projectId: options.projectId }\n\t\t\t\t\t: { op },\n\t\t\t);\n\t\t\tthrow wrapped;\n\t\t}\n\t}\n\n\tasync listProjects(filter: {\n\t\torgId?: string;\n\t}): Promise<NeonProjectSnapshot[]> {\n\t\treturn this.call(\n\t\t\tfilter.orgId ? `listProjects(org=${filter.orgId})` : \"listProjects\",\n\t\t\tasync () => {\n\t\t\t\tconst projects: ProjectListItem[] = [];\n\t\t\t\tlet cursor: string | undefined;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst body = unwrap(\n\t\t\t\t\t\tawait rawListProjects({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\t\t...(filter.orgId\n\t\t\t\t\t\t\t\t\t? { org_id: filter.orgId }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(cursor ? { cursor } : {}),\n\t\t\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tprojects.push(...body.projects);\n\t\t\t\t\tconst next = (body as { pagination?: { next?: string } })\n\t\t\t\t\t\t.pagination?.next;\n\t\t\t\t\tif (!next || next === cursor) break;\n\t\t\t\t\tcursor = next;\n\t\t\t\t}\n\t\t\t\treturn projects.map(projectToSnapshot);\n\t\t\t},\n\t\t);\n\t}\n\n\tasync getProject(projectId: string): Promise<NeonProjectSnapshot> {\n\t\treturn this.call(\n\t\t\t`getProject(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst body = unwrap(\n\t\t\t\t\tawait rawGetProject({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(body.project);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync createProject(\n\t\tinput: CreateProjectInput,\n\t): Promise<NeonProjectSnapshot> {\n\t\tconst body: ProjectCreateRequest = {\n\t\t\tproject: {\n\t\t\t\tname: input.name,\n\t\t\t\tregion_id: input.regionId,\n\t\t\t\t...(input.pgVersion !== undefined\n\t\t\t\t\t? { pg_version: input.pgVersion as PgVersion }\n\t\t\t\t\t: {}),\n\t\t\t\t...(input.orgId ? { org_id: input.orgId } : {}),\n\t\t\t\t...(input.defaultEndpointSettings\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdefault_endpoint_settings:\n\t\t\t\t\t\t\t\tcomputeSettingsToDefaults(\n\t\t\t\t\t\t\t\t\tinput.defaultEndpointSettings,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(input.defaultBranchName\n\t\t\t\t\t? { branch: { name: input.defaultBranchName } }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t\treturn this.call(\n\t\t\t`createProject(${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawCreateProject({ client: this.client, body }),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(data.project);\n\t\t\t},\n\t\t\t{ mutating: true },\n\t\t);\n\t}\n\n\tasync updateProject(\n\t\tprojectId: string,\n\t\tinput: { name?: string; defaultEndpointSettings?: ComputeSettings },\n\t): Promise<NeonProjectSnapshot> {\n\t\tconst body: ProjectUpdateRequest = {\n\t\t\tproject: {\n\t\t\t\t...(input.name !== undefined ? { name: input.name } : {}),\n\t\t\t\t...(input.defaultEndpointSettings\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdefault_endpoint_settings:\n\t\t\t\t\t\t\t\tcomputeSettingsToDefaults(\n\t\t\t\t\t\t\t\t\tinput.defaultEndpointSettings,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t\treturn this.call(\n\t\t\t`updateProject(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProject({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn projectToSnapshot(data.project);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listBranches(projectId: string): Promise<NeonBranchSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranches(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst branches: Branch[] = [];\n\t\t\t\tlet cursor: string | undefined;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst body = unwrap(\n\t\t\t\t\t\tawait rawListProjectBranches({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t\t\t\t...(cursor ? { cursor } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbranches.push(...(body.branches as Branch[]));\n\t\t\t\t\tconst next = (body as { pagination?: { next?: string } })\n\t\t\t\t\t\t.pagination?.next;\n\t\t\t\t\tif (!next || next === cursor) break;\n\t\t\t\t\tcursor = next;\n\t\t\t\t}\n\t\t\t\treturn branches.map(branchToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync createBranch(\n\t\tprojectId: string,\n\t\tinput: CreateBranchInput,\n\t): Promise<{\n\t\tbranch: NeonBranchSnapshot;\n\t\tendpoints: NeonEndpointSnapshot[];\n\t}> {\n\t\tconst endpointOptions: BranchCreateRequestEndpointOptions | undefined =\n\t\t\tinput.computeSettings\n\t\t\t\t? {\n\t\t\t\t\t\ttype: \"read_write\",\n\t\t\t\t\t\t...computeSettingsToEndpointOptions(\n\t\t\t\t\t\t\tinput.computeSettings,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: { type: \"read_write\" };\n\n\t\tconst body: BranchCreateRequest = {\n\t\t\tbranch: {\n\t\t\t\tname: input.name,\n\t\t\t\t...(input.parentId ? { parent_id: input.parentId } : {}),\n\t\t\t\t...(input.expiresAt ? { expires_at: input.expiresAt } : {}),\n\t\t\t\t...(input.protected !== undefined\n\t\t\t\t\t? { protected: input.protected }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t\tendpoints: [endpointOptions],\n\t\t};\n\t\treturn this.call(\n\t\t\t`createBranch(${projectId}/${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawCreateProjectBranch({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tbody,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\tbranch: branchToSnapshot(data.branch),\n\t\t\t\t\tendpoints: (data.endpoints ?? []).map(endpointToSnapshot),\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync updateBranch(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: UpdateBranchInput,\n\t): Promise<NeonBranchSnapshot> {\n\t\tconst branch: BranchUpdateRequest[\"branch\"] = {};\n\t\tif (input.name !== undefined) branch.name = input.name;\n\t\tif (input.expiresAt !== undefined) branch.expires_at = input.expiresAt;\n\t\tif (input.protected !== undefined) branch.protected = input.protected;\n\t\treturn this.call(\n\t\t\t`updateBranch(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProjectBranch({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t\tbody: { branch },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn branchToSnapshot(data.branch);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listEndpoints(projectId: string): Promise<NeonEndpointSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listEndpoints(${projectId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectEndpoints({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.endpoints as Endpoint[]).map(endpointToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync updateEndpoint(\n\t\tprojectId: string,\n\t\tendpointId: string,\n\t\tsettings: ComputeSettings,\n\t): Promise<NeonEndpointSnapshot> {\n\t\tconst endpoint: EndpointUpdateRequest[\"endpoint\"] =\n\t\t\tcomputeSettingsToEndpointOptions(settings);\n\t\treturn this.call(\n\t\t\t`updateEndpoint(${projectId}/${endpointId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawUpdateProjectEndpoint({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tendpoint_id: endpointId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: { endpoint },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn endpointToSnapshot(data.endpoint);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync listBranchRoles(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonRoleSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranchRoles(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectBranchRoles({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.roles as Role[]).map(roleToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync listBranchDatabases(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonDatabaseSnapshot[]> {\n\t\treturn this.call(\n\t\t\t`listBranchDatabases(${projectId}/${branchId})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawListProjectBranchDatabases({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId, branch_id: branchId },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn (data.databases as Database[]).map(databaseToSnapshot);\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync getConnectionUri(\n\t\tprojectId: string,\n\t\tinput: GetConnectionUriInput,\n\t): Promise<{ uri: string }> {\n\t\tconst op = `getConnectionUri(${projectId}/${input.databaseName}@${input.roleName}${input.pooled ? \" pooled\" : \"\"})`;\n\t\t// Always send `pooled` explicitly. The Neon API has switched its default\n\t\t// to returning the pooled URI when the parameter is omitted, so we have\n\t\t// to be explicit to get the direct URI back.\n\t\tconst pooled = input.pooled === true;\n\t\treturn this.call(\n\t\t\top,\n\t\t\tasync () => {\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawGetConnectionUri({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: { project_id: projectId },\n\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\tdatabase_name: input.databaseName,\n\t\t\t\t\t\t\trole_name: input.roleName,\n\t\t\t\t\t\t\t...(input.branchId\n\t\t\t\t\t\t\t\t? { branch_id: input.branchId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(input.endpointId\n\t\t\t\t\t\t\t\t? { endpoint_id: input.endpointId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\tpooled,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn { uri: data.uri };\n\t\t\t},\n\t\t\t{ projectId },\n\t\t);\n\t}\n\n\tasync getNeonAuth(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonAuthSnapshot | null> {\n\t\t// `GET /projects/:pid/branches/:bid/auth` returns 404 when no integration exists.\n\t\t// Surface that as `null` so callers can branch cleanly instead of try/catch.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getNeonAuth(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = unwrap(\n\t\t\t\t\t\tawait rawGetNeonAuth({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\treturn neonAuthResponseToSnapshot(\n\t\t\t\t\t\tneonAuthResponseSchema.parse(data),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync enableNeonAuth(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: { databaseName?: string } = {},\n\t): Promise<NeonAuthSnapshot> {\n\t\t// Idempotent: if an integration already exists on the branch, the POST returns 409\n\t\t// (`Conflict`). We swallow that and re-fetch the existing snapshot so callers can\n\t\t// rely on `enableNeonAuth` to be safe to invoke from any push, including no-ops.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`enableNeonAuth(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\t// The generated `createNeonAuth` doesn't narrow this branch\n\t\t\t\t\t// endpoint to `better_auth`, so post the REST body directly.\n\t\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\t\t`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/auth`,\n\t\t\t\t\t\tcreateNeonAuthRestInput(input),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = neonAuthResponseSchema.parse(data);\n\t\t\t\t\treturn neonAuthResponseToSnapshot(parsed);\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof PlatformError &&\n\t\t\t\terr.code === ErrorCode.Conflict\n\t\t\t) {\n\t\t\t\tconst existing = await this.getNeonAuth(projectId, branchId);\n\t\t\t\tif (existing) return existing;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tprivate async postJson(path: string, body: unknown): Promise<unknown> {\n\t\treturn this.request(\"POST\", path, {\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t}\n\n\tprivate async getJson(path: string): Promise<unknown> {\n\t\treturn this.request(\"GET\", path);\n\t}\n\n\tprivate async deleteJson(path: string): Promise<unknown> {\n\t\treturn this.request(\"DELETE\", path);\n\t}\n\n\t/**\n\t * Upload a built function bundle via `multipart/form-data` to the deploy endpoint\n\t * (`POST .../functions/{slug}/deployments`). Body shape lives in the pure\n\t * {@link buildFunctionDeployForm} helper so it can be unit-tested against the spec.\n\t */\n\tprivate async postMultipart(\n\t\tpath: string,\n\t\tinput: DeployFunctionInput,\n\t): Promise<unknown> {\n\t\treturn this.request(\"POST\", path, {\n\t\t\tbody: buildFunctionDeployForm(input),\n\t\t});\n\t}\n\n\tprivate async request(\n\t\tmethod: \"GET\" | \"POST\" | \"DELETE\",\n\t\tpath: string,\n\t\tinit: { headers?: Record<string, string>; body?: BodyInit } = {},\n\t): Promise<unknown> {\n\t\tconst url = `${this.restConfig.baseUrl.replace(/\\/+$/, \"\")}${path}`;\n\t\tconst res = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.restConfig.apiKey}`,\n\t\t\t\t...(init.headers ?? {}),\n\t\t\t},\n\t\t\t...(init.body !== undefined ? { body: init.body } : {}),\n\t\t});\n\t\tconst data = await readJsonBody(res);\n\t\tif (!res.ok) {\n\t\t\tthrow {\n\t\t\t\tresponse: {\n\t\t\t\t\tstatus: res.status,\n\t\t\t\t\tdata,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn data;\n\t}\n\n\tasync getNeonDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t): Promise<NeonDataApiSnapshot | null> {\n\t\t// Same shape as getNeonAuth — 404 means \"no integration on this branch/db\", which\n\t\t// we translate to `null` for the caller.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getNeonDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\t\tasync () =>\n\t\t\t\t\tdataApiSnapshotFromResponse(\n\t\t\t\t\t\tunwrap(\n\t\t\t\t\t\t\tawait rawGetProjectBranchDataApi({\n\t\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync enableProjectBranchDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t\tinput?: EnableDataApiInput,\n\t): Promise<NeonDataApiSnapshot> {\n\t\t// Idempotent in the same shape as `enableNeonAuth`: if an integration already\n\t\t// exists, the POST returns 409 and we re-fetch the existing snapshot.\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`enableProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = unwrap(\n\t\t\t\t\t\tawait rawCreateProjectBranchDataApi({\n\t\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: dataApiCreateRequest(input),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\t// The create response only carries `url`; settings/status come from a\n\t\t\t\t\t// follow-up GET, which we leave to the caller when it needs them.\n\t\t\t\t\treturn { url: data.url };\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tif (\n\t\t\t\terr instanceof PlatformError &&\n\t\t\t\terr.code === ErrorCode.Conflict\n\t\t\t) {\n\t\t\t\tconst existing = await this.getNeonDataApi(\n\t\t\t\t\tprojectId,\n\t\t\t\t\tbranchId,\n\t\t\t\t\tdatabaseName,\n\t\t\t\t);\n\t\t\t\tif (existing) return existing;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync updateProjectBranchDataApi(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tdatabaseName: string,\n\t\tsettings: DataApiSettings,\n\t): Promise<NeonDataApiSnapshot> {\n\t\treturn await this.call(\n\t\t\t`updateProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`,\n\t\t\tasync () => {\n\t\t\t\tunwrap(\n\t\t\t\t\tawait rawUpdateProjectBranchDataApi({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: { settings: dataApiSettingsToApi(settings) },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\t// The PATCH returns an empty body; re-fetch so the caller sees the\n\t\t\t\t// post-update url/status/settings.\n\t\t\t\tconst data = unwrap(\n\t\t\t\t\tawait rawGetProjectBranchDataApi({\n\t\t\t\t\t\tclient: this.client,\n\t\t\t\t\t\tpath: {\n\t\t\t\t\t\t\tproject_id: projectId,\n\t\t\t\t\t\t\tbranch_id: branchId,\n\t\t\t\t\t\t\tdatabase_name: databaseName,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn dataApiSnapshotFromResponse(data);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\t// ─── Preview: buckets ──────────────────────────────────────────────────────\n\n\tasync listBranchBuckets(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonBucketSnapshot[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listBranchBuckets(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"buckets\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = bucketsListResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.buckets.map(bucketToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Object storage (buckets)\");\n\t\t}\n\t}\n\n\tasync createBranchBucket(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: CreateBucketInput,\n\t): Promise<NeonBucketSnapshot> {\n\t\treturn this.call(\n\t\t\t`createBranchBucket(${projectId}/${branchId}/${input.name})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"buckets\"),\n\t\t\t\t\t{\n\t\t\t\t\t\tname: input.name,\n\t\t\t\t\t\t...(input.accessLevel\n\t\t\t\t\t\t\t? { access_level: input.accessLevel }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tconst parsed = bucketResponseSchema.parse(data);\n\t\t\t\treturn bucketToSnapshot(parsed.bucket);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync deleteBranchBucket(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tbucketName: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`deleteBranchBucket(${projectId}/${branchId}/${bucketName})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"buckets\")}/${encodeURIComponent(bucketName)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync getProjectBranchStorage(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonBranchStorageSnapshot | null> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`getProjectBranchStorage(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\t`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/storage`,\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = branchStorageSchema.parse(data);\n\t\t\t\t\treturn {\n\t\t\t\t\t\ts3Endpoint: parsed.s3_endpoint,\n\t\t\t\t\t\tregion: parsed.region,\n\t\t\t\t\t\tforcePathStyle: parsed.force_path_style,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\t// 404 BranchStorageNotEnabled → storage not usable on this branch; let the\n\t\t\t// caller decide (fetchEnv throws a clear \"enable storage first\" error).\n\t\t\tif (err instanceof PlatformError && err.code === ErrorCode.NotFound)\n\t\t\t\treturn null;\n\t\t\tthrow previewUnavailableError(err, \"Object storage\");\n\t\t}\n\t}\n\n\t// ─── Preview: functions ────────────────────────────────────────────────────\n\n\tasync listBranchFunctions(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonFunctionSnapshot[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listBranchFunctions(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tbranchPreviewPath(projectId, branchId, \"functions\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = functionsListResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.functions.map(functionToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Functions\");\n\t\t}\n\t}\n\n\tasync deleteBranchFunction(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tslug: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`deleteBranchFunction(${projectId}/${branchId}/${slug})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"functions\")}/${encodeURIComponent(slug)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\tasync deployBranchFunction(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tslug: string,\n\t\tinput: DeployFunctionInput,\n\t): Promise<NeonFunctionDeploymentSnapshot> {\n\t\treturn this.call(\n\t\t\t`deployBranchFunction(${projectId}/${branchId}/${slug})`,\n\t\t\tasync () => {\n\t\t\t\tconst data = await this.postMultipart(\n\t\t\t\t\t`${branchPreviewPath(projectId, branchId, \"functions\")}/${encodeURIComponent(slug)}/deployments`,\n\t\t\t\t\tinput,\n\t\t\t\t);\n\t\t\t\tconst parsed = functionDeploymentResponseSchema.parse(data);\n\t\t\t\treturn deploymentToSnapshot(parsed.deployment);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n\n\t// ─── Preview: AI Gateway ───────────────────────────────────────────────────\n\t//\n\t// No methods: the AI Gateway is always available on a branch (credential-gated, not\n\t// per-branch provisioned). There is no control-plane enable/disable/status route — the\n\t// gateway is reached at the branch host with a credential carrying `ai_gateway:invoke`.\n\t// `preview.aiGateway` only drives that credential scope and the `OPENAI_*` /\n\t// `NEON_AI_GATEWAY_*` env vars (see `@neon/env`); nothing is provisioned here, so\n\t// `plan` / `apply` never touch an AI Gateway route and can't fail on its availability.\n\n\t// ─── Preview: branch-scoped credentials ──────────────────────────────────\n\n\tasync createCredential(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\tinput: CreateCredentialInput,\n\t): Promise<NeonCredentialSecret> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`createCredential(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.postJson(\n\t\t\t\t\t\tcredentialsPath(projectId, branchId),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tscopes: input.scopes,\n\t\t\t\t\t\t\tprincipal_type: input.principalType,\n\t\t\t\t\t\t\t...(input.functionId\n\t\t\t\t\t\t\t\t? { function_id: input.functionId }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(input.name ? { name: input.name } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = createCredentialResponseSchema.parse(data);\n\t\t\t\t\treturn createCredentialToSnapshot(parsed);\n\t\t\t\t},\n\t\t\t\t{ projectId, mutating: true },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Branch credentials\");\n\t\t}\n\t}\n\n\tasync listCredentials(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t): Promise<NeonCredentialMeta[]> {\n\t\ttry {\n\t\t\treturn await this.call(\n\t\t\t\t`listCredentials(${projectId}/${branchId})`,\n\t\t\t\tasync () => {\n\t\t\t\t\tconst data = await this.getJson(\n\t\t\t\t\t\tcredentialsPath(projectId, branchId),\n\t\t\t\t\t);\n\t\t\t\t\tconst parsed = listCredentialsResponseSchema.parse(data);\n\t\t\t\t\treturn parsed.credentials.map(credentialMetaToSnapshot);\n\t\t\t\t},\n\t\t\t\t{ projectId },\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tthrow previewUnavailableError(err, \"Branch credentials\");\n\t\t}\n\t}\n\n\tasync revokeCredential(\n\t\tprojectId: string,\n\t\tbranchId: string,\n\t\ttokenId: string,\n\t): Promise<void> {\n\t\tawait this.call(\n\t\t\t`revokeCredential(${projectId}/${branchId}/${tokenId})`,\n\t\t\tasync () => {\n\t\t\t\tawait this.deleteJson(\n\t\t\t\t\t`${credentialsPath(projectId, branchId)}/${encodeURIComponent(tokenId)}`,\n\t\t\t\t);\n\t\t\t},\n\t\t\t{ projectId, mutating: true },\n\t\t);\n\t}\n}\n\nfunction branchPreviewPath(\n\tprojectId: string,\n\tbranchId: string,\n\tresource: \"buckets\" | \"functions\",\n): string {\n\treturn `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/${resource}`;\n}\n\nfunction credentialsPath(projectId: string, branchId: string): string {\n\treturn `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/credentials`;\n}\n\nfunction createCredentialToSnapshot(\n\tdata: z.infer<typeof createCredentialResponseSchema>,\n): NeonCredentialSecret {\n\tconst snapshot: NeonCredentialSecret = {\n\t\ttokenId: data.token_id,\n\t\ttokenIdShort: data.token_id_short,\n\t\tapiToken: data.api_token,\n\t\ts3SecretAccessKey: data.s3_secret_access_key,\n\t\tscopes: data.scopes,\n\t\tbranchId: data.branch_id,\n\t\tcreatedAt: data.created_at,\n\t};\n\tif (data.name !== undefined) snapshot.name = data.name;\n\tif (data.expires_at !== undefined) snapshot.expiresAt = data.expires_at;\n\treturn snapshot;\n}\n\nfunction credentialMetaToSnapshot(\n\tdata: z.infer<typeof credentialMetaSchema>,\n): NeonCredentialMeta {\n\tconst snapshot: NeonCredentialMeta = {\n\t\ttokenId: data.token_id,\n\t\ttokenIdShort: data.token_id_short,\n\t\tscopes: data.scopes,\n\t\tprincipalType: data.principal_type,\n\t\tcreatedAt: data.created_at,\n\t};\n\tif (data.name !== undefined) snapshot.name = data.name;\n\tif (data.function_id !== undefined) snapshot.functionId = data.function_id;\n\tif (data.branch_id !== undefined) snapshot.branchId = data.branch_id;\n\tif (data.last_used_at !== undefined)\n\t\tsnapshot.lastUsedAt = data.last_used_at;\n\tif (data.revoked_at !== undefined) snapshot.revokedAt = data.revoked_at;\n\tif (data.expires_at !== undefined) snapshot.expiresAt = data.expires_at;\n\treturn snapshot;\n}\n\nfunction bucketToSnapshot(\n\tbucket: z.infer<typeof bucketSchema>,\n): NeonBucketSnapshot {\n\treturn {\n\t\tname: bucket.name,\n\t\taccessLevel: normalizeBucketAccessLevel(bucket.access_level),\n\t};\n}\n\n/**\n * The Neon API returns `access_level` as a free-form string (per the API guidelines:\n * responses use plain strings, not enums). Map the known values onto our union and treat\n * anything else as `private` — the safe default for an unrecognised access level.\n */\nfunction normalizeBucketAccessLevel(\n\tvalue: string | undefined,\n): BucketAccessLevel {\n\treturn value === \"public_read\" ? \"public_read\" : \"private\";\n}\n\nfunction functionToSnapshot(\n\tfn: z.infer<typeof neonFunctionSchema>,\n): NeonFunctionSnapshot {\n\tconst snapshot: NeonFunctionSnapshot = {\n\t\tid: fn.id,\n\t\tslug: fn.slug,\n\t\tname: fn.name,\n\t\tinvocationUrl: fn.invocation_url,\n\t};\n\tif (fn.active_deployment) {\n\t\tsnapshot.activeDeploymentId = fn.active_deployment.id;\n\t}\n\treturn snapshot;\n}\n\nfunction deploymentToSnapshot(\n\tdeployment: z.infer<typeof functionDeploymentSchema>,\n): NeonFunctionDeploymentSnapshot {\n\treturn {\n\t\tid: deployment.id,\n\t\tstatus: normalizeDeploymentStatus(deployment.status),\n\t};\n}\n\nfunction normalizeDeploymentStatus(\n\tvalue: string,\n): NeonFunctionDeploymentSnapshot[\"status\"] {\n\tswitch (value) {\n\t\tcase \"pending\":\n\t\tcase \"building\":\n\t\tcase \"completed\":\n\t\tcase \"failed\":\n\t\t\treturn value;\n\t\tdefault:\n\t\t\t// Unknown status from a newer server — surface as `pending` rather than throwing,\n\t\t\t// matching the API guideline that clients treat undocumented enum values leniently.\n\t\t\treturn \"pending\";\n\t}\n}\n\n/**\n * Whether an error from a Preview-feature read means the feature simply isn't available\n * for this project/branch/region (as opposed to a real, transient failure). Neon signals\n * this a few ways: a 404 \"this route does not exist\" (the route isn't deployed at all), or\n * a 503/4xx whose message says the platform feature is \"not available\" / \"not enabled\".\n *\n * Callers do **not** swallow this into an empty result — touching a Preview feature that\n * isn't available is surfaced as a {@link previewUnavailableError} so `plan` / `status` /\n * `pull` (and `neon dev`) fail clearly instead of, say, planning to create resources the\n * API will refuse to create.\n */\nexport function isPreviewFeatureUnavailable(err: unknown): boolean {\n\tif (!(err instanceof PlatformError)) return false;\n\tconst status = err.details.status;\n\tconst message =\n\t\ttypeof err.details.neonMessage === \"string\"\n\t\t\t? err.details.neonMessage.toLowerCase()\n\t\t\t: \"\";\n\tconst mentionsUnavailable =\n\t\tmessage.includes(\"not available\") ||\n\t\tmessage.includes(\"does not exist\") ||\n\t\tmessage.includes(\"not enabled\");\n\treturn (\n\t\tmentionsUnavailable &&\n\t\t(status === 503 || status === 404 || status === 501)\n\t);\n}\n\n/**\n * Reason phrase for the handful of HTTP statuses a Preview-feature read can surface as\n * \"unavailable\". Used to print a short `HTTP <status> <reason>` line (not a stack trace),\n * so the message reads like the API response the user would see in a tool like curl.\n */\nconst HTTP_STATUS_TEXT: Record<number, string> = {\n\t401: \"Unauthorized\",\n\t403: \"Forbidden\",\n\t404: \"Not Found\",\n\t500: \"Internal Server Error\",\n\t501: \"Not Implemented\",\n\t503: \"Service Unavailable\",\n};\n\n/**\n * Per-status guidance for a Preview feature that came back \"unavailable\". A preview can be\n * gated several different ways and the HTTP status is the best signal for which, so we tailor\n * the next step instead of emitting one catch-all — valuable while these features are in\n * preview and rolling out region by region:\n *\n * - 404 / 501 — the route isn't deployed for this project's region (or the account isn't in\n * the private preview): create a project in a region where the preview is enabled, and\n * confirm your account has preview access.\n * - 503 — the route exists but is refusing right now: either the preview is still coming up,\n * or Neon is having a transient incident. Retry; if it persists it's likely an incident.\n * - anything else — generic \"not enabled for your account/region; request access\".\n *\n * Only statuses {@link isPreviewFeatureUnavailable} accepts (404/501/503) actually reach\n * this, so there is intentionally no 401/403 branch — those never classify as \"unavailable\".\n */\nfunction previewUnavailableHint(status: number | undefined): string {\n\tswitch (status) {\n\t\tcase 404:\n\t\tcase 501:\n\t\t\treturn (\n\t\t\t\t\"This usually means the preview isn't available in your project's region yet, or \" +\n\t\t\t\t\"your Neon account isn't in the private preview: create a project in a region where \" +\n\t\t\t\t\"the preview is enabled, and make sure your account has access to the preview.\"\n\t\t\t);\n\t\tcase 503:\n\t\t\treturn (\n\t\t\t\t\"The endpoint is reachable but refused the request — the preview may still be \" +\n\t\t\t\t\"coming up, or Neon may be having a transient incident. Retry shortly; if it keeps \" +\n\t\t\t\t\"failing, check https://neonstatus.com and report it to Neon support.\"\n\t\t\t);\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\"This usually means the preview isn't enabled for your Neon account or the project's \" +\n\t\t\t\t\"region. Request access to the preview, or use a project in a region where it's available.\"\n\t\t\t);\n\t}\n}\n\n/**\n * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is\n * unavailable for the project; otherwise pass the original error through unchanged so a\n * genuine failure (auth, transient 5xx, …) keeps its specific code and message.\n *\n * The message names the failing feature, summarizes the response in one short\n * `HTTP <status> <reason>` line, includes the raw Neon API message + request id (valuable\n * signal while the feature is in preview), gives status-specific guidance (see\n * {@link previewUnavailableHint}), and offers removing the feature from the policy as an\n * escape hatch. `status`/`requestId` are also kept on `details` for programmatic consumers.\n */\nexport function previewUnavailableError(\n\terr: unknown,\n\tfeatureLabel: string,\n): unknown {\n\tif (!isPreviewFeatureUnavailable(err)) return err;\n\tconst details = err instanceof PlatformError ? err.details : {};\n\tconst status =\n\t\ttypeof details.status === \"number\" ? details.status : undefined;\n\tconst neonMessage =\n\t\ttypeof details.neonMessage === \"string\"\n\t\t\t? details.neonMessage\n\t\t\t: undefined;\n\tconst requestId =\n\t\ttypeof details.requestId === \"string\" ? details.requestId : undefined;\n\n\t// One short status line + the raw API message + request id — never a stack trace.\n\tconst statusText = status ? HTTP_STATUS_TEXT[status] : undefined;\n\tconst apiParts = [\n\t\tstatus\n\t\t\t? `HTTP ${status}${statusText ? ` ${statusText}` : \"\"}`\n\t\t\t: undefined,\n\t\tneonMessage ? `Neon API said: \"${neonMessage}\"` : undefined,\n\t\trequestId ? `request id ${requestId}` : undefined,\n\t].filter((part): part is string => part !== undefined);\n\tconst apiContext = apiParts.length > 0 ? ` (${apiParts.join(\"; \")})` : \"\";\n\n\treturn new PlatformError(\n\t\tErrorCode.FeatureUnavailable,\n\t\t[\n\t\t\t`${featureLabel} is a Preview feature and isn't available for this Neon project${apiContext}.`,\n\t\t\tpreviewUnavailableHint(status),\n\t\t\t\"If you don't need it, remove the corresponding feature from the `preview` block of your neon.ts and re-run.\",\n\t\t].join(\" \"),\n\t\t{\n\t\t\tcause: err,\n\t\t\tdetails: {\n\t\t\t\tfeature: featureLabel,\n\t\t\t\t...(status !== undefined ? { status } : {}),\n\t\t\t\t...(requestId !== undefined ? { requestId } : {}),\n\t\t\t},\n\t\t},\n\t);\n}\n\nfunction neonAuthResponseToSnapshot(\n\tdata: z.infer<typeof neonAuthResponseSchema>,\n): NeonAuthSnapshot {\n\tconst snapshot: NeonAuthSnapshot = {\n\t\tprojectId: data.auth_provider_project_id,\n\t\tjwksUrl: data.jwks_url,\n\t};\n\tif (data.pub_client_key !== undefined) {\n\t\tsnapshot.publishableClientKey = data.pub_client_key;\n\t}\n\tif (data.secret_server_key !== undefined) {\n\t\tsnapshot.secretServerKey = data.secret_server_key;\n\t}\n\tif (data.base_url) snapshot.baseUrl = data.base_url;\n\treturn snapshot;\n}\n\nexport function createNeonAuthRestInput(input: {\n\tdatabaseName?: string;\n}): CreateNeonAuthRestInput {\n\treturn {\n\t\tauth_provider: \"better_auth\",\n\t\t...(input.databaseName ? { database_name: input.databaseName } : {}),\n\t};\n}\n\n/**\n * Build the `multipart/form-data` body for a function deployment, matching the public\n * `FunctionDeployRequest` schema (`POST .../functions/{slug}/deployments`):\n *\n * - `zip` — the bundle as a binary part (named `bundle.zip`).\n * - `runtime` — the function runtime.\n * - `environment` — a single JSON-encoded string→string map (multipart can't carry a typed\n * object part), omitted entirely when there are no env vars.\n *\n * Pure (no I/O) so it can be unit-tested against the spec without stubbing `fetch`.\n */\nexport function buildFunctionDeployForm(input: DeployFunctionInput): FormData {\n\tconst form = new FormData();\n\tform.set(\n\t\t\"zip\",\n\t\tnew Blob([input.bundle as BlobPart], { type: \"application/zip\" }),\n\t\t\"bundle.zip\",\n\t);\n\tform.set(\"runtime\", input.runtime);\n\tif (Object.keys(input.environment).length > 0) {\n\t\tform.set(\"environment\", JSON.stringify(input.environment));\n\t}\n\treturn form;\n}\n\n/**\n * Read a response body as JSON, tolerating non-JSON. Some Neon routes return a plain-text\n * body (e.g. a 404 `\"this route does not exist\"` for a Preview feature not enabled in the\n * project/region). Parsing that with `JSON.parse` used to throw a cryptic\n * `SyntaxError: Unexpected token …`, which — because parsing happens before the `res.ok`\n * check in {@link request} — masked the real HTTP status. We instead return the raw text\n * wrapped as `{ message }` so the status-based error path in `request` / `wrapNeonError`\n * runs and produces a proper {@link PlatformError} (e.g. `NotFound`), and a non-error body\n * that simply isn't JSON degrades to text rather than crashing.\n */\nexport async function readJsonBody(res: Response): Promise<unknown> {\n\tconst text = await res.text();\n\tif (text.trim() === \"\") return {};\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch {\n\t\treturn { message: text.trim() };\n\t}\n}\n\nfunction projectToSnapshot(\n\tproject: Project | ProjectListItem,\n): NeonProjectSnapshot {\n\tconst defaults = project.default_endpoint_settings;\n\tconst snapshot: NeonProjectSnapshot = {\n\t\tid: project.id,\n\t\tname: project.name,\n\t\tregionId: project.region_id,\n\t\tpgVersion: project.pg_version,\n\t};\n\tif (project.org_id) snapshot.orgId = project.org_id;\n\tif (defaults) {\n\t\tconst compute = defaultsToComputeSettings(defaults);\n\t\tif (compute) snapshot.defaultEndpointSettings = compute;\n\t}\n\treturn snapshot;\n}\n\nfunction branchToSnapshot(branch: Branch): NeonBranchSnapshot {\n\tconst snapshot: NeonBranchSnapshot = {\n\t\tid: branch.id,\n\t\tname: branch.name,\n\t\tisDefault: branch.default,\n\t\tprotected: branch.protected === true,\n\t};\n\tif (branch.parent_id) snapshot.parentId = branch.parent_id;\n\tif (branch.expires_at) snapshot.expiresAt = branch.expires_at;\n\treturn snapshot;\n}\n\nfunction endpointToSnapshot(endpoint: Endpoint): NeonEndpointSnapshot {\n\treturn {\n\t\tid: endpoint.id,\n\t\tbranchId: endpoint.branch_id,\n\t\ttype: endpoint.type === \"read_only\" ? \"read_only\" : \"read_write\",\n\t\tautoscalingLimitMinCu:\n\t\t\tendpoint.autoscaling_limit_min_cu as ComputeSettings[\"autoscalingLimitMinCu\"],\n\t\tautoscalingLimitMaxCu:\n\t\t\tendpoint.autoscaling_limit_max_cu as ComputeSettings[\"autoscalingLimitMaxCu\"],\n\t\tsuspendTimeout: formatSuspendTimeout(endpoint.suspend_timeout_seconds),\n\t};\n}\n\nfunction roleToSnapshot(role: Role): NeonRoleSnapshot {\n\treturn {\n\t\tname: role.name,\n\t\tbranchId: role.branch_id,\n\t\tprotected: role.protected ?? false,\n\t};\n}\n\nfunction databaseToSnapshot(database: Database): NeonDatabaseSnapshot {\n\treturn {\n\t\tname: database.name,\n\t\tbranchId: database.branch_id,\n\t\townerName: database.owner_name,\n\t};\n}\n\nfunction computeSettingsToDefaults(\n\tsettings: ComputeSettings,\n): DefaultEndpointSettings {\n\tconst out: DefaultEndpointSettings = {};\n\tif (settings.autoscalingLimitMinCu !== undefined)\n\t\tout.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;\n\tif (settings.autoscalingLimitMaxCu !== undefined)\n\t\tout.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;\n\tif (settings.suspendTimeout !== undefined) {\n\t\tconst parsed = parseSuspendTimeout(settings.suspendTimeout);\n\t\tif (\"error\" in parsed) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.InvalidConfig,\n\t\t\t\t`Invalid suspendTimeout: ${parsed.error}`,\n\t\t\t);\n\t\t}\n\t\tout.suspend_timeout_seconds = parsed.seconds;\n\t}\n\treturn out;\n}\n\nfunction computeSettingsToEndpointOptions(settings: ComputeSettings): {\n\tautoscaling_limit_min_cu?: number;\n\tautoscaling_limit_max_cu?: number;\n\tsuspend_timeout_seconds?: number;\n} {\n\tconst out: {\n\t\tautoscaling_limit_min_cu?: number;\n\t\tautoscaling_limit_max_cu?: number;\n\t\tsuspend_timeout_seconds?: number;\n\t} = {};\n\tif (settings.autoscalingLimitMinCu !== undefined)\n\t\tout.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;\n\tif (settings.autoscalingLimitMaxCu !== undefined)\n\t\tout.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;\n\tif (settings.suspendTimeout !== undefined) {\n\t\tconst parsed = parseSuspendTimeout(settings.suspendTimeout);\n\t\tif (\"error\" in parsed) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.InvalidConfig,\n\t\t\t\t`Invalid suspendTimeout: ${parsed.error}`,\n\t\t\t);\n\t\t}\n\t\tout.suspend_timeout_seconds = parsed.seconds;\n\t}\n\treturn out;\n}\n\nfunction defaultsToComputeSettings(\n\tdefaults: DefaultEndpointSettings,\n): ComputeSettings | undefined {\n\tconst out: ComputeSettings = {};\n\tif (defaults.autoscaling_limit_min_cu !== undefined)\n\t\tout.autoscalingLimitMinCu =\n\t\t\tdefaults.autoscaling_limit_min_cu as ComputeSettings[\"autoscalingLimitMinCu\"];\n\tif (defaults.autoscaling_limit_max_cu !== undefined)\n\t\tout.autoscalingLimitMaxCu =\n\t\t\tdefaults.autoscaling_limit_max_cu as ComputeSettings[\"autoscalingLimitMaxCu\"];\n\tif (defaults.suspend_timeout_seconds !== undefined)\n\t\tout.suspendTimeout = formatSuspendTimeout(\n\t\t\tdefaults.suspend_timeout_seconds,\n\t\t);\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n"],"mappings":"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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ type ServiceToggleInput = boolean | ServiceToggle;
|
|
|
135
135
|
* (a present toggle defaults to enabled)
|
|
136
136
|
* - the bare `boolean | … | undefined` (no literal info) → `false`
|
|
137
137
|
*
|
|
138
|
-
* Shared by the {@link Config} static cross-field checks and the `@
|
|
138
|
+
* Shared by the {@link Config} static cross-field checks and the `@neon/env`
|
|
139
139
|
* `NeonEnv` namespace derivation, so both read "is this service on?" identically.
|
|
140
140
|
*/
|
|
141
141
|
type ServiceEnabled<T> = [T] extends [false] ? false : [T] extends [{
|
package/dist/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":["/**\n * Valid Neon Compute Unit values.\n * Most plans support 0.25, 0.5, 1, 2, 4, 8. Higher values may be available on Business plans.\n */\nexport type ComputeUnit = 0.25 | 0.5 | 1 | 2 | 4 | 8;\n\n/** Time units accepted in a {@link DurationString}: seconds, minutes, hours, days, weeks. */\nexport type DurationUnit = \"s\" | \"m\" | \"h\" | \"d\" | \"w\";\n\n/**\n * A Neon duration string: a positive integer **followed by a unit** — `s` (seconds),\n * `m` (minutes), `h` (hours), `d` (days), or `w` (weeks). Used by\n * {@link ComputeSettings.suspendTimeout} and {@link BranchTuning.ttl}.\n *\n * A **unit is required**: a bare numeric string like `\"7\"` is rejected at the type level. To\n * express a raw number of seconds, pass a `number` (`300`) — not a string (`\"300\"`). This\n * removes the old ambiguity where `\"7\"` silently meant 7 *seconds* instead of, say, `\"7d\"`.\n *\n * @example \"5m\" // 5 minutes\n * @example \"1h\" // 1 hour\n * @example \"7d\" // 7 days\n */\nexport type DurationString = `${number}${DurationUnit}`;\n\n/**\n * Autocomplete suggestions for {@link ComputeSettings.suspendTimeout}. Every value sits inside\n * the Neon API's allowed scale-to-zero band: **60s–604800s** (1 minute – 1 week). This is *not*\n * a closed set — the field also accepts any other {@link DurationString} or a `number` of\n * seconds; out-of-range values type-check but are rejected at apply time.\n */\ntype SuspendTimeoutSuggestion =\n\t| \"1m\"\n\t| \"5m\"\n\t| \"15m\"\n\t| \"30m\"\n\t| \"1h\"\n\t| \"6h\"\n\t| \"12h\"\n\t| \"1d\"\n\t| \"7d\";\n\n/**\n * Autocomplete suggestions for {@link BranchTuning.ttl}. Every value sits within the Neon API's\n * branch-expiration limit (**max 30 days** from creation; the Console's own presets are 1h / 1d\n * / 7d). This is *not* a closed set — the field also accepts any other {@link DurationString} or\n * a `number` of seconds; values over 30 days are rejected at apply time.\n */\ntype TtlSuggestion = \"1h\" | \"6h\" | \"12h\" | \"1d\" | \"3d\" | \"7d\" | \"14d\" | \"30d\";\n\n/**\n * Compose a field's duration type: its curated autocomplete `Suggestions` plus the open\n * `DurationString` template (so any `<integer><unit>` string still type-checks) and a `number`\n * of seconds. Intersecting the template arm with `NonNullable<unknown>` stops TypeScript from\n * collapsing the literal suggestions into the template, which is what preserves the autocomplete.\n */\ntype DurationField<Suggestions extends DurationString> =\n\t| Suggestions\n\t| (DurationString & NonNullable<unknown>)\n\t| number;\n\n/**\n * Compute settings applied to the read/write endpoint of a branch.\n *\n * Mirrors the subset of {@link https://api-docs.neon.tech/reference/getting-started-with-neon-api Neon endpoint}\n * fields that we expose as IaC primitives. Anything left undefined falls back to the project's\n * `default_endpoint_settings` (which themselves fall back to Neon platform defaults).\n */\nexport interface ComputeSettings {\n\t/**\n\t * Minimum number of Compute Units. Set to 0.25 for true scale-to-zero.\n\t * @example 0.25 // scale-to-zero\n\t * @example 1 // always-on with 1 CU minimum\n\t */\n\tautoscalingLimitMinCu?: ComputeUnit;\n\t/**\n\t * Maximum number of Compute Units for autoscaling.\n\t * @example 2\n\t * @example 8\n\t */\n\tautoscalingLimitMaxCu?: ComputeUnit;\n\t/**\n\t * How long an idle compute waits before suspending (Neon's scale-to-zero). Accepts a\n\t * {@link DurationString} (autocompletes common values), a number of seconds, or `false`.\n\t *\n\t * - `false` — never suspend (always-on compute)\n\t * - {@link DurationString} — e.g. `\"5m\"`; autocompletes the in-range values `\"1m\"`, `\"5m\"`,\n\t * `\"15m\"`, `\"30m\"`, `\"1h\"`, `\"6h\"`, `\"12h\"`, `\"1d\"`, `\"7d\"`, and accepts any other\n\t * `<integer><unit>` (units: `s`, `m`, `h`, `d`, `w`). A **unit is required** — for raw\n\t * seconds pass a `number`, not a string.\n\t * - `number` — custom timeout in **seconds**, must be in `60`–`604800` (1 minute to 1 week)\n\t * - `undefined` — use the Neon platform default (currently 300s / 5 minutes)\n\t *\n\t * Whichever form you use, the resolved timeout must fall in `60`–`604800` seconds (the Neon\n\t * API limit); the suggestions are all within that band, anything else is checked at apply.\n\t *\n\t * @example false // never suspend (always-on)\n\t * @example \"5m\" // suspend after 5 minutes idle\n\t * @example \"1h\" // suspend after 1 hour idle\n\t * @example 300 // 5 minutes, expressed in seconds\n\t */\n\tsuspendTimeout?: false | DurationField<SuspendTimeoutSuggestion>;\n}\n\n/**\n * Read-only descriptor of the branch a {@link Config} policy is being evaluated for — the\n * `branch` argument passed to your `defineConfig({ branch: (branch) => … })` closure. It describes\n * **which** branch this invocation decides for; it is not a live branch handle and must not\n * be mutated. Switch on its fields and return the desired {@link BranchConfig}.\n */\nexport interface BranchTarget {\n\t/** Branch name being evaluated. For `branch dev`, this is the generated branch name. */\n\tname: string;\n\t/** Neon branch id when the branch already exists. Undefined during pre-create eval. */\n\tid?: string;\n\t/** Whether this branch already exists on Neon. */\n\texists: boolean;\n\t/** Parent branch id from Neon when known. */\n\tparentId?: string;\n\t/** Whether Neon marks this branch as the project default. */\n\tisDefault?: boolean;\n\t/** Whether Neon currently marks this branch protected. */\n\tisProtected?: boolean;\n\t/** Current expiration timestamp from Neon, when set. */\n\texpiresAt?: string;\n}\n\n/**\n * Object form of a branch-scoped service toggle. `{}` or `{ enabled: true }` enables it;\n * `{ enabled: false }` opts out. Used as the object half of {@link ServiceToggleInput}.\n */\nexport interface ServiceToggle {\n\t/** Defaults to `true` when the service namespace is present. Set `false` to opt out. */\n\tenabled?: boolean;\n}\n\n/**\n * How a branch-scoped service (Neon Auth, Data API, AI Gateway) is toggled in a policy.\n *\n * - `true` / `{}` / `{ enabled: true }` — enabled.\n * - `false` / `{ enabled: false }` — disabled.\n * - omitted (`undefined`) — not part of the policy at all.\n *\n * These toggles are **static** (they live in the top-level `defineConfig({ … })` object,\n * not in the per-branch `branch` closure) so the secret set they imply can be derived at\n * the type level — that's what makes `NeonEnv<typeof config>` exact.\n */\nexport type ServiceToggleInput = boolean | ServiceToggle;\n\n/**\n * Resolve a **static** service toggle (`true` / `false` / `{ enabled?: boolean }` / object /\n * `undefined`) to a type-level boolean. The tuple wrapping (`[T] extends […]`) disables\n * distribution so a union/`undefined` is judged as a single 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 | … | undefined` (no literal info) → `false`\n *\n * Shared by the {@link Config} static cross-field checks and the `@neondatabase/env`\n * `NeonEnv` namespace derivation, so both read \"is this service on?\" identically.\n */\nexport type ServiceEnabled<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\nexport interface PostgresConfig {\n\tcomputeSettings?: ComputeSettings;\n}\n\n/**\n * Authentication providers a Data API integration can verify JWTs against, as written in\n * `neon.ts`. Friendly authoring values (mapped to the Neon API's `neon_auth` / `external`\n * at the API boundary):\n *\n * - `\"neon\"` — verify tokens minted by **Neon Auth** on the same branch. Neon supplies the\n * JWKS URL / provider wiring for you, so the `jwksUrl` / `providerName` / `jwtAudience`\n * fields are forbidden (a type error) on this variant — and the policy must also enable\n * top-level `auth` (Neon Auth) so the tokens exist.\n * - `\"external\"` — verify tokens from a third-party IdP (Clerk, Stytch, Auth0, …). You\n * provide `jwksUrl` (and optionally `providerName` / `jwtAudience`).\n */\nexport const DATA_API_AUTH_PROVIDERS = [\"neon\", \"external\"] as const;\nexport type DataApiAuthProvider = (typeof DATA_API_AUTH_PROVIDERS)[number];\n\n/**\n * Reusable runtime settings for a Data API integration (the Neon API `DataAPISettings`,\n * camelCased to match the rest of `neon.ts`). Every field is optional; omitted fields keep\n * the Neon defaults shown below. These are the **only** Data API fields that can change on\n * an already-enabled integration — drift here is reconciled as an *update* (requires\n * `updateExisting` / `--update-existing`); the create-only auth wiring above cannot.\n */\nexport interface DataApiSettings {\n\t/** Enable the aggregates feature (`db_aggregates_enabled`). Default `true`. */\n\tdbAggregatesEnabled?: boolean;\n\t/** Database role used for anonymous requests (`db_anon_role`). Default `\"anonymous\"`. */\n\tdbAnonRole?: string;\n\t/** Extra schemas appended to the search path (`db_extra_search_path`). */\n\tdbExtraSearchPath?: string;\n\t/** Maximum rows returned in a single request (`db_max_rows`). */\n\tdbMaxRows?: number;\n\t/** Schemas exposed via the API (`db_schemas`). Default `[\"public\"]`. */\n\tdbSchemas?: string[];\n\t/** JWT claim key used for role extraction (`jwt_role_claim_key`). Default `\".role\"`. */\n\tjwtRoleClaimKey?: string;\n\t/** Maximum lifetime of the JWT cache, in seconds (`jwt_cache_max_lifetime`). */\n\tjwtCacheMaxLifetime?: number;\n\t/** OpenAPI spec mode (`openapi_mode`). Default `\"disabled\"`. */\n\topenapiMode?: \"ignore-privileges\" | \"disabled\";\n\t/** CORS allowed origins (`server_cors_allowed_origins`). */\n\tserverCorsAllowedOrigins?: string;\n\t/** Emit server-timing headers (`server_timing_enabled`). */\n\tserverTimingEnabled?: boolean;\n}\n\n/** Fields shared by every {@link DataApiConfig} variant. */\ninterface DataApiConfigBase {\n\t/** Defaults to `true` when the `dataApi` namespace is present. Set `false` to opt out. */\n\tenabled?: boolean;\n\t/** Reusable runtime settings. Drift here is reconciled as an update. */\n\tsettings?: DataApiSettings;\n}\n\n/**\n * Data API verified by **Neon Auth** (`authProvider: \"neon\"`, the default). The external\n * IdP fields are statically forbidden (`?: never`) because Neon supplies them; declaring any\n * of them is a type error directing you to `authProvider: \"external\"`.\n */\nexport interface DataApiNeonAuthConfig extends DataApiConfigBase {\n\tauthProvider?: \"neon\";\n\t/** Forbidden with `authProvider: \"neon\"` — Neon provides the JWKS URL. */\n\tjwksUrl?: never;\n\t/** Forbidden with `authProvider: \"neon\"` — the provider is Neon Auth. */\n\tproviderName?: never;\n\t/** Forbidden with `authProvider: \"neon\"` — Neon manages the audience. */\n\tjwtAudience?: never;\n}\n\n/**\n * Data API verified by an **external** IdP (`authProvider: \"external\"`). You provide the\n * JWKS URL (and optionally a provider label / expected audience).\n */\nexport interface DataApiExternalAuthConfig extends DataApiConfigBase {\n\tauthProvider: \"external\";\n\t/** URL that publishes the IdP's JWKS (JSON Web Key Set). */\n\tjwksUrl?: string;\n\t/** Human label for the IdP (e.g. \"Clerk\", \"Stytch\", \"Auth0\"). */\n\tproviderName?: string;\n\t/**\n\t * Expected `aud` claim. ⚠️ This only **rejects** tokens carrying a *different* audience;\n\t * tokens with no `aud` claim are still accepted.\n\t */\n\tjwtAudience?: string;\n}\n\n/**\n * Object form of the `dataApi` toggle. A discriminated union on {@link DataApiAuthProvider}:\n * the `\"neon\"` variant forbids the external-IdP fields, the `\"external\"` variant allows them.\n */\nexport type DataApiConfig = DataApiNeonAuthConfig | DataApiExternalAuthConfig;\n\n/**\n * How the Data API is toggled in a policy: a bare boolean (like the other service toggles)\n * or the richer {@link DataApiConfig} object. `true` / `{}` / `{ enabled: true }` enable it\n * with Neon defaults; `false` / `{ enabled: false }` opt out.\n */\nexport type DataApiInput = boolean | DataApiConfig;\n\n/**\n * Supported function runtimes. Mirrors the Neon Functions deploy API `runtime` enum.\n * Only `nodejs24` exists today; kept as a union so adding runtimes later is a\n * non-breaking, type-checked change.\n */\nexport type FunctionRuntime = \"nodejs24\";\n\n/**\n * Local-development settings for a function, used by `neon dev` when it serves every\n * function declared in `neon.ts` (i.e. invoked with no `--source`). Never affects deploy.\n */\nexport interface FunctionDevConfig {\n\t/**\n\t * Port the local server binds. Bound exactly (and `neon dev` fails loudly if it is taken)\n\t * when set; a free port is found automatically when omitted.\n\t */\n\tport?: number;\n}\n\n/**\n * Static definition of a Neon Function (Preview feature). Declares that the function\n * **exists** on every branch; its branch-unique slug is the **record key** in\n * {@link PreviewInput.functions} (not a field here), so slugs are statically enumerable,\n * cannot duplicate, and the `branch` closure can only tune slugs that are declared here.\n *\n * A function is invoked like a Cloudflare/Vercel handler — its source module\n * `export default { fetch }` or `export async function handler(req): Response`. The\n * `source` path is bundled (esbuild) and uploaded as a deployment; the newest deployment\n * becomes active.\n *\n * Runtime tuning is **not** here — it varies per branch and lives in the `branch` closure\n * (see {@link FunctionTuning}). Memory is fixed by the platform policy for now and is not\n * user-configurable.\n */\nexport interface FunctionDef {\n\t/** Free-form display name. @example \"Hello World\" */\n\tname: string;\n\t/**\n\t * Path to the function's entry module, **relative to `neon.ts`** (or absolute). The\n\t * module's default export (`{ fetch }`) or `handler` export is the function entry. This\n\t * path is resolved against the loaded `neon.ts` location and bundled with esbuild at\n\t * deploy time.\n\t *\n\t * We require a string path rather than an imported handler because a JS function value\n\t * carries no reference back to its source file, so esbuild has nothing to bundle from.\n\t * @example \"./functions/hello-world.ts\"\n\t */\n\tsource: string;\n\t/**\n\t * Environment variables injected into the deployed function, keyed by the var name the\n\t * function reads at runtime. The **keys** are static (preserved at the type level so\n\t * `parseEnv(config, \"<slug>\").function.<key>` is typed); the **values** are arbitrary\n\t * strings evaluated when `neon.ts` is loaded (typically `process.env.X`) and uploaded\n\t * at `config apply`. Every value must be a defined string — a `process.env.X` that is\n\t * `undefined` (unset) errors at validation time rather than silently shipping\n\t * `undefined`.\n\t * @example { resendApiKey: process.env.RESEND_API_KEY ?? \"\" }\n\t */\n\tenv?: Record<string, string>;\n\t/**\n\t * Local-development settings used by `neon dev` when serving every function from\n\t * `neon.ts`. Ignored at deploy time. See {@link FunctionDevConfig}.\n\t */\n\tdev?: FunctionDevConfig;\n}\n\n/**\n * A single capability a branch-scoped service credential may exercise (Preview). A\n * credential is granted a set of these and may only perform the listed actions. Mirrors\n * the Neon API `CredentialScope` enum (`x-stability-level: beta`):\n *\n * - `storage:read` / `storage:write` — object-storage (bucket) access via the S3 key.\n * - `ai_gateway:invoke` — call the AI Gateway with the bearer `api_token`.\n * - `functions:invoke` — invoke Neon Functions with the bearer `api_token`.\n *\n * The set a policy needs is derived from its enabled Preview features (see\n * {@link deriveCredentialScopes}); it is never authored by hand.\n */\nexport type CredentialScope =\n\t| \"storage:read\"\n\t| \"storage:write\"\n\t| \"ai_gateway:invoke\"\n\t| \"functions:invoke\";\n\n/**\n * Who a credential acts as. `user` is the developer/app principal minted for local dev and\n * app bootstrap (`fetchEnv` / `env pull`); `function` is a deployed-function principal\n * (carries a `function_id`). The env tooling only mints `user` credentials today.\n */\nexport type CredentialPrincipalType = \"user\" | \"function\";\n\n/** Anonymous-access level for a branchable object-storage bucket. */\nexport type BucketAccessLevel = \"private\" | \"public_read\";\n\n/**\n * Static definition of a branchable object-storage bucket (Preview feature). The bucket's\n * name is the **record key** in {@link PreviewInput.buckets}, so names are statically\n * enumerable and cannot duplicate.\n */\nexport interface BucketDef {\n\t/**\n\t * Anonymous access level. `private` (default) requires authenticated reads/writes;\n\t * `public_read` allows anonymous GetObject/HeadObject.\n\t */\n\taccess?: BucketAccessLevel;\n}\n\n/**\n * Static, branch-scoped **Preview** features. Grouped under `preview` to signal they are\n * backed by Neon `x-stability-level: beta` endpoints and may change before GA. Everything\n * here is existential (it determines what exists on the branch); per-branch tuning lives in\n * the `branch` closure.\n */\nexport interface PreviewInput {\n\t/** Enable/disable the AI Gateway on the branch (toggle, like auth / dataApi). */\n\taiGateway?: ServiceToggleInput;\n\t/** Functions to deploy, keyed by branch-unique slug (`^[a-z0-9]{1,20}$`). */\n\tfunctions?: Record<string, FunctionDef>;\n\t/** Object-storage buckets to create, keyed by bucket name. */\n\tbuckets?: Record<string, BucketDef>;\n}\n\n/**\n * Per-branch deploy tuning for a single function. Returned (per slug) by the `branch`\n * closure. Deliberately **cannot** change the function's existence, source, name, env\n * **keys**, or memory — only runtime selection is currently configurable — so the static\n * secret/function set stays sound.\n */\nexport interface FunctionTuning {\n\t/** Runtime to execute the function with. Defaults to `\"nodejs24\"`. */\n\truntime?: FunctionRuntime;\n}\n\n/**\n * Per-branch tuning of Preview features. Only existing function slugs (those declared in\n * the static {@link PreviewInput.functions}) may be tuned — `Slug` is constrained to the\n * declared keys by {@link BranchTuningFn}.\n */\nexport interface PreviewTuning<Slug extends string = string> {\n\tfunctions?: Partial<Record<Slug, FunctionTuning>>;\n}\n\n/**\n * The per-branch tuning object returned by the `branch` closure. It can adjust branch\n * lifecycle (`parent`, `ttl`, `protected`), Postgres compute settings, and per-function\n * deploy tuning — but **cannot** add/remove services or functions. That guarantee is what\n * keeps the static secret set (and therefore `NeonEnv`) exact.\n */\nexport interface BranchTuning<Slug extends string = string> {\n\t/** Parent branch name used when creating a new branch. Not a Postgres setting. */\n\tparent?: string;\n\t/**\n\t * Branch time-to-live: how long after creation the branch should auto-expire. Applied\n\t * when creating a new branch and reconciled on existing branches (when `updateExisting`\n\t * is set). Accepts a {@link DurationString} (autocompletes common values) or a number of\n\t * seconds. Omit to keep the branch indefinitely.\n\t *\n\t * - {@link DurationString} — e.g. `\"7d\"`; autocompletes `\"1h\"`, `\"6h\"`, `\"12h\"`, `\"1d\"`,\n\t * `\"3d\"`, `\"7d\"`, `\"14d\"`, `\"30d\"`, and accepts any other `<integer><unit>` (units: `s`,\n\t * `m`, `h`, `d`, `w` — e.g. `\"12h\"`, `\"2w\"`). A **unit is required** — `\"7\"` is rejected;\n\t * for raw seconds pass a `number`.\n\t * - `number` — custom TTL in **seconds** (e.g. `3600`)\n\t * - `undefined` — no expiry; the branch persists until explicitly deleted\n\t *\n\t * The Neon API caps branch expiration at **30 days** from creation, so the resolved TTL must\n\t * be `> 0` and `<= 30d`; the suggestions stay within that limit and anything longer is\n\t * rejected at apply.\n\t *\n\t * @example \"1d\" // ephemeral preview branch: expires a day after creation\n\t * @example \"7d\" // one-week TTL\n\t * @example \"30d\" // the maximum the API allows\n\t * @example 3600 // 1 hour, expressed in seconds\n\t */\n\tttl?: DurationField<TtlSuggestion>;\n\t/** Whether the selected branch should be protected. Undefined means \"leave as-is\". */\n\tprotected?: boolean;\n\tpostgres?: PostgresConfig;\n\tpreview?: PreviewTuning<Slug>;\n}\n\n/** Extract the declared function slugs from a {@link PreviewInput} for closure typing. */\ntype FunctionSlugsOf<Preview extends PreviewInput | undefined> =\n\tPreview extends {\n\t\tfunctions: infer F;\n\t}\n\t\t? Extract<keyof F, string>\n\t\t: string;\n\n/**\n * Signature of the `branch` closure. Generic over the static {@link PreviewInput} so the\n * `preview.functions` keys it may tune are constrained to the slugs actually declared.\n */\nexport type BranchTuningFn<\n\tPreview extends PreviewInput | undefined = PreviewInput | undefined,\n> = (branch: BranchTarget) => BranchTuning<FunctionSlugsOf<Preview>>;\n\n/**\n * A validated Neon branch policy — the value `defineConfig({ … })` returns and `neon.ts`\n * default-exports.\n *\n * Split into a **static** existential set (top-level `auth` / `dataApi` GA toggles plus the\n * beta `preview` block) and a **dynamic** per-branch `branch` closure for tuning. The\n * static half is what makes the secret set — and therefore `NeonEnv<typeof config>` and\n * `parseEnv` — exact; the closure can tune but never change what exists.\n *\n * Generic over the three static fields so the type system can read the exact toggle/slug\n * literals; the defaults make the bare `Config` a usable \"any policy\" type for runtime\n * function signatures.\n */\nexport interface Config<\n\tAuth extends ServiceToggleInput | undefined =\n\t\t| ServiceToggleInput\n\t\t| undefined,\n\tDataApi extends DataApiInput | undefined = DataApiInput | undefined,\n\tPreview extends PreviewInput | undefined = PreviewInput | undefined,\n> {\n\t/** Neon Auth integration toggle (GA). Static — drives `NeonEnv.auth`. */\n\tauth?: Auth;\n\t/**\n\t * Neon Data API integration (GA). Static — drives `NeonEnv.dataApi`. A boolean/toggle, or\n\t * a {@link DataApiConfig} object selecting the auth provider (`\"neon\"` / `\"external\"`) and\n\t * runtime {@link DataApiSettings}. With `authProvider: \"neon\"` the policy must also enable\n\t * top-level `auth`.\n\t */\n\tdataApi?: DataApi;\n\t/** Beta (Preview) feature set: AI Gateway, functions, buckets. Static. */\n\tpreview?: Preview;\n\t/** Per-branch tuning closure. Cannot change the static existential set. */\n\tbranch?: BranchTuningFn<Preview>;\n}\n\n/**\n * A function with all deploy defaults applied. `resolveConfig` fills in `runtime` so\n * downstream diff/apply never has to re-derive it.\n */\nexport interface ResolvedFunctionConfig {\n\tslug: string;\n\tname: string;\n\tsource: string;\n\tenv: Record<string, string>;\n\truntime: FunctionRuntime;\n\t/**\n\t * Local-development settings, passed through untouched from {@link FunctionDef.dev}\n\t * (no defaults applied). Only consumed by `neon dev`; deploy ignores it.\n\t */\n\tdev?: FunctionDevConfig;\n}\n\n/** A bucket with its access level defaulted to `private`. */\nexport interface ResolvedBucketConfig {\n\tname: string;\n\taccess: BucketAccessLevel;\n}\n\n/**\n * Normalized {@link PreviewInput}. Only present on {@link ResolvedBranchConfig} when the\n * policy returned a `preview` block. `aiGatewayEnabled` follows the same\n * \"present-and-not-`false`\" semantics as `authEnabled` / `dataApiEnabled`.\n */\nexport interface ResolvedPreviewConfig {\n\tfunctions: ResolvedFunctionConfig[];\n\tbuckets: ResolvedBucketConfig[];\n\taiGatewayEnabled: boolean;\n}\n\n/**\n * Normalized Data API integration. Present on {@link ResolvedBranchConfig} only when the\n * policy enables `dataApi`. `authProvider` always resolves (defaults to `\"neon\"`); the\n * external-IdP wiring is present only for `\"external\"`; `settings` carries the camelCase\n * runtime settings (reconciled as an update when they drift).\n */\nexport interface ResolvedDataApiConfig {\n\tauthProvider: DataApiAuthProvider;\n\tjwksUrl?: string;\n\tproviderName?: string;\n\tjwtAudience?: string;\n\tsettings?: DataApiSettings;\n}\n\nexport interface ResolvedBranchConfig {\n\tparent?: string;\n\tttlSeconds?: number;\n\tprotected?: boolean;\n\tpostgres?: PostgresConfig;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Resolved Data API integration. Present iff {@link dataApiEnabled} is `true`. Carries the\n\t * create-time auth wiring and the updatable {@link DataApiSettings}.\n\t */\n\tdataApi?: ResolvedDataApiConfig;\n\tpreview?: ResolvedPreviewConfig;\n}\n\n/**\n * One concrete change `pushConfig` made (or, in dry-run, would make) on the remote.\n */\nexport interface AppliedChange {\n\t/**\n\t * `service` covers branch-scoped integrations driven by the branch policy (e.g.\n\t * Neon Auth, Data API).\n\t */\n\tkind: \"branch\" | \"service\";\n\taction: \"create\" | \"update\" | \"noop\";\n\tidentifier: string;\n\tdetails?: Record<string, unknown>;\n}\n\n/**\n * A diff entry that conflicts with the desired config. `pushConfig` throws\n * {@link PushConflictError} on the first call when conflicts exist; pass\n * `updateExisting: true` to apply mutable drift (settings, `protected`, TTL, project\n * rename). Immutable fields (region, Postgres major version) are always conflicts —\n * recreate the project to change them.\n */\nexport interface ConflictReport {\n\tkind: \"branch\";\n\tidentifier: string;\n\tfield: string;\n\tcurrent: unknown;\n\tdesired: unknown;\n\treason: string;\n}\n\n/**\n * Result of a `pushConfig` invocation.\n */\nexport interface PushResult {\n\tprojectId: string;\n\torgId?: string;\n\tbranchId: string;\n\tbranchName: string;\n\t/**\n\t * `true` when `pushConfig` was called with `{ dryRun: true }`. `applied` then records\n\t * what **would** be applied on a real push; no API mutations were performed.\n\t */\n\tdryRun: boolean;\n\tapplied: AppliedChange[];\n\tconflicts: ConflictReport[];\n}\n"],"mappings":";;;;;;;;;;;;;AA+LA,MAAa,0BAA0B,CAAC,QAAQ,UAAU"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":["/**\n * Valid Neon Compute Unit values.\n * Most plans support 0.25, 0.5, 1, 2, 4, 8. Higher values may be available on Business plans.\n */\nexport type ComputeUnit = 0.25 | 0.5 | 1 | 2 | 4 | 8;\n\n/** Time units accepted in a {@link DurationString}: seconds, minutes, hours, days, weeks. */\nexport type DurationUnit = \"s\" | \"m\" | \"h\" | \"d\" | \"w\";\n\n/**\n * A Neon duration string: a positive integer **followed by a unit** — `s` (seconds),\n * `m` (minutes), `h` (hours), `d` (days), or `w` (weeks). Used by\n * {@link ComputeSettings.suspendTimeout} and {@link BranchTuning.ttl}.\n *\n * A **unit is required**: a bare numeric string like `\"7\"` is rejected at the type level. To\n * express a raw number of seconds, pass a `number` (`300`) — not a string (`\"300\"`). This\n * removes the old ambiguity where `\"7\"` silently meant 7 *seconds* instead of, say, `\"7d\"`.\n *\n * @example \"5m\" // 5 minutes\n * @example \"1h\" // 1 hour\n * @example \"7d\" // 7 days\n */\nexport type DurationString = `${number}${DurationUnit}`;\n\n/**\n * Autocomplete suggestions for {@link ComputeSettings.suspendTimeout}. Every value sits inside\n * the Neon API's allowed scale-to-zero band: **60s–604800s** (1 minute – 1 week). This is *not*\n * a closed set — the field also accepts any other {@link DurationString} or a `number` of\n * seconds; out-of-range values type-check but are rejected at apply time.\n */\ntype SuspendTimeoutSuggestion =\n\t| \"1m\"\n\t| \"5m\"\n\t| \"15m\"\n\t| \"30m\"\n\t| \"1h\"\n\t| \"6h\"\n\t| \"12h\"\n\t| \"1d\"\n\t| \"7d\";\n\n/**\n * Autocomplete suggestions for {@link BranchTuning.ttl}. Every value sits within the Neon API's\n * branch-expiration limit (**max 30 days** from creation; the Console's own presets are 1h / 1d\n * / 7d). This is *not* a closed set — the field also accepts any other {@link DurationString} or\n * a `number` of seconds; values over 30 days are rejected at apply time.\n */\ntype TtlSuggestion = \"1h\" | \"6h\" | \"12h\" | \"1d\" | \"3d\" | \"7d\" | \"14d\" | \"30d\";\n\n/**\n * Compose a field's duration type: its curated autocomplete `Suggestions` plus the open\n * `DurationString` template (so any `<integer><unit>` string still type-checks) and a `number`\n * of seconds. Intersecting the template arm with `NonNullable<unknown>` stops TypeScript from\n * collapsing the literal suggestions into the template, which is what preserves the autocomplete.\n */\ntype DurationField<Suggestions extends DurationString> =\n\t| Suggestions\n\t| (DurationString & NonNullable<unknown>)\n\t| number;\n\n/**\n * Compute settings applied to the read/write endpoint of a branch.\n *\n * Mirrors the subset of {@link https://api-docs.neon.tech/reference/getting-started-with-neon-api Neon endpoint}\n * fields that we expose as IaC primitives. Anything left undefined falls back to the project's\n * `default_endpoint_settings` (which themselves fall back to Neon platform defaults).\n */\nexport interface ComputeSettings {\n\t/**\n\t * Minimum number of Compute Units. Set to 0.25 for true scale-to-zero.\n\t * @example 0.25 // scale-to-zero\n\t * @example 1 // always-on with 1 CU minimum\n\t */\n\tautoscalingLimitMinCu?: ComputeUnit;\n\t/**\n\t * Maximum number of Compute Units for autoscaling.\n\t * @example 2\n\t * @example 8\n\t */\n\tautoscalingLimitMaxCu?: ComputeUnit;\n\t/**\n\t * How long an idle compute waits before suspending (Neon's scale-to-zero). Accepts a\n\t * {@link DurationString} (autocompletes common values), a number of seconds, or `false`.\n\t *\n\t * - `false` — never suspend (always-on compute)\n\t * - {@link DurationString} — e.g. `\"5m\"`; autocompletes the in-range values `\"1m\"`, `\"5m\"`,\n\t * `\"15m\"`, `\"30m\"`, `\"1h\"`, `\"6h\"`, `\"12h\"`, `\"1d\"`, `\"7d\"`, and accepts any other\n\t * `<integer><unit>` (units: `s`, `m`, `h`, `d`, `w`). A **unit is required** — for raw\n\t * seconds pass a `number`, not a string.\n\t * - `number` — custom timeout in **seconds**, must be in `60`–`604800` (1 minute to 1 week)\n\t * - `undefined` — use the Neon platform default (currently 300s / 5 minutes)\n\t *\n\t * Whichever form you use, the resolved timeout must fall in `60`–`604800` seconds (the Neon\n\t * API limit); the suggestions are all within that band, anything else is checked at apply.\n\t *\n\t * @example false // never suspend (always-on)\n\t * @example \"5m\" // suspend after 5 minutes idle\n\t * @example \"1h\" // suspend after 1 hour idle\n\t * @example 300 // 5 minutes, expressed in seconds\n\t */\n\tsuspendTimeout?: false | DurationField<SuspendTimeoutSuggestion>;\n}\n\n/**\n * Read-only descriptor of the branch a {@link Config} policy is being evaluated for — the\n * `branch` argument passed to your `defineConfig({ branch: (branch) => … })` closure. It describes\n * **which** branch this invocation decides for; it is not a live branch handle and must not\n * be mutated. Switch on its fields and return the desired {@link BranchConfig}.\n */\nexport interface BranchTarget {\n\t/** Branch name being evaluated. For `branch dev`, this is the generated branch name. */\n\tname: string;\n\t/** Neon branch id when the branch already exists. Undefined during pre-create eval. */\n\tid?: string;\n\t/** Whether this branch already exists on Neon. */\n\texists: boolean;\n\t/** Parent branch id from Neon when known. */\n\tparentId?: string;\n\t/** Whether Neon marks this branch as the project default. */\n\tisDefault?: boolean;\n\t/** Whether Neon currently marks this branch protected. */\n\tisProtected?: boolean;\n\t/** Current expiration timestamp from Neon, when set. */\n\texpiresAt?: string;\n}\n\n/**\n * Object form of a branch-scoped service toggle. `{}` or `{ enabled: true }` enables it;\n * `{ enabled: false }` opts out. Used as the object half of {@link ServiceToggleInput}.\n */\nexport interface ServiceToggle {\n\t/** Defaults to `true` when the service namespace is present. Set `false` to opt out. */\n\tenabled?: boolean;\n}\n\n/**\n * How a branch-scoped service (Neon Auth, Data API, AI Gateway) is toggled in a policy.\n *\n * - `true` / `{}` / `{ enabled: true }` — enabled.\n * - `false` / `{ enabled: false }` — disabled.\n * - omitted (`undefined`) — not part of the policy at all.\n *\n * These toggles are **static** (they live in the top-level `defineConfig({ … })` object,\n * not in the per-branch `branch` closure) so the secret set they imply can be derived at\n * the type level — that's what makes `NeonEnv<typeof config>` exact.\n */\nexport type ServiceToggleInput = boolean | ServiceToggle;\n\n/**\n * Resolve a **static** service toggle (`true` / `false` / `{ enabled?: boolean }` / object /\n * `undefined`) to a type-level boolean. The tuple wrapping (`[T] extends […]`) disables\n * distribution so a union/`undefined` is judged as a single 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 | … | undefined` (no literal info) → `false`\n *\n * Shared by the {@link Config} static cross-field checks and the `@neon/env`\n * `NeonEnv` namespace derivation, so both read \"is this service on?\" identically.\n */\nexport type ServiceEnabled<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\nexport interface PostgresConfig {\n\tcomputeSettings?: ComputeSettings;\n}\n\n/**\n * Authentication providers a Data API integration can verify JWTs against, as written in\n * `neon.ts`. Friendly authoring values (mapped to the Neon API's `neon_auth` / `external`\n * at the API boundary):\n *\n * - `\"neon\"` — verify tokens minted by **Neon Auth** on the same branch. Neon supplies the\n * JWKS URL / provider wiring for you, so the `jwksUrl` / `providerName` / `jwtAudience`\n * fields are forbidden (a type error) on this variant — and the policy must also enable\n * top-level `auth` (Neon Auth) so the tokens exist.\n * - `\"external\"` — verify tokens from a third-party IdP (Clerk, Stytch, Auth0, …). You\n * provide `jwksUrl` (and optionally `providerName` / `jwtAudience`).\n */\nexport const DATA_API_AUTH_PROVIDERS = [\"neon\", \"external\"] as const;\nexport type DataApiAuthProvider = (typeof DATA_API_AUTH_PROVIDERS)[number];\n\n/**\n * Reusable runtime settings for a Data API integration (the Neon API `DataAPISettings`,\n * camelCased to match the rest of `neon.ts`). Every field is optional; omitted fields keep\n * the Neon defaults shown below. These are the **only** Data API fields that can change on\n * an already-enabled integration — drift here is reconciled as an *update* (requires\n * `updateExisting` / `--update-existing`); the create-only auth wiring above cannot.\n */\nexport interface DataApiSettings {\n\t/** Enable the aggregates feature (`db_aggregates_enabled`). Default `true`. */\n\tdbAggregatesEnabled?: boolean;\n\t/** Database role used for anonymous requests (`db_anon_role`). Default `\"anonymous\"`. */\n\tdbAnonRole?: string;\n\t/** Extra schemas appended to the search path (`db_extra_search_path`). */\n\tdbExtraSearchPath?: string;\n\t/** Maximum rows returned in a single request (`db_max_rows`). */\n\tdbMaxRows?: number;\n\t/** Schemas exposed via the API (`db_schemas`). Default `[\"public\"]`. */\n\tdbSchemas?: string[];\n\t/** JWT claim key used for role extraction (`jwt_role_claim_key`). Default `\".role\"`. */\n\tjwtRoleClaimKey?: string;\n\t/** Maximum lifetime of the JWT cache, in seconds (`jwt_cache_max_lifetime`). */\n\tjwtCacheMaxLifetime?: number;\n\t/** OpenAPI spec mode (`openapi_mode`). Default `\"disabled\"`. */\n\topenapiMode?: \"ignore-privileges\" | \"disabled\";\n\t/** CORS allowed origins (`server_cors_allowed_origins`). */\n\tserverCorsAllowedOrigins?: string;\n\t/** Emit server-timing headers (`server_timing_enabled`). */\n\tserverTimingEnabled?: boolean;\n}\n\n/** Fields shared by every {@link DataApiConfig} variant. */\ninterface DataApiConfigBase {\n\t/** Defaults to `true` when the `dataApi` namespace is present. Set `false` to opt out. */\n\tenabled?: boolean;\n\t/** Reusable runtime settings. Drift here is reconciled as an update. */\n\tsettings?: DataApiSettings;\n}\n\n/**\n * Data API verified by **Neon Auth** (`authProvider: \"neon\"`, the default). The external\n * IdP fields are statically forbidden (`?: never`) because Neon supplies them; declaring any\n * of them is a type error directing you to `authProvider: \"external\"`.\n */\nexport interface DataApiNeonAuthConfig extends DataApiConfigBase {\n\tauthProvider?: \"neon\";\n\t/** Forbidden with `authProvider: \"neon\"` — Neon provides the JWKS URL. */\n\tjwksUrl?: never;\n\t/** Forbidden with `authProvider: \"neon\"` — the provider is Neon Auth. */\n\tproviderName?: never;\n\t/** Forbidden with `authProvider: \"neon\"` — Neon manages the audience. */\n\tjwtAudience?: never;\n}\n\n/**\n * Data API verified by an **external** IdP (`authProvider: \"external\"`). You provide the\n * JWKS URL (and optionally a provider label / expected audience).\n */\nexport interface DataApiExternalAuthConfig extends DataApiConfigBase {\n\tauthProvider: \"external\";\n\t/** URL that publishes the IdP's JWKS (JSON Web Key Set). */\n\tjwksUrl?: string;\n\t/** Human label for the IdP (e.g. \"Clerk\", \"Stytch\", \"Auth0\"). */\n\tproviderName?: string;\n\t/**\n\t * Expected `aud` claim. ⚠️ This only **rejects** tokens carrying a *different* audience;\n\t * tokens with no `aud` claim are still accepted.\n\t */\n\tjwtAudience?: string;\n}\n\n/**\n * Object form of the `dataApi` toggle. A discriminated union on {@link DataApiAuthProvider}:\n * the `\"neon\"` variant forbids the external-IdP fields, the `\"external\"` variant allows them.\n */\nexport type DataApiConfig = DataApiNeonAuthConfig | DataApiExternalAuthConfig;\n\n/**\n * How the Data API is toggled in a policy: a bare boolean (like the other service toggles)\n * or the richer {@link DataApiConfig} object. `true` / `{}` / `{ enabled: true }` enable it\n * with Neon defaults; `false` / `{ enabled: false }` opt out.\n */\nexport type DataApiInput = boolean | DataApiConfig;\n\n/**\n * Supported function runtimes. Mirrors the Neon Functions deploy API `runtime` enum.\n * Only `nodejs24` exists today; kept as a union so adding runtimes later is a\n * non-breaking, type-checked change.\n */\nexport type FunctionRuntime = \"nodejs24\";\n\n/**\n * Local-development settings for a function, used by `neon dev` when it serves every\n * function declared in `neon.ts` (i.e. invoked with no `--source`). Never affects deploy.\n */\nexport interface FunctionDevConfig {\n\t/**\n\t * Port the local server binds. Bound exactly (and `neon dev` fails loudly if it is taken)\n\t * when set; a free port is found automatically when omitted.\n\t */\n\tport?: number;\n}\n\n/**\n * Static definition of a Neon Function (Preview feature). Declares that the function\n * **exists** on every branch; its branch-unique slug is the **record key** in\n * {@link PreviewInput.functions} (not a field here), so slugs are statically enumerable,\n * cannot duplicate, and the `branch` closure can only tune slugs that are declared here.\n *\n * A function is invoked like a Cloudflare/Vercel handler — its source module\n * `export default { fetch }` or `export async function handler(req): Response`. The\n * `source` path is bundled (esbuild) and uploaded as a deployment; the newest deployment\n * becomes active.\n *\n * Runtime tuning is **not** here — it varies per branch and lives in the `branch` closure\n * (see {@link FunctionTuning}). Memory is fixed by the platform policy for now and is not\n * user-configurable.\n */\nexport interface FunctionDef {\n\t/** Free-form display name. @example \"Hello World\" */\n\tname: string;\n\t/**\n\t * Path to the function's entry module, **relative to `neon.ts`** (or absolute). The\n\t * module's default export (`{ fetch }`) or `handler` export is the function entry. This\n\t * path is resolved against the loaded `neon.ts` location and bundled with esbuild at\n\t * deploy time.\n\t *\n\t * We require a string path rather than an imported handler because a JS function value\n\t * carries no reference back to its source file, so esbuild has nothing to bundle from.\n\t * @example \"./functions/hello-world.ts\"\n\t */\n\tsource: string;\n\t/**\n\t * Environment variables injected into the deployed function, keyed by the var name the\n\t * function reads at runtime. The **keys** are static (preserved at the type level so\n\t * `parseEnv(config, \"<slug>\").function.<key>` is typed); the **values** are arbitrary\n\t * strings evaluated when `neon.ts` is loaded (typically `process.env.X`) and uploaded\n\t * at `config apply`. Every value must be a defined string — a `process.env.X` that is\n\t * `undefined` (unset) errors at validation time rather than silently shipping\n\t * `undefined`.\n\t * @example { resendApiKey: process.env.RESEND_API_KEY ?? \"\" }\n\t */\n\tenv?: Record<string, string>;\n\t/**\n\t * Local-development settings used by `neon dev` when serving every function from\n\t * `neon.ts`. Ignored at deploy time. See {@link FunctionDevConfig}.\n\t */\n\tdev?: FunctionDevConfig;\n}\n\n/**\n * A single capability a branch-scoped service credential may exercise (Preview). A\n * credential is granted a set of these and may only perform the listed actions. Mirrors\n * the Neon API `CredentialScope` enum (`x-stability-level: beta`):\n *\n * - `storage:read` / `storage:write` — object-storage (bucket) access via the S3 key.\n * - `ai_gateway:invoke` — call the AI Gateway with the bearer `api_token`.\n * - `functions:invoke` — invoke Neon Functions with the bearer `api_token`.\n *\n * The set a policy needs is derived from its enabled Preview features (see\n * {@link deriveCredentialScopes}); it is never authored by hand.\n */\nexport type CredentialScope =\n\t| \"storage:read\"\n\t| \"storage:write\"\n\t| \"ai_gateway:invoke\"\n\t| \"functions:invoke\";\n\n/**\n * Who a credential acts as. `user` is the developer/app principal minted for local dev and\n * app bootstrap (`fetchEnv` / `env pull`); `function` is a deployed-function principal\n * (carries a `function_id`). The env tooling only mints `user` credentials today.\n */\nexport type CredentialPrincipalType = \"user\" | \"function\";\n\n/** Anonymous-access level for a branchable object-storage bucket. */\nexport type BucketAccessLevel = \"private\" | \"public_read\";\n\n/**\n * Static definition of a branchable object-storage bucket (Preview feature). The bucket's\n * name is the **record key** in {@link PreviewInput.buckets}, so names are statically\n * enumerable and cannot duplicate.\n */\nexport interface BucketDef {\n\t/**\n\t * Anonymous access level. `private` (default) requires authenticated reads/writes;\n\t * `public_read` allows anonymous GetObject/HeadObject.\n\t */\n\taccess?: BucketAccessLevel;\n}\n\n/**\n * Static, branch-scoped **Preview** features. Grouped under `preview` to signal they are\n * backed by Neon `x-stability-level: beta` endpoints and may change before GA. Everything\n * here is existential (it determines what exists on the branch); per-branch tuning lives in\n * the `branch` closure.\n */\nexport interface PreviewInput {\n\t/** Enable/disable the AI Gateway on the branch (toggle, like auth / dataApi). */\n\taiGateway?: ServiceToggleInput;\n\t/** Functions to deploy, keyed by branch-unique slug (`^[a-z0-9]{1,20}$`). */\n\tfunctions?: Record<string, FunctionDef>;\n\t/** Object-storage buckets to create, keyed by bucket name. */\n\tbuckets?: Record<string, BucketDef>;\n}\n\n/**\n * Per-branch deploy tuning for a single function. Returned (per slug) by the `branch`\n * closure. Deliberately **cannot** change the function's existence, source, name, env\n * **keys**, or memory — only runtime selection is currently configurable — so the static\n * secret/function set stays sound.\n */\nexport interface FunctionTuning {\n\t/** Runtime to execute the function with. Defaults to `\"nodejs24\"`. */\n\truntime?: FunctionRuntime;\n}\n\n/**\n * Per-branch tuning of Preview features. Only existing function slugs (those declared in\n * the static {@link PreviewInput.functions}) may be tuned — `Slug` is constrained to the\n * declared keys by {@link BranchTuningFn}.\n */\nexport interface PreviewTuning<Slug extends string = string> {\n\tfunctions?: Partial<Record<Slug, FunctionTuning>>;\n}\n\n/**\n * The per-branch tuning object returned by the `branch` closure. It can adjust branch\n * lifecycle (`parent`, `ttl`, `protected`), Postgres compute settings, and per-function\n * deploy tuning — but **cannot** add/remove services or functions. That guarantee is what\n * keeps the static secret set (and therefore `NeonEnv`) exact.\n */\nexport interface BranchTuning<Slug extends string = string> {\n\t/** Parent branch name used when creating a new branch. Not a Postgres setting. */\n\tparent?: string;\n\t/**\n\t * Branch time-to-live: how long after creation the branch should auto-expire. Applied\n\t * when creating a new branch and reconciled on existing branches (when `updateExisting`\n\t * is set). Accepts a {@link DurationString} (autocompletes common values) or a number of\n\t * seconds. Omit to keep the branch indefinitely.\n\t *\n\t * - {@link DurationString} — e.g. `\"7d\"`; autocompletes `\"1h\"`, `\"6h\"`, `\"12h\"`, `\"1d\"`,\n\t * `\"3d\"`, `\"7d\"`, `\"14d\"`, `\"30d\"`, and accepts any other `<integer><unit>` (units: `s`,\n\t * `m`, `h`, `d`, `w` — e.g. `\"12h\"`, `\"2w\"`). A **unit is required** — `\"7\"` is rejected;\n\t * for raw seconds pass a `number`.\n\t * - `number` — custom TTL in **seconds** (e.g. `3600`)\n\t * - `undefined` — no expiry; the branch persists until explicitly deleted\n\t *\n\t * The Neon API caps branch expiration at **30 days** from creation, so the resolved TTL must\n\t * be `> 0` and `<= 30d`; the suggestions stay within that limit and anything longer is\n\t * rejected at apply.\n\t *\n\t * @example \"1d\" // ephemeral preview branch: expires a day after creation\n\t * @example \"7d\" // one-week TTL\n\t * @example \"30d\" // the maximum the API allows\n\t * @example 3600 // 1 hour, expressed in seconds\n\t */\n\tttl?: DurationField<TtlSuggestion>;\n\t/** Whether the selected branch should be protected. Undefined means \"leave as-is\". */\n\tprotected?: boolean;\n\tpostgres?: PostgresConfig;\n\tpreview?: PreviewTuning<Slug>;\n}\n\n/** Extract the declared function slugs from a {@link PreviewInput} for closure typing. */\ntype FunctionSlugsOf<Preview extends PreviewInput | undefined> =\n\tPreview extends {\n\t\tfunctions: infer F;\n\t}\n\t\t? Extract<keyof F, string>\n\t\t: string;\n\n/**\n * Signature of the `branch` closure. Generic over the static {@link PreviewInput} so the\n * `preview.functions` keys it may tune are constrained to the slugs actually declared.\n */\nexport type BranchTuningFn<\n\tPreview extends PreviewInput | undefined = PreviewInput | undefined,\n> = (branch: BranchTarget) => BranchTuning<FunctionSlugsOf<Preview>>;\n\n/**\n * A validated Neon branch policy — the value `defineConfig({ … })` returns and `neon.ts`\n * default-exports.\n *\n * Split into a **static** existential set (top-level `auth` / `dataApi` GA toggles plus the\n * beta `preview` block) and a **dynamic** per-branch `branch` closure for tuning. The\n * static half is what makes the secret set — and therefore `NeonEnv<typeof config>` and\n * `parseEnv` — exact; the closure can tune but never change what exists.\n *\n * Generic over the three static fields so the type system can read the exact toggle/slug\n * literals; the defaults make the bare `Config` a usable \"any policy\" type for runtime\n * function signatures.\n */\nexport interface Config<\n\tAuth extends ServiceToggleInput | undefined =\n\t\t| ServiceToggleInput\n\t\t| undefined,\n\tDataApi extends DataApiInput | undefined = DataApiInput | undefined,\n\tPreview extends PreviewInput | undefined = PreviewInput | undefined,\n> {\n\t/** Neon Auth integration toggle (GA). Static — drives `NeonEnv.auth`. */\n\tauth?: Auth;\n\t/**\n\t * Neon Data API integration (GA). Static — drives `NeonEnv.dataApi`. A boolean/toggle, or\n\t * a {@link DataApiConfig} object selecting the auth provider (`\"neon\"` / `\"external\"`) and\n\t * runtime {@link DataApiSettings}. With `authProvider: \"neon\"` the policy must also enable\n\t * top-level `auth`.\n\t */\n\tdataApi?: DataApi;\n\t/** Beta (Preview) feature set: AI Gateway, functions, buckets. Static. */\n\tpreview?: Preview;\n\t/** Per-branch tuning closure. Cannot change the static existential set. */\n\tbranch?: BranchTuningFn<Preview>;\n}\n\n/**\n * A function with all deploy defaults applied. `resolveConfig` fills in `runtime` so\n * downstream diff/apply never has to re-derive it.\n */\nexport interface ResolvedFunctionConfig {\n\tslug: string;\n\tname: string;\n\tsource: string;\n\tenv: Record<string, string>;\n\truntime: FunctionRuntime;\n\t/**\n\t * Local-development settings, passed through untouched from {@link FunctionDef.dev}\n\t * (no defaults applied). Only consumed by `neon dev`; deploy ignores it.\n\t */\n\tdev?: FunctionDevConfig;\n}\n\n/** A bucket with its access level defaulted to `private`. */\nexport interface ResolvedBucketConfig {\n\tname: string;\n\taccess: BucketAccessLevel;\n}\n\n/**\n * Normalized {@link PreviewInput}. Only present on {@link ResolvedBranchConfig} when the\n * policy returned a `preview` block. `aiGatewayEnabled` follows the same\n * \"present-and-not-`false`\" semantics as `authEnabled` / `dataApiEnabled`.\n */\nexport interface ResolvedPreviewConfig {\n\tfunctions: ResolvedFunctionConfig[];\n\tbuckets: ResolvedBucketConfig[];\n\taiGatewayEnabled: boolean;\n}\n\n/**\n * Normalized Data API integration. Present on {@link ResolvedBranchConfig} only when the\n * policy enables `dataApi`. `authProvider` always resolves (defaults to `\"neon\"`); the\n * external-IdP wiring is present only for `\"external\"`; `settings` carries the camelCase\n * runtime settings (reconciled as an update when they drift).\n */\nexport interface ResolvedDataApiConfig {\n\tauthProvider: DataApiAuthProvider;\n\tjwksUrl?: string;\n\tproviderName?: string;\n\tjwtAudience?: string;\n\tsettings?: DataApiSettings;\n}\n\nexport interface ResolvedBranchConfig {\n\tparent?: string;\n\tttlSeconds?: number;\n\tprotected?: boolean;\n\tpostgres?: PostgresConfig;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Resolved Data API integration. Present iff {@link dataApiEnabled} is `true`. Carries the\n\t * create-time auth wiring and the updatable {@link DataApiSettings}.\n\t */\n\tdataApi?: ResolvedDataApiConfig;\n\tpreview?: ResolvedPreviewConfig;\n}\n\n/**\n * One concrete change `pushConfig` made (or, in dry-run, would make) on the remote.\n */\nexport interface AppliedChange {\n\t/**\n\t * `service` covers branch-scoped integrations driven by the branch policy (e.g.\n\t * Neon Auth, Data API).\n\t */\n\tkind: \"branch\" | \"service\";\n\taction: \"create\" | \"update\" | \"noop\";\n\tidentifier: string;\n\tdetails?: Record<string, unknown>;\n}\n\n/**\n * A diff entry that conflicts with the desired config. `pushConfig` throws\n * {@link PushConflictError} on the first call when conflicts exist; pass\n * `updateExisting: true` to apply mutable drift (settings, `protected`, TTL, project\n * rename). Immutable fields (region, Postgres major version) are always conflicts —\n * recreate the project to change them.\n */\nexport interface ConflictReport {\n\tkind: \"branch\";\n\tidentifier: string;\n\tfield: string;\n\tcurrent: unknown;\n\tdesired: unknown;\n\treason: string;\n}\n\n/**\n * Result of a `pushConfig` invocation.\n */\nexport interface PushResult {\n\tprojectId: string;\n\torgId?: string;\n\tbranchId: string;\n\tbranchName: string;\n\t/**\n\t * `true` when `pushConfig` was called with `{ dryRun: true }`. `applied` then records\n\t * what **would** be applied on a real push; no API mutations were performed.\n\t */\n\tdryRun: boolean;\n\tapplied: AppliedChange[];\n\tconflicts: ConflictReport[];\n}\n"],"mappings":";;;;;;;;;;;;;AA+LA,MAAa,0BAA0B,CAAC,QAAQ,UAAU"}
|
package/dist/v1.js
CHANGED
|
@@ -9,11 +9,11 @@ import { loadConfigFromFile } from "./lib/loader.js";
|
|
|
9
9
|
import { DATA_API_AUTH_PROVIDERS } from "./lib/types.js";
|
|
10
10
|
//#region src/v1.ts
|
|
11
11
|
/**
|
|
12
|
-
* `@
|
|
12
|
+
* `@neon/config/v1` — the v1 public API for Config-as-Code on the Neon Platform.
|
|
13
13
|
*
|
|
14
14
|
* Usage in `neon.ts`:
|
|
15
15
|
* ```ts
|
|
16
|
-
* import { defineConfig } from "@
|
|
16
|
+
* import { defineConfig } from "@neon/config/v1";
|
|
17
17
|
*
|
|
18
18
|
* export default defineConfig({
|
|
19
19
|
* // Static: what *exists* on every branch (drives the typed env).
|
|
@@ -31,11 +31,11 @@ import { DATA_API_AUTH_PROVIDERS } from "./lib/types.js";
|
|
|
31
31
|
* importing it from `neon.ts` stays cheap and bundler-safe.
|
|
32
32
|
*
|
|
33
33
|
* The imperative operations (`inspect` / `plan` / `apply`, `pushConfig` / `pullConfig`) and
|
|
34
|
-
* function bundling/deploy live in **`@
|
|
34
|
+
* function bundling/deploy live in **`@neon/config-runtime`**, which depends on this
|
|
35
35
|
* package and pulls in `esbuild`. Import that from your CLI / CI, not from `neon.ts`:
|
|
36
36
|
* ```ts
|
|
37
37
|
* import config from "../neon";
|
|
38
|
-
* import { inspect, plan, apply } from "@
|
|
38
|
+
* import { inspect, plan, apply } from "@neon/config-runtime/v1";
|
|
39
39
|
* ```
|
|
40
40
|
*
|
|
41
41
|
* Surface guidelines:
|
package/dist/v1.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v1.js","names":[],"sources":["../src/v1.ts"],"sourcesContent":["/**\n * `@
|
|
1
|
+
{"version":3,"file":"v1.js","names":[],"sources":["../src/v1.ts"],"sourcesContent":["/**\n * `@neon/config/v1` — the v1 public API for Config-as-Code on the Neon Platform.\n *\n * Usage in `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neon/config/v1\";\n *\n * export default defineConfig({\n * // Static: what *exists* on every branch (drives the typed env).\n * auth: true,\n * // Dynamic: per-branch tuning only — cannot add/remove services.\n * branch: (branch) => ({\n * protected: branch.name === \"main\",\n * ...(branch.name === \"main\" ? {} : { parent: \"main\", ttl: \"7d\" }),\n * }),\n * });\n * ```\n *\n * This is the **authoring** surface — `defineConfig`, types, schemas, the pure diff engine,\n * and the Neon API adapter. It is intentionally free of heavy/native dependencies so that\n * importing it from `neon.ts` stays cheap and bundler-safe.\n *\n * The imperative operations (`inspect` / `plan` / `apply`, `pushConfig` / `pullConfig`) and\n * function bundling/deploy live in **`@neon/config-runtime`**, which depends on this\n * package and pulls in `esbuild`. Import that from your CLI / CI, not from `neon.ts`:\n * ```ts\n * import config from \"../neon\";\n * import { inspect, plan, apply } from \"@neon/config-runtime/v1\";\n * ```\n *\n * Surface guidelines:\n * - Top-level: `defineConfig` / `resolveConfig`, the pure `diffConfig` engine, the\n * `createRealNeonApi` adapter + `NeonApi` types, the config loader, the `PlatformError`\n * base class + `ErrorCode` enum, and the config types used in `neon.ts`.\n * - `errors` namespace: specific `PlatformError` subclasses (`ConfigLoadError`,\n * `PushConflictError`, …).\n * - `schemas` namespace: the zod schemas underlying `defineConfig`.\n */\n\nimport {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tisPlatformError,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} from \"./lib/errors.js\";\nimport {\n\tbranchTuningSchema,\n\tbucketDefSchema,\n\tcomputeSettingsSchema,\n\tconfigInputSchema,\n\tdataApiConfigSchema,\n\tdataApiInputSchema,\n\tdataApiSettingsSchema,\n\tfunctionDefSchema,\n\tfunctionTuningSchema,\n\tpostgresConfigSchema,\n\tpreviewInputSchema,\n\tserviceToggleInputSchema,\n\tserviceToggleSchema,\n} from \"./lib/schema.js\";\n\n/**\n * Specific `PlatformError` subclasses, grouped for `instanceof` / structured access.\n * Also available as top-level exports.\n */\nexport const errors = {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tisPlatformError,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} as const;\n\n/** The zod schemas underlying `defineConfig`, grouped under product-friendly names. */\nexport const schemas = {\n\tconfig: configInputSchema,\n\tbranchTuning: branchTuningSchema,\n\tbucket: bucketDefSchema,\n\tcomputeSettings: computeSettingsSchema,\n\tdataApi: dataApiConfigSchema,\n\tdataApiInput: dataApiInputSchema,\n\tdataApiSettings: dataApiSettingsSchema,\n\tfunction: functionDefSchema,\n\tfunctionTuning: functionTuningSchema,\n\tpostgres: postgresConfigSchema,\n\tpreview: previewInputSchema,\n\tservice: serviceToggleSchema,\n\tserviceInput: serviceToggleInputSchema,\n} as const;\n\n// ─── Lower-level adapters ──────────────────────────────────────────────────────\nexport { createNeonApiFromOptions, resolveApiKey } from \"./lib/auth.js\";\n// ─── Credentials (pure scope derivation; Preview) ─────────────────────────────\nexport type { CredentialFeatureFlags } from \"./lib/credentials.js\";\nexport {\n\tcredentialScopesSatisfied,\n\tderiveCredentialScopes,\n} from \"./lib/credentials.js\";\nexport { defineConfig, resolveConfig } from \"./lib/define-config.js\";\n// ─── Diff engine (pure; consumed by @neon/config-runtime) ─────────────\nexport type {\n\tDiffOptions,\n\tDiffResult,\n\tPlanStep,\n\tRemotePreviewState,\n\tRemoteServiceState,\n\tRemoteState,\n} from \"./lib/diff.js\";\nexport { diffConfig } from \"./lib/diff.js\";\n// ─── Errors ────────────────────────────────────────────────────────────────────\nexport {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tisPlatformError,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} from \"./lib/errors.js\";\nexport type { LoadConfigOptions } from \"./lib/loader.js\";\nexport { loadConfigFromFile } from \"./lib/loader.js\";\n// ─── NeonApi types (needed by callers implementing their own adapters) ────────\nexport type {\n\tCreateBranchInput,\n\tCreateBucketInput,\n\tCreateCredentialInput,\n\tCreateProjectInput,\n\tDeployFunctionInput,\n\tEnableDataApiInput,\n\tGetConnectionUriInput,\n\tNeonApi,\n\tNeonAuthSnapshot,\n\tNeonBranchSnapshot,\n\tNeonBranchStorageSnapshot,\n\tNeonBucketSnapshot,\n\tNeonCredentialMeta,\n\tNeonCredentialSecret,\n\tNeonDataApiSnapshot,\n\tNeonDatabaseSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionDeploymentSnapshot,\n\tNeonFunctionSnapshot,\n\tNeonProjectSnapshot,\n\tNeonRoleSnapshot,\n\tUpdateBranchInput,\n} from \"./lib/neon-api.js\";\nexport { createRealNeonApi } from \"./lib/neon-api-real.js\";\nexport type {\n\tAppliedChange,\n\tBranchTarget,\n\tBranchTuning,\n\tBranchTuningFn,\n\tBucketAccessLevel,\n\tBucketDef,\n\tComputeSettings,\n\tComputeUnit,\n\tConfig,\n\tConflictReport,\n\tCredentialPrincipalType,\n\tCredentialScope,\n\tDataApiAuthProvider,\n\tDataApiConfig,\n\tDataApiExternalAuthConfig,\n\tDataApiInput,\n\tDataApiNeonAuthConfig,\n\tDataApiSettings,\n\tDurationString,\n\tDurationUnit,\n\tFunctionDef,\n\tFunctionDevConfig,\n\tFunctionRuntime,\n\tFunctionTuning,\n\tPostgresConfig,\n\tPreviewInput,\n\tPreviewTuning,\n\tPushResult,\n\tResolvedBranchConfig,\n\tResolvedBucketConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceEnabled,\n\tServiceToggle,\n\tServiceToggleInput,\n} from \"./lib/types.js\";\n// ─── Config types (used in neon.ts and in operation return values) ────────────\nexport { DATA_API_AUTH_PROVIDERS } from \"./lib/types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,MAAa,SAAS;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;AAGA,MAAa,UAAU;CACtB,QAAQ;CACR,cAAc;CACd,QAAQ;CACR,iBAAiB;CACjB,SAAS;CACT,cAAc;CACd,iBAAiB;CACjB,UAAU;CACV,gBAAgB;CAChB,UAAU;CACV,SAAS;CACT,SAAS;CACT,cAAc;AACf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neondatabase/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Config-as-Code for the Neon Platform. Define a `neon.ts` policy and inspect/diff/deploy it against the Neon API as plain TypeScript functions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"jiti": "^2.7.0",
|
|
52
52
|
"zod": "^4.4.3",
|
|
53
|
-
"@neon/sdk": "
|
|
53
|
+
"@neon/sdk": "1.0.0"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
56
|
-
"node": ">=
|
|
56
|
+
"node": ">=20.19.0"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"provenance": false
|