@neondatabase/config 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,7 @@ export default defineConfig({
31
31
  triggers: [
32
32
  { type: "schedule", name: "hourly", cron: "0 * * * *" },
33
33
  ],
34
+ customDomains: ["docs.example.com"], // default branch only
34
35
  },
35
36
  },
36
37
  },
@@ -44,11 +45,62 @@ export default defineConfig({
44
45
 
45
46
  A policy is split into a **static** existential set and a **dynamic** `branch` closure:
46
47
 
47
- - **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`.
48
- - **`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.
48
+ - **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`. Function source, env, and triggers apply on every branch. `customDomains` is default-branch-only ([details](#default-branch-only-fields)).
49
+ - **`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`, per-function `runtime`, and per-function `customDomains`. 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.
49
50
 
50
51
  Service toggles accept `true` / `{}` / `{ enabled: true }` (enabled) and `false` / `{ enabled: false }` (disabled). Function slugs (record keys) must match `^[a-z0-9]{1,20}$`.
51
52
 
53
+ ### Default-branch-only fields
54
+
55
+ A hostname is globally unique, so a child checkout would 409 if it inherited the production list. `plan`, `apply`, and `neon checkout` apply a function's static `customDomains` only when the target is the project's default branch. Today that is the only field with this rule.
56
+
57
+ ```ts
58
+ export default defineConfig({
59
+ preview: {
60
+ functions: {
61
+ hello: {
62
+ name: "Hello",
63
+ source: "./functions/hello.ts",
64
+ customDomains: ["docs.example.com"],
65
+ },
66
+ },
67
+ },
68
+ });
69
+ ```
70
+
71
+ | Declaration | Default branch | Other branches |
72
+ | --- | --- | --- |
73
+ | Static `customDomains` omitted from the `branch` closure | Register the list | Skip the list; still deploy the function |
74
+ | `branch` closure sets `customDomains: ["preview.example.com"]` | Replace the static list | Register that list |
75
+ | `branch` closure sets `customDomains: []` | Register nothing | Register nothing |
76
+
77
+ `neon checkout preview` deploys `hello` and does not register `docs.example.com`. Give that branch its own hostname:
78
+
79
+ ```ts
80
+ branch: (branch) =>
81
+ branch.name === "preview"
82
+ ? {
83
+ preview: {
84
+ functions: {
85
+ hello: { customDomains: ["preview.example.com"] },
86
+ },
87
+ },
88
+ }
89
+ : {},
90
+ ```
91
+
92
+ Neither skipping the static list nor setting `[]` deletes a remote registration. Delete with `neon function domains delete`.
93
+
94
+ ### Function custom domains (beta)
95
+
96
+ `customDomains` is a list of hostnames on a function. v1 supports functions only. Hostnames are unique across functions in the resolved policy (after the default-branch rule and any tuning merge). `plan` previews registrations and retargets; `apply` performs them. `apply` registers a hostname that is not on this branch, retargets one that already points at another function on this branch (`--update-existing` / `updateExisting: true`; DELETE then POST), and leaves omitted remotes alone.
97
+
98
+ Retarget waits for this apply's function deployment, then re-reads ownership before DELETE. If that read shows a different owner than the plan, apply stops without DELETE. DELETE is still unconditional on the hostname, so a move between that read and DELETE can remove someone else's registration. If POST fails after DELETE, the error reports both operations and tells you to inspect with `neon function domains list`.
99
+
100
+ DNS is yours: `plan` / `apply` print `CNAME <hostname> -> <cname_target>` when the hostname is registered to the declared function and the API returns a target. A blocking conflict does not print CNAME. Point the hostname at that target. An empty target means the region has no custom-domains front door.
101
+
102
+ A project that can deploy functions can still reject custom-domain list and register with HTTP 404 until custom domains are enabled for that project.
103
+
52
104
  ### Shipping a prebuilt directory (`bundler: "none"`)
53
105
 
54
106
  esbuild is the default. A file `source` is the entry; a directory is searched for `index.ts`, then `index.js`, then `index.mjs`. Set `bundler: "none"` to zip `source` as-is instead — a directory whose root contains `index.mjs` or `index.js`, or a single file of that name:
@@ -177,7 +229,7 @@ The three operations mirror the Terraform mental model: **`inspect`** (read live
177
229
 
178
230
  ```ts
179
231
  import config from "../neon";
180
- import { inspect, plan, apply } from "@neon/config/v1";
232
+ import { inspect, plan, apply } from "@neon/config-runtime/v1";
181
233
 
182
234
  const target = { projectId: "patient-art-12345", branchId: "main" };
183
235
 
@@ -203,16 +255,11 @@ const live = await inspect(target);
203
255
 
204
256
  ## Lower-level engine
205
257
 
206
- `inspect` / `plan` / `apply` are thin wrappers over `pullConfig(options)` / `pushConfig(config, options)` (both require `projectId` + `branchId`), which are also exported for advanced/programmatic use along with `defineConfig`, `loadConfigFromFile` (optional `neon.ts` loader), `createRealNeonApi`, the `PlatformError` base class + `ErrorCode` enum, the `errors` and `schemas` namespaces, and the supporting types.
258
+ `inspect` / `plan` / `apply` / `pushConfig` / `pullConfig` live in [`@neon/config-runtime`](../config-runtime). This package keeps `defineConfig`, `loadConfigFromFile`, `createRealNeonApi`, `diffConfig`, the `PlatformError` base class + `ErrorCode` enum, the `errors` and `schemas` namespaces, and the supporting types.
207
259
 
208
260
  ```ts
209
261
  import {
210
262
  defineConfig,
211
- inspect,
212
- plan,
213
- apply,
214
- pushConfig,
215
- pullConfig,
216
263
  loadConfigFromFile,
217
264
  createRealNeonApi,
218
265
  PlatformError,
@@ -228,6 +275,7 @@ import {
228
275
  - `auth: {}` and `dataApi: {}` enable those integrations with Neon defaults. Absence of `dataApi` leaves an existing Data API alone. `dataApi: false` / `dataApi.enabled: false` disables it (an override: `updateExisting` or `confirm`). `auth.enabled: false` still leaves Auth alone.
229
276
  - Mutable branch drift (`protected`, `ttl`, `postgres.computeSettings`) is reported as a conflict unless `updateExisting` is passed (or a `confirm` callback is supplied to `pushConfig`).
230
277
  - Applying to a branch with the `protected` flag set on Neon requires `allowProtectedBranch` (or a `confirm` callback).
278
+ - Omitted `customDomains` leave remote registrations alone. The static list applies only on the default branch (see [Default-branch-only fields](#default-branch-only-fields)). `[]` in branch tuning registers nothing on that branch. Delete with `neon function domains delete`.
231
279
 
232
280
  ## Env vars
233
281
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AppliedChange, BranchTarget, BranchTuning, BranchTuningFn, BucketAccessLevel, BucketDef, ComputeSettings, ComputeUnit, Config, ConflictReport, CredentialPrincipalType, CredentialScope, DATA_API_AUTH_PROVIDERS, DataApiAuthProvider, DataApiConfig, DataApiExternalAuthConfig, DataApiInput, DataApiNeonAuthConfig, DataApiSettings, DurationString, DurationUnit, ExternalPackageDef, ExternalPackageEntry, FunctionBundle, FunctionBundler, FunctionBundlerInput, FunctionDef, FunctionDevConfig, FunctionRuntime, FunctionScheduleTriggerDef, FunctionTuning, PostgresConfig, PreviewInput, PreviewTuning, PushResult, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedDataApiConfig, ResolvedExternalPackage, ResolvedFunctionConfig, ResolvedFunctionScheduleTrigger, ResolvedPreviewConfig, ServiceEnabled, ServiceToggle, ServiceToggleInput } from "./lib/types.js";
2
2
  import { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PartialBranchCreateError, PlatformError, PushAbortedError, PushConflictError, isPartialBranchCreateError, isPlatformError } from "./lib/errors.js";
3
- import { CreateBranchInput, CreateBucketInput, CreateCredentialInput, CreateProjectInput, CreateTriggerInput, DeployFunctionInput, EnableDataApiInput, GetConnectionUriInput, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBranchStorageSnapshot, NeonBucketSnapshot, NeonCredentialMeta, NeonCredentialReveal, NeonCredentialSecret, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, NeonTriggerSnapshot, UpdateBranchInput, UpdateTriggerInput } from "./lib/neon-api.js";
3
+ import { CreateBranchInput, CreateBucketInput, CreateCredentialInput, CreateProjectInput, CreateTriggerInput, DeployFunctionInput, EnableDataApiInput, GetConnectionUriInput, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBranchStorageSnapshot, NeonBucketSnapshot, NeonCredentialMeta, NeonCredentialReveal, NeonCredentialSecret, NeonCustomDomainSnapshot, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, NeonTriggerSnapshot, UpdateBranchInput, UpdateTriggerInput } from "./lib/neon-api.js";
4
4
  import { createNeonApiFromOptions } from "./lib/auth.js";
5
5
  import { CredentialFeatureFlags, credentialScopesSatisfied, deriveCredentialScopes } from "./lib/credentials.js";
6
6
  import { defineConfig, resolveConfig } from "./lib/define-config.js";
@@ -10,4 +10,4 @@ import { FUNCTION_ARCHIVE_ENTRIES, FUNCTION_SOURCE_ENTRIES, FunctionArchiveEntry
10
10
  import { LoadConfigOptions, loadConfigFromFile } from "./lib/loader.js";
11
11
  import { createRealNeonApi } from "./lib/neon-api-real.js";
12
12
  import { errors, schemas } from "./v1.js";
13
- export { AppliedChange, BranchTarget, BranchTuning, BranchTuningFn, BucketAccessLevel, BucketDef, ComputeSettings, ComputeUnit, Config, ConfigLoadError, ConfigValidationError, ConflictReport, CreateBranchInput, CreateBucketInput, CreateCredentialInput, CreateProjectInput, CreateTriggerInput, CredentialFeatureFlags, CredentialPrincipalType, CredentialScope, DATA_API_AUTH_PROVIDERS, DataApiAuthProvider, DataApiConfig, DataApiExternalAuthConfig, DataApiInput, DataApiNeonAuthConfig, DataApiSettings, DeployFunctionInput, DiffOptions, DiffResult, DurationString, DurationUnit, EnableDataApiInput, ErrorCode, ExternalPackageDef, ExternalPackageEntry, FUNCTION_ARCHIVE_ENTRIES, FUNCTION_SOURCE_ENTRIES, FunctionArchiveEntry, FunctionBundle, FunctionBundler, FunctionBundlerInput, FunctionDef, FunctionDevConfig, FunctionRuntime, FunctionScheduleTriggerDef, FunctionSourceEntry, FunctionTuning, GetConnectionUriInput, LoadConfigOptions, MissingContextError, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBranchStorageSnapshot, NeonBucketSnapshot, NeonCredentialMeta, NeonCredentialReveal, NeonCredentialSecret, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, NeonTriggerSnapshot, PartialBranchCreateError, PlanStep, PlatformError, PostgresConfig, PreviewInput, PreviewTuning, PushAbortedError, PushConflictError, PushResult, RemotePreviewState, RemoteServiceState, RemoteState, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedDataApiConfig, ResolvedExternalPackage, ResolvedFunctionConfig, ResolvedFunctionScheduleTrigger, ResolvedPreviewConfig, ServiceEnabled, ServiceToggle, ServiceToggleInput, UpdateBranchInput, UpdateTriggerInput, createNeonApiFromOptions, createRealNeonApi, credentialScopesSatisfied, defineConfig, deriveCredentialScopes, diffConfig, errors, externalPackageRoot, isFunctionArchiveEntry, isPartialBranchCreateError, isPlatformError, loadConfigFromFile, packagesToStage, pickFunctionSourceEntry, resolveConfig, schemas };
13
+ export { AppliedChange, BranchTarget, BranchTuning, BranchTuningFn, BucketAccessLevel, BucketDef, ComputeSettings, ComputeUnit, Config, ConfigLoadError, ConfigValidationError, ConflictReport, CreateBranchInput, CreateBucketInput, CreateCredentialInput, CreateProjectInput, CreateTriggerInput, CredentialFeatureFlags, CredentialPrincipalType, CredentialScope, DATA_API_AUTH_PROVIDERS, DataApiAuthProvider, DataApiConfig, DataApiExternalAuthConfig, DataApiInput, DataApiNeonAuthConfig, DataApiSettings, DeployFunctionInput, DiffOptions, DiffResult, DurationString, DurationUnit, EnableDataApiInput, ErrorCode, ExternalPackageDef, ExternalPackageEntry, FUNCTION_ARCHIVE_ENTRIES, FUNCTION_SOURCE_ENTRIES, FunctionArchiveEntry, FunctionBundle, FunctionBundler, FunctionBundlerInput, FunctionDef, FunctionDevConfig, FunctionRuntime, FunctionScheduleTriggerDef, FunctionSourceEntry, FunctionTuning, GetConnectionUriInput, LoadConfigOptions, MissingContextError, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBranchStorageSnapshot, NeonBucketSnapshot, NeonCredentialMeta, NeonCredentialReveal, NeonCredentialSecret, NeonCustomDomainSnapshot, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, NeonTriggerSnapshot, PartialBranchCreateError, PlanStep, PlatformError, PostgresConfig, PreviewInput, PreviewTuning, PushAbortedError, PushConflictError, PushResult, RemotePreviewState, RemoteServiceState, RemoteState, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedDataApiConfig, ResolvedExternalPackage, ResolvedFunctionConfig, ResolvedFunctionScheduleTrigger, ResolvedPreviewConfig, ServiceEnabled, ServiceToggle, ServiceToggleInput, UpdateBranchInput, UpdateTriggerInput, createNeonApiFromOptions, createRealNeonApi, credentialScopesSatisfied, defineConfig, deriveCredentialScopes, diffConfig, errors, externalPackageRoot, isFunctionArchiveEntry, isPartialBranchCreateError, isPlatformError, loadConfigFromFile, packagesToStage, pickFunctionSourceEntry, resolveConfig, schemas };
@@ -0,0 +1,7 @@
1
+ //#region src/lib/custom-domain.d.ts
2
+ declare function normalizeCustomDomain(value: string): string;
3
+ /** `undefined` when `value` is a usable custom domain; otherwise the schema message. */
4
+ declare function customDomainValidationError(value: string): string | undefined;
5
+ //#endregion
6
+ export { customDomainValidationError, normalizeCustomDomain };
7
+ //# sourceMappingURL=custom-domain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-domain.d.ts","names":[],"sources":["../../src/lib/custom-domain.ts"],"mappings":";iBAAgB,qBAAA;AAAhB;AAYgB,iBAAA,2BAAA,CAA2B,KAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,SAAA"}
@@ -0,0 +1,20 @@
1
+ //#region src/lib/custom-domain.ts
2
+ function normalizeCustomDomain(value) {
3
+ return value.trim().toLowerCase().replace(/\.$/, "");
4
+ }
5
+ const DNS_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
6
+ const MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH = 3;
7
+ const MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH = 253;
8
+ const CUSTOM_DOMAIN_RE = new RegExp(`^(?=.{${MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH},${MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH}}$)(?:${DNS_LABEL}\\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})$`);
9
+ /** `undefined` when `value` is a usable custom domain; otherwise the schema message. */
10
+ function customDomainValidationError(value) {
11
+ if (typeof value !== "string") return "custom domain must be a hostname string";
12
+ const normalized = normalizeCustomDomain(value);
13
+ if (normalized.length === 0) return `custom domain ${JSON.stringify(value)} is empty after normalizing`;
14
+ if (normalized.length < MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH || normalized.length > MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH) return `custom domain ${JSON.stringify(value)} must be ${MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH}–${MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH} characters after normalizing`;
15
+ if (!CUSTOM_DOMAIN_RE.test(normalized)) return `custom domain ${JSON.stringify(value)} is not a DNS hostname`;
16
+ }
17
+ //#endregion
18
+ export { customDomainValidationError, normalizeCustomDomain };
19
+
20
+ //# sourceMappingURL=custom-domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-domain.js","names":[],"sources":["../../src/lib/custom-domain.ts"],"sourcesContent":["export function normalizeCustomDomain(value: string): string {\n\treturn value.trim().toLowerCase().replace(/\\.$/, \"\");\n}\n\nconst DNS_LABEL = \"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\";\nconst MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH = 3;\nconst MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH = 253;\nconst CUSTOM_DOMAIN_RE = new RegExp(\n\t`^(?=.{${MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH},${MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH}}$)(?:${DNS_LABEL}\\\\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})$`,\n);\n\n/** `undefined` when `value` is a usable custom domain; otherwise the schema message. */\nexport function customDomainValidationError(value: string): string | undefined {\n\tif (typeof value !== \"string\") {\n\t\treturn \"custom domain must be a hostname string\";\n\t}\n\tconst normalized = normalizeCustomDomain(value);\n\tif (normalized.length === 0) {\n\t\treturn `custom domain ${JSON.stringify(value)} is empty after normalizing`;\n\t}\n\tif (\n\t\tnormalized.length < MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH ||\n\t\tnormalized.length > MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH\n\t) {\n\t\treturn `custom domain ${JSON.stringify(value)} must be ${MIN_NORMALIZED_CUSTOM_DOMAIN_LENGTH}–${MAX_NORMALIZED_CUSTOM_DOMAIN_LENGTH} characters after normalizing`;\n\t}\n\tif (!CUSTOM_DOMAIN_RE.test(normalized)) {\n\t\treturn `custom domain ${JSON.stringify(value)} is not a DNS hostname`;\n\t}\n\treturn undefined;\n}\n"],"mappings":";AAAA,SAAgB,sBAAsB,OAAuB;CAC5D,OAAO,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;AACpD;AAEA,MAAM,YAAY;AAClB,MAAM,sCAAsC;AAC5C,MAAM,sCAAsC;AAC5C,MAAM,mBAAmB,IAAI,OAC5B,SAAS,oCAAoC,GAAG,oCAAoC,QAAQ,UAAU,0CACvG;;AAGA,SAAgB,4BAA4B,OAAmC;CAC9E,IAAI,OAAO,UAAU,UACpB,OAAO;CAER,MAAM,aAAa,sBAAsB,KAAK;CAC9C,IAAI,WAAW,WAAW,GACzB,OAAO,iBAAiB,KAAK,UAAU,KAAK,EAAE;CAE/C,IACC,WAAW,SAAS,uCACpB,WAAW,SAAS,qCAEpB,OAAO,iBAAiB,KAAK,UAAU,KAAK,EAAE,WAAW,oCAAoC,GAAG,oCAAoC;CAErI,IAAI,CAAC,iBAAiB,KAAK,UAAU,GACpC,OAAO,iBAAiB,KAAK,UAAU,KAAK,EAAE;AAGhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"define-config.d.ts","names":[],"sources":["../../src/lib/define-config.ts"],"mappings":";;;;;;AAyBoB;AAcI;AACR;AAAf,KADI,mBACJ,CAAA,OAAA,CAAA,GAAA,cAAA,CAAe,OAAf,CAAA,SAAA,IAAA,GAAA,CACI,OADJ,CAAA,SAAA,CAAA;EACI,YAAA,EAAA,UAAA;AAAO,CAAA,CAAA,GAAA,KAAA,GAAA,IAAA,GAAA,KAAA;AAiBZ;AAqBA;AAAwB;AACH;AAApB;AACkB;AAAf;AACC;AAAU;AACV;AACD,KA1BQ,oBAAA,GA0BR,+QAAA;AAAU;AAAY;AAAC;AAoBH;AAAa;AACJ;AAAI;AAEnC;AAC8B;AAAI;AAAS;AAoC7C;AAA4B;AACR;AACG;AACA;AAQf,KA5EI,YA4EJ,CAAA,IAAA,EAAA,OAAA,CAAA,GA3EP,mBA2EO,CA3Ea,OA2Eb,CAAA,SAAA,IAAA,GA1EJ,cA0EI,CA1EW,IA0EX,CAAA,SAAA,IAAA,GAzEH,OAyEG,GAzEO,YAyEP,GAxEH,oBAwEG,GAvEJ,OAuEI,GAvEM,YAuEN;AAAO;AAIS;AAAM;AAAnB;AAIA;AAAU;AAAmC;AAApB;AACX;AAAf;AACC;AAAM;AAAS;AAAtB;AAAM;AA4BV;AAA6B;AACpB;AACA,KA3FJ,mBA2FI,CAAA,OAAA,CAAA,GAAA,CA3F4B,OA2F5B,SAAA;EACN,SAAA,EAAA,KAAA,EAAA;AAAoB,CAAA,GAAA;EA4LP,SAAA,EAAA,iBAvRiB,IAAI;gBAEnC;;;4BAC8B,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCpB,gCACI,kEACG,4DACA;SAQf,OAAO;YAIJ,aAAa,MAAM;YAInB,UAAU,eAAe,oBAAoB;WAC9C,eAAe;IACrB,OAAO,MAAM,SAAS;;;;;;;;iBA4BV,aAAA,SACP,gBACA,eACN;;;;;;iBA4La,eAAA"}
1
+ {"version":3,"file":"define-config.d.ts","names":[],"sources":["../../src/lib/define-config.ts"],"mappings":";;;;;;AA0BoB;AAcI;AACR;AAAf,KADI,mBACJ,CAAA,OAAA,CAAA,GAAA,cAAA,CAAe,OAAf,CAAA,SAAA,IAAA,GAAA,CACI,OADJ,CAAA,SAAA,CAAA;EACI,YAAA,EAAA,UAAA;AAAO,CAAA,CAAA,GAAA,KAAA,GAAA,IAAA,GAAA,KAAA;AAiBZ;AAqBA;AAAwB;AACH;AAApB;AACkB;AAAf;AACC;AAAU;AACV;AACD,KA1BQ,oBAAA,GA0BR,+QAAA;AAAU;AAAY;AAAC;AAoBH;AAAa;AACJ;AAAI;AAEnC;AAC8B;AAAI;AAAS;AAoC7C;AAA4B;AACR;AACG;AACA;AAQf,KA5EI,YA4EJ,CAAA,IAAA,EAAA,OAAA,CAAA,GA3EP,mBA2EO,CA3Ea,OA2Eb,CAAA,SAAA,IAAA,GA1EJ,cA0EI,CA1EW,IA0EX,CAAA,SAAA,IAAA,GAzEH,OAyEG,GAzEO,YAyEP,GAxEH,oBAwEG,GAvEJ,OAuEI,GAvEM,YAuEN;AAAO;AAIS;AAAM;AAAnB;AAIA;AAAU;AAAmC;AAApB;AACX;AAAf;AACC;AAAM;AAAS;AAAtB;AAAM;AA4BV;AAA6B;AACpB;AACA,KA3FJ,mBA2FI,CAAA,OAAA,CAAA,GAAA,CA3F4B,OA2F5B,SAAA;EACN,SAAA,EAAA,KAAA,EAAA;AAAoB,CAAA,GAAA;EAiOP,SAAA,EAAA,iBA5TiB,IAAI;gBAEnC;;;4BAC8B,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCpB,gCACI,kEACG,4DACA;SAQf,OAAO;YAIJ,aAAa,MAAM;YAInB,UAAU,eAAe,oBAAoB;WAC9C,eAAe;IACrB,OAAO,MAAM,SAAS;;;;;;;;iBA4BV,aAAA,SACP,gBACA,eACN;;;;;;iBAiOa,eAAA"}
@@ -1,4 +1,5 @@
1
1
  import { ConfigValidationError } from "./errors.js";
2
+ import { normalizeCustomDomain } from "./custom-domain.js";
2
3
  import { parseBranchTtl } from "./duration.js";
3
4
  import { normalizeExternalPackage } from "./external-packages.js";
4
5
  import { branchTuningSchema, configInputSchema, formatZodIssues } from "./schema.js";
@@ -70,7 +71,7 @@ function resolveConfig(config, branch) {
70
71
  }
71
72
  if (tuning.protected !== void 0) resolved.protected = tuning.protected;
72
73
  if (tuning.postgres?.computeSettings) resolved.postgres = { computeSettings: { ...tuning.postgres.computeSettings } };
73
- const preview = resolvePreviewConfig(config.preview, tuning);
74
+ const preview = resolvePreviewConfig(config.preview, tuning, branch);
74
75
  if (preview) resolved.preview = preview;
75
76
  return resolved;
76
77
  }
@@ -134,11 +135,13 @@ function normalizeDataApiSettings(settings) {
134
135
  * block so the field can be omitted entirely. Function slugs / bucket names come from the
135
136
  * record keys.
136
137
  */
137
- function resolvePreviewConfig(preview, tuning) {
138
+ function resolvePreviewConfig(preview, tuning, branch) {
138
139
  if (!preview) return void 0;
139
140
  const fnTuning = tuning.preview?.functions ?? {};
141
+ const functions = Object.entries(preview.functions ?? {}).map(([slug, def]) => resolveFunctionConfig(slug, def, fnTuning[slug] ?? {}, branch));
142
+ assertUniqueResolvedCustomDomains(functions);
140
143
  return {
141
- functions: Object.entries(preview.functions ?? {}).map(([slug, def]) => resolveFunctionConfig(slug, def, fnTuning[slug] ?? {})),
144
+ functions,
142
145
  buckets: Object.entries(preview.buckets ?? {}).map(([name, def]) => ({
143
146
  name,
144
147
  access: def.access ?? "private"
@@ -146,7 +149,8 @@ function resolvePreviewConfig(preview, tuning) {
146
149
  aiGatewayEnabled: isServiceEnabled(preview.aiGateway)
147
150
  };
148
151
  }
149
- function resolveFunctionConfig(slug, def, tuning) {
152
+ function resolveFunctionConfig(slug, def, tuning, branch) {
153
+ const customDomains = resolveFunctionCustomDomains(def, tuning, branch);
150
154
  return {
151
155
  slug,
152
156
  name: def.name,
@@ -162,9 +166,22 @@ function resolveFunctionConfig(slug, def, tuning) {
162
166
  cron: trigger.cron,
163
167
  functionPath: trigger.functionPath ?? "/",
164
168
  enabled: trigger.enabled ?? true
165
- })) } : {}
169
+ })) } : {},
170
+ ...customDomains !== void 0 ? { customDomains } : {}
166
171
  };
167
172
  }
173
+ function resolveFunctionCustomDomains(def, tuning, branch) {
174
+ if (tuning.customDomains !== void 0) return tuning.customDomains.map(normalizeCustomDomain);
175
+ if (def.customDomains !== void 0 && branch.isDefault === true) return def.customDomains.map(normalizeCustomDomain);
176
+ }
177
+ function assertUniqueResolvedCustomDomains(functions) {
178
+ const byDomain = /* @__PURE__ */ new Map();
179
+ for (const fn of functions) for (const domain of fn.customDomains ?? []) {
180
+ const prior = byDomain.get(domain);
181
+ if (prior !== void 0) throw new ConfigValidationError([`custom domain "${domain}" is used by both function "${prior}" and "${fn.slug}"`]);
182
+ byDomain.set(domain, fn.slug);
183
+ }
184
+ }
168
185
  /**
169
186
  * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes
170
187
  * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the
@@ -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 { normalizeExternalPackage } from \"./external-packages.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 DEFAULT_FUNCTION_BUNDLER = \"esbuild\" 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> =\n\tServiceEnabled<DataApi> extends true\n\t\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t\t? false\n\t\t\t: true\n\t\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 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\tdataApiPolicy:\n\t\t\tconfig.dataApi === undefined\n\t\t\t\t? \"omitted\"\n\t\t\t\t: isDataApiEnabled(config.dataApi)\n\t\t\t\t\t? \"enabled\"\n\t\t\t\t\t: \"disabled\",\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\tbundler: def.bundler ?? DEFAULT_FUNCTION_BUNDLER,\n\t\t// Normalized only when declared, so a policy without it resolves unchanged and takes\n\t\t// the pre-existing bundling path. Both bundlers read it; `neon dev` mirrors it so a\n\t\t// local run leaves the same packages unbundled as a deploy.\n\t\t...(def.externalPackages\n\t\t\t? {\n\t\t\t\t\texternalPackages: def.externalPackages.map(\n\t\t\t\t\t\tnormalizeExternalPackage,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: {}),\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t\t...(def.triggers\n\t\t\t? {\n\t\t\t\t\ttriggers: def.triggers.map((trigger) => ({\n\t\t\t\t\t\ttype: \"schedule\" as const,\n\t\t\t\t\t\tname: trigger.name,\n\t\t\t\t\t\tcron: trigger.cron,\n\t\t\t\t\t\tfunctionPath: trigger.functionPath ?? \"/\",\n\t\t\t\t\t\tenabled: trigger.enabled ?? true,\n\t\t\t\t\t})),\n\t\t\t\t}\n\t\t\t: {}),\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 { normalizeCustomDomain } from \"./custom-domain.js\";\nimport { parseBranchTtl } from \"./duration.js\";\nimport { ConfigValidationError } from \"./errors.js\";\nimport { normalizeExternalPackage } from \"./external-packages.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 DEFAULT_FUNCTION_BUNDLER = \"esbuild\" 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> =\n\tServiceEnabled<DataApi> extends true\n\t\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t\t? false\n\t\t\t: true\n\t\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 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\tdataApiPolicy:\n\t\t\tconfig.dataApi === undefined\n\t\t\t\t? \"omitted\"\n\t\t\t\t: isDataApiEnabled(config.dataApi)\n\t\t\t\t\t? \"enabled\"\n\t\t\t\t\t: \"disabled\",\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, branch);\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\tbranch: BranchTarget,\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] ?? {}, branch),\n\t);\n\tassertUniqueResolvedCustomDomains(functions);\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\tbranch: BranchTarget,\n): ResolvedFunctionConfig {\n\tconst customDomains = resolveFunctionCustomDomains(def, tuning, branch);\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\tbundler: def.bundler ?? DEFAULT_FUNCTION_BUNDLER,\n\t\t// Normalized only when declared, so a policy without it resolves unchanged and takes\n\t\t// the pre-existing bundling path. Both bundlers read it; `neon dev` mirrors it so a\n\t\t// local run leaves the same packages unbundled as a deploy.\n\t\t...(def.externalPackages\n\t\t\t? {\n\t\t\t\t\texternalPackages: def.externalPackages.map(\n\t\t\t\t\t\tnormalizeExternalPackage,\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: {}),\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t\t...(def.triggers\n\t\t\t? {\n\t\t\t\t\ttriggers: def.triggers.map((trigger) => ({\n\t\t\t\t\t\ttype: \"schedule\" as const,\n\t\t\t\t\t\tname: trigger.name,\n\t\t\t\t\t\tcron: trigger.cron,\n\t\t\t\t\t\tfunctionPath: trigger.functionPath ?? \"/\",\n\t\t\t\t\t\tenabled: trigger.enabled ?? true,\n\t\t\t\t\t})),\n\t\t\t\t}\n\t\t\t: {}),\n\t\t...(customDomains !== undefined ? { customDomains } : {}),\n\t};\n}\n\nfunction resolveFunctionCustomDomains(\n\tdef: FunctionDef,\n\ttuning: FunctionTuning,\n\tbranch: BranchTarget,\n): string[] | undefined {\n\tif (tuning.customDomains !== undefined) {\n\t\treturn tuning.customDomains.map(normalizeCustomDomain);\n\t}\n\t// Hostnames are globally unique, so a child checkout 409s if it inherits this list.\n\tif (def.customDomains !== undefined && branch.isDefault === true) {\n\t\treturn def.customDomains.map(normalizeCustomDomain);\n\t}\n\treturn undefined;\n}\n\nfunction assertUniqueResolvedCustomDomains(\n\tfunctions: ResolvedFunctionConfig[],\n): void {\n\tconst byDomain = new Map<string, string>();\n\tfor (const fn of functions) {\n\t\tfor (const domain of fn.customDomains ?? []) {\n\t\t\tconst prior = byDomain.get(domain);\n\t\t\tif (prior !== undefined) {\n\t\t\t\tthrow new ConfigValidationError([\n\t\t\t\t\t`custom domain \"${domain}\" is used by both function \"${prior}\" and \"${fn.slug}\"`,\n\t\t\t\t]);\n\t\t\t}\n\t\t\tbyDomain.set(domain, fn.slug);\n\t\t}\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,5 +1,5 @@
1
1
  import { BucketAccessLevel, ComputeSettings, ConflictReport, DataApiSettings, ResolvedBranchConfig, ResolvedFunctionConfig, ResolvedFunctionScheduleTrigger } from "./types.js";
2
- import { EnableDataApiInput, NeonBranchSnapshot, NeonBucketSnapshot, NeonEndpointSnapshot, NeonFunctionSnapshot, NeonTriggerSnapshot } from "./neon-api.js";
2
+ import { EnableDataApiInput, NeonBranchSnapshot, NeonBucketSnapshot, NeonCustomDomainSnapshot, NeonEndpointSnapshot, NeonFunctionSnapshot, NeonTriggerSnapshot } from "./neon-api.js";
3
3
 
4
4
  //#region src/lib/diff.d.ts
5
5
 
@@ -89,6 +89,21 @@ type PlanStep = {
89
89
  triggerId: string;
90
90
  functionSlug: string;
91
91
  trigger: ResolvedFunctionScheduleTrigger;
92
+ } | {
93
+ kind: "register-custom-domain";
94
+ projectId: string;
95
+ branchId: string;
96
+ branchName: string;
97
+ functionSlug: string;
98
+ domain: string;
99
+ } | {
100
+ kind: "retarget-custom-domain";
101
+ projectId: string;
102
+ branchId: string;
103
+ branchName: string;
104
+ functionSlug: string;
105
+ previousSlug: string;
106
+ domain: string;
92
107
  };
93
108
  interface RemoteServiceState {
94
109
  databaseName: string;
@@ -109,6 +124,8 @@ interface RemotePreviewState {
109
124
  buckets: NeonBucketSnapshot[];
110
125
  functions: NeonFunctionSnapshot[];
111
126
  triggers: NeonTriggerSnapshot[];
127
+ /** Absent on pre-feature snapshots; treated as none. */
128
+ customDomains?: NeonCustomDomainSnapshot[];
112
129
  }
113
130
  interface RemoteState {
114
131
  projectId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","names":[],"sources":["../../src/lib/diff.ts"],"mappings":";;;;;;;AAuBA;AAAoB;AAoBP,KApBD,QAAA,GAoBC;EAgBF,IAAA,EAAA,mBAAA;EAQE,SAAA,EAAA,MAAA;EAeG,QAAA,EAAA,MAAA;EAcT,UAAA,EAAA,MAAA;EAUK,SAAA,EAAA,MAAA,GAAA,IAAA;AASA,CAAA,GAAA;EAA+B,IAAA,EAAA,yBAAA;EAG1B,SAAA,EAAA,MAAA;EAgBA,QAAA,EAAA,MAAA;EAAkB,UAAA,EAAA,MAAA;EACzB,SAAA,EAAA,OAAA;AACE,CAAA,GAAA;EACD,IAAA,EAAA,iBAAA;EAAmB,SAAA,EAAA,MAAA;EAGb,UAAA,EAAA,MAAW;EAAA,UAAA,EAAA,MAAA;EAEnB,QAAA,EAnGI,eAmGJ;AACG,CAAA,GAAA;EACD,IAAA,EAAA,aAAA;EACA,SAAA,EAAA,MAAA;EAAkB,QAAA,EAAA,MAAA;EAGZ,UAAA,EAAA,MAAW;EAQX,YAAA,CAAU,EAAA,MAAA;AAAA,CAAA,GAAA;EACpB,IAAA,EAAA,iBAAA;EACK,SAAA,EAAA,MAAA;EAAc,QAAA,EAAA,MAAA;EAMV,UAAA,EAAA,MAAU;EAAA,YAAA,EAAA,MAAA;EACjB;EACA,KAAA,CAAA,EA3GE,kBA2GF;AACC,CAAA,GAAA;EACP,IAAA,EAAA,iBAAA;EAAU,SAAA,EAAA,MAAA;;;;YArGA;;;;;;;;;;;;;eAeG;;;;;;;;;;;;;MAcT;;;;;;;;;WAUK;;;;;;;;WASA;;UAGK,kBAAA;;;;;;;;;oBASE;;;;;;UAOF,kBAAA;WACP;aACE;YACD;;UAGM,WAAA;;UAER;aACG;YACD;YACA;;UAGM,WAAA;;;;;;;UAQA,UAAA;QACV;aACK;;;;;iBAMI,UAAA,SACP,8BACA,sBACC,cACP"}
1
+ {"version":3,"file":"diff.d.ts","names":[],"sources":["../../src/lib/diff.ts"],"mappings":";;;;;;;AAwBA;AAAoB;AAoBP,KApBD,QAAA,GAoBC;EAgBF,IAAA,EAAA,mBAAA;EAQE,SAAA,EAAA,MAAA;EAeG,QAAA,EAAA,MAAA;EAcT,UAAA,EAAA,MAAA;EAUK,SAAA,EAAA,MAAA,GAAA,IAAA;AASA,CAAA,GAAA;EAA+B,IAAA,EAAA,yBAAA;EAoB1B,SAAA,EAAA,MAAA;EAgBA,QAAA,EAAA,MAAA;EAAkB,UAAA,EAAA,MAAA;EACzB,SAAA,EAAA,OAAA;AACE,CAAA,GAAA;EACD,IAAA,EAAA,iBAAA;EAEM,SAAA,EAAA,MAAA;EAAwB,UAAA,EAAA,MAAA;EAGxB,UAAA,EAAA,MAAW;EAAA,QAAA,EApHf,eAoHe;AAEnB,CAAA,GAAA;EACG,IAAA,EAAA,aAAA;EACD,SAAA,EAAA,MAAA;EACA,QAAA,EAAA,MAAA;EAAkB,UAAA,EAAA,MAAA;EAGZ,YAAA,CAAA,EAAW,MAAA;AAQ5B,CAAA,GAAiB;EAAU,IAAA,EAAA,iBAAA;EACpB,SAAA,EAAA,MAAA;EACK,QAAA,EAAA,MAAA;EAAc,UAAA,EAAA,MAAA;EAMV,YAAA,EAAU,MAAA;EAAA;EACjB,KAAA,CAAA,EA7HE,kBA6HF;AACA,CAAA,GAAA;EACC,IAAA,EAAA,iBAAA;EACP,SAAA,EAAA,MAAA;EAAU,QAAA,EAAA,MAAA;;;YAxHA;;;;;;;;;;;;;eAeG;;;;;;;;;;;;;MAcT;;;;;;;;;WAUK;;;;;;;;WASA;;;;;;;;;;;;;;;;;UAoBK,kBAAA;;;;;;;;;oBASE;;;;;;UAOF,kBAAA;WACP;aACE;YACD;;kBAEM;;UAGA,WAAA;;UAER;aACG;YACD;YACA;;UAGM,WAAA;;;;;;;UAQA,UAAA;QACV;aACK;;;;;iBAMI,UAAA,SACP,8BACA,sBACC,cACP"}
package/dist/lib/diff.js CHANGED
@@ -22,7 +22,9 @@ function diffConfig(config, remote, options) {
22
22
  diffPreview({
23
23
  config,
24
24
  remote,
25
- plan
25
+ options,
26
+ plan,
27
+ conflicts
26
28
  });
27
29
  return {
28
30
  plan,
@@ -44,7 +46,7 @@ function diffConfig(config, remote, options) {
44
46
  * create (first deploy) or an update (re-deploy).
45
47
  */
46
48
  function diffPreview(args) {
47
- const { config, remote, plan } = args;
49
+ const { config, remote, options, plan, conflicts } = args;
48
50
  const preview = config.preview;
49
51
  if (!preview) return;
50
52
  const state = remote.preview ?? {
@@ -52,6 +54,7 @@ function diffPreview(args) {
52
54
  functions: [],
53
55
  triggers: []
54
56
  };
57
+ const customDomains = state.customDomains ?? [];
55
58
  for (const bucket of preview.buckets) {
56
59
  if (state.buckets.some((b) => b.name === bucket.name)) continue;
57
60
  plan.push({
@@ -96,6 +99,70 @@ function diffPreview(args) {
96
99
  trigger
97
100
  });
98
101
  }
102
+ diffFunctionCustomDomains({
103
+ fn,
104
+ customDomains,
105
+ remote,
106
+ options,
107
+ plan,
108
+ conflicts
109
+ });
110
+ }
111
+ }
112
+ const FUNCTION_CUSTOM_DOMAIN_ENTITY = "function";
113
+ function diffFunctionCustomDomains(args) {
114
+ const { fn, customDomains, remote, options, plan, conflicts } = args;
115
+ for (const domain of fn.customDomains ?? []) {
116
+ const existing = customDomains.find((d) => d.domain === domain);
117
+ if (!existing) {
118
+ plan.push({
119
+ kind: "register-custom-domain",
120
+ projectId: remote.projectId,
121
+ branchId: remote.branch.id,
122
+ branchName: remote.branch.name,
123
+ functionSlug: fn.slug,
124
+ domain
125
+ });
126
+ continue;
127
+ }
128
+ if (existing.entityType !== FUNCTION_CUSTOM_DOMAIN_ENTITY) {
129
+ conflicts.push({
130
+ kind: "branch",
131
+ identifier: remote.branch.name,
132
+ field: "customDomain",
133
+ current: {
134
+ entityType: existing.entityType,
135
+ entityId: existing.entityId
136
+ },
137
+ desired: {
138
+ entityType: FUNCTION_CUSTOM_DOMAIN_ENTITY,
139
+ entityId: fn.slug
140
+ },
141
+ reason: `custom domain "${domain}" is registered to entity type "${existing.entityType}", which neon.ts cannot retarget. Delete it with \`neon function domains delete\` or stop declaring it.`
142
+ });
143
+ continue;
144
+ }
145
+ if (existing.entityId === fn.slug) continue;
146
+ if (options.updateExisting) {
147
+ plan.push({
148
+ kind: "retarget-custom-domain",
149
+ projectId: remote.projectId,
150
+ branchId: remote.branch.id,
151
+ branchName: remote.branch.name,
152
+ functionSlug: fn.slug,
153
+ previousSlug: existing.entityId,
154
+ domain
155
+ });
156
+ continue;
157
+ }
158
+ conflicts.push({
159
+ kind: "branch",
160
+ identifier: remote.branch.name,
161
+ field: "customDomain",
162
+ current: existing.entityId,
163
+ desired: fn.slug,
164
+ reason: `custom domain "${domain}" is registered to function "${existing.entityId}". Pass \`updateExisting: true\` (SDK) or \`--update-existing\` (CLI) to retarget it to "${fn.slug}".`
165
+ });
99
166
  }
100
167
  }
101
168
  /**
@@ -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\tNeonTriggerSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedFunctionScheduleTrigger,\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\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: \"disable-data-api\";\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: \"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\t| {\n\t\t\tkind: \"create-trigger\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfunctionSlug: string;\n\t\t\ttrigger: ResolvedFunctionScheduleTrigger;\n\t }\n\t| {\n\t\t\tkind: \"update-trigger\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\ttriggerId: string;\n\t\t\tfunctionSlug: string;\n\t\t\ttrigger: ResolvedFunctionScheduleTrigger;\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\ttriggers: NeonTriggerSnapshot[];\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 * Data API teardown requires explicit `dataApi: false` so omission remains non-destructive.\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\ttriggers: [],\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\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\tfor (const trigger of fn.triggers ?? []) {\n\t\t\tconst remoteTrigger = state.triggers.find(\n\t\t\t\t(t) => t.functionSlug === fn.slug && t.name === trigger.name,\n\t\t\t);\n\t\t\tif (!remoteTrigger) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"create-trigger\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\t\ttrigger,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tremoteTrigger.cron !== trigger.cron ||\n\t\t\t\tremoteTrigger.enabled !== trigger.enabled ||\n\t\t\t\tremoteTrigger.functionPath !== trigger.functionPath\n\t\t\t) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-trigger\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\t\ttriggerId: remoteTrigger.triggerId,\n\t\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\t\ttrigger,\n\t\t\t\t});\n\t\t\t}\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\tconst dataApiPolicy =\n\t\tconfig.dataApiPolicy ?? (config.dataApiEnabled ? \"enabled\" : \"omitted\");\n\tif (dataApiPolicy === \"enabled\") {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t} else if (dataApiPolicy === \"disabled\" && state.dataApiEnabled) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"disable-data-api\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\tdatabaseName: state.databaseName,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: remote.branch.name,\n\t\t\t\tfield: \"dataApi\",\n\t\t\t\tcurrent: true,\n\t\t\t\tdesired: false,\n\t\t\t\treason: \"Existing Data API would be deleted. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\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\tNeonCustomDomainSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionSnapshot,\n\tNeonTriggerSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedFunctionScheduleTrigger,\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\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: \"disable-data-api\";\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: \"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\t| {\n\t\t\tkind: \"create-trigger\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfunctionSlug: string;\n\t\t\ttrigger: ResolvedFunctionScheduleTrigger;\n\t }\n\t| {\n\t\t\tkind: \"update-trigger\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\ttriggerId: string;\n\t\t\tfunctionSlug: string;\n\t\t\ttrigger: ResolvedFunctionScheduleTrigger;\n\t }\n\t| {\n\t\t\tkind: \"register-custom-domain\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfunctionSlug: string;\n\t\t\tdomain: string;\n\t }\n\t| {\n\t\t\tkind: \"retarget-custom-domain\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfunctionSlug: string;\n\t\t\tpreviousSlug: string;\n\t\t\tdomain: string;\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\ttriggers: NeonTriggerSnapshot[];\n\t/** Absent on pre-feature snapshots; treated as none. */\n\tcustomDomains?: NeonCustomDomainSnapshot[];\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, options, plan, conflicts });\n\treturn { plan, conflicts };\n}\n\n/**\n * Data API teardown requires explicit `dataApi: false` so omission remains non-destructive.\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\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = 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\ttriggers: [],\n\t};\n\tconst customDomains = state.customDomains ?? [];\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\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\tfor (const trigger of fn.triggers ?? []) {\n\t\t\tconst remoteTrigger = state.triggers.find(\n\t\t\t\t(t) => t.functionSlug === fn.slug && t.name === trigger.name,\n\t\t\t);\n\t\t\tif (!remoteTrigger) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"create-trigger\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\t\ttrigger,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tremoteTrigger.cron !== trigger.cron ||\n\t\t\t\tremoteTrigger.enabled !== trigger.enabled ||\n\t\t\t\tremoteTrigger.functionPath !== trigger.functionPath\n\t\t\t) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-trigger\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\t\ttriggerId: remoteTrigger.triggerId,\n\t\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\t\ttrigger,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tdiffFunctionCustomDomains({\n\t\t\tfn,\n\t\t\tcustomDomains,\n\t\t\tremote,\n\t\t\toptions,\n\t\t\tplan,\n\t\t\tconflicts,\n\t\t});\n\t}\n}\n\nconst FUNCTION_CUSTOM_DOMAIN_ENTITY = \"function\";\n\nfunction diffFunctionCustomDomains(args: {\n\tfn: ResolvedFunctionConfig;\n\tcustomDomains: NeonCustomDomainSnapshot[];\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { fn, customDomains, remote, options, plan, conflicts } = args;\n\tfor (const domain of fn.customDomains ?? []) {\n\t\tconst existing = customDomains.find((d) => d.domain === domain);\n\t\tif (!existing) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"register-custom-domain\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\tdomain,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (existing.entityType !== FUNCTION_CUSTOM_DOMAIN_ENTITY) {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: remote.branch.name,\n\t\t\t\tfield: \"customDomain\",\n\t\t\t\tcurrent: {\n\t\t\t\t\tentityType: existing.entityType,\n\t\t\t\t\tentityId: existing.entityId,\n\t\t\t\t},\n\t\t\t\tdesired: {\n\t\t\t\t\tentityType: FUNCTION_CUSTOM_DOMAIN_ENTITY,\n\t\t\t\t\tentityId: fn.slug,\n\t\t\t\t},\n\t\t\t\treason: `custom domain \"${domain}\" is registered to entity type \"${existing.entityType}\", which neon.ts cannot retarget. Delete it with \\`neon function domains delete\\` or stop declaring it.`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (existing.entityId === fn.slug) continue;\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"retarget-custom-domain\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\tfunctionSlug: fn.slug,\n\t\t\t\tpreviousSlug: existing.entityId,\n\t\t\t\tdomain,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconflicts.push({\n\t\t\tkind: \"branch\",\n\t\t\tidentifier: remote.branch.name,\n\t\t\tfield: \"customDomain\",\n\t\t\tcurrent: existing.entityId,\n\t\t\tdesired: fn.slug,\n\t\t\treason: `custom domain \"${domain}\" is registered to function \"${existing.entityId}\". Pass \\`updateExisting: true\\` (SDK) or \\`--update-existing\\` (CLI) to retarget it to \"${fn.slug}\".`,\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\tconst dataApiPolicy =\n\t\tconfig.dataApiPolicy ?? (config.dataApiEnabled ? \"enabled\" : \"omitted\");\n\tif (dataApiPolicy === \"enabled\") {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t} else if (dataApiPolicy === \"disabled\" && state.dataApiEnabled) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"disable-data-api\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName: remote.branch.name,\n\t\t\t\tdatabaseName: state.databaseName,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: remote.branch.name,\n\t\t\t\tfield: \"dataApi\",\n\t\t\t\tcurrent: true,\n\t\t\t\tdesired: false,\n\t\t\t\treason: \"Existing Data API would be deleted. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\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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"}