@neondatabase/config 0.8.0 → 0.9.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
@@ -1,4 +1,4 @@
1
- # @neondatabase/config
1
+ # @neon/config
2
2
 
3
3
  Config-as-Code for the Neon Platform. A repo-local `neon.ts` exports a TypeScript policy function describing a branch's desired state. This package exposes **functions** to inspect, diff, and deploy that policy against the Neon API.
4
4
 
@@ -7,14 +7,16 @@ Config-as-Code for the Neon Platform. A repo-local `neon.ts` exports a TypeScrip
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @neondatabase/config
10
+ npm install @neon/config
11
11
  ```
12
12
 
13
+ > **Requirements:** Node.js >= 20.19.
14
+
13
15
  ## Define a policy
14
16
 
15
17
  ```ts
16
18
  // neon.ts
17
- import { defineConfig } from "@neondatabase/config/v1";
19
+ import { defineConfig } from "@neon/config/v1";
18
20
 
19
21
  export default defineConfig({
20
22
  // Static: what *exists* on every branch. GA service toggles drive the typed env.
@@ -36,7 +38,7 @@ export default defineConfig({
36
38
 
37
39
  A policy is split into a **static** existential set and a **dynamic** `branch` closure:
38
40
 
39
- - **Static top-level** — `auth` / `dataApi` (GA service toggles) and the beta `preview` block (`aiGateway`, `functions` keyed by slug, `buckets` keyed by name). Because this is static, the secret set is known at the type level, so `parseEnv` / `fetchEnv` from `@neondatabase/env` return an exact `NeonEnv`.
41
+ - **Static top-level** — `auth` / `dataApi` (GA service toggles) and the beta `preview` block (`aiGateway`, `functions` keyed by slug, `buckets` keyed by name). Because this is static, the secret set is known at the type level, so `parseEnv` / `fetchEnv` from `@neon/env` return an exact `NeonEnv`.
40
42
  - **`branch` closure** — receives a **read-only descriptor** (`BranchTarget`) of the branch being evaluated (`name`, `id`, `exists`, `isDefault`, `isProtected`, `parentId`, `expiresAt`) and returns per-branch *tuning*: `parent`, `ttl`, `protected`, `postgres.computeSettings`, and per-function `runtime`. Function memory is fixed at `2048` MiB for now and is not user-configurable. It runs both against existing branches and during pre-create evaluation (`exists: false`). It **cannot** change which services or functions exist — that is what keeps the static secret set sound.
41
43
 
42
44
  Service toggles accept `true` / `{}` / `{ enabled: true }` (enabled) and `false` / `{ enabled: false }` (disabled). Function slugs (record keys) must match `^[a-z0-9]{1,20}$`.
@@ -90,7 +92,7 @@ The three operations mirror the Terraform mental model: **`inspect`** (read live
90
92
 
91
93
  ```ts
92
94
  import config from "../neon";
93
- import { inspect, plan, apply } from "@neondatabase/config/v1";
95
+ import { inspect, plan, apply } from "@neon/config/v1";
94
96
 
95
97
  const target = { projectId: "patient-art-12345", branchId: "main" };
96
98
 
@@ -131,7 +133,7 @@ import {
131
133
  ErrorCode,
132
134
  errors,
133
135
  schemas,
134
- } from "@neondatabase/config/v1";
136
+ } from "@neon/config/v1";
135
137
  ```
136
138
 
137
139
  ## Safety Rules
@@ -143,4 +145,4 @@ import {
143
145
 
144
146
  ## Env vars
145
147
 
146
- Connection-string resolution/injection lives in the companion package [`@neondatabase/env`](../env), which depends on this package for the `Config` type and the Neon API client.
148
+ Connection-string resolution/injection lives in the companion package [`@neon/env`](../env), which depends on this package for the `Config` type and the Neon API client.
@@ -70,7 +70,7 @@ type PreviewAutocomplete<Preview> = (Preview extends {
70
70
  *
71
71
  * Used at the top of `neon.ts`:
72
72
  * ```ts
73
- * import { defineConfig } from "@neondatabase/config/v1";
73
+ * import { defineConfig } from "@neon/config/v1";
74
74
  *
75
75
  * export default defineConfig({
76
76
  * auth: true,
@@ -10,7 +10,7 @@ const REGION_PREFIX = /^(aws|azure|gcp)-/;
10
10
  *
11
11
  * Used at the top of `neon.ts`:
12
12
  * ```ts
13
- * import { defineConfig } from "@neondatabase/config/v1";
13
+ * import { defineConfig } from "@neon/config/v1";
14
14
  *
15
15
  * export default defineConfig({
16
16
  * auth: true,
@@ -1 +1 @@
1
- {"version":3,"file":"define-config.js","names":[],"sources":["../../src/lib/define-config.ts"],"sourcesContent":["import { parseBranchTtl } from \"./duration.js\";\nimport { ConfigValidationError } from \"./errors.js\";\nimport {\n\tbranchTuningSchema,\n\tconfigInputSchema,\n\tformatZodIssues,\n} from \"./schema.js\";\nimport type {\n\tBranchTarget,\n\tBranchTuning,\n\tBranchTuningFn,\n\tBucketDef,\n\tConfig,\n\tDataApiInput,\n\tDataApiSettings,\n\tFunctionDef,\n\tFunctionTuning,\n\tPreviewInput,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceEnabled,\n\tServiceToggleInput,\n} from \"./types.js\";\n\n/** Default deploy parameters applied to functions that omit them in `neon.ts`. */\nconst DEFAULT_FUNCTION_RUNTIME = \"nodejs24\" as const;\n\nconst REGION_PREFIX = /^(aws|azure|gcp)-/;\n\n/**\n * Whether a `dataApi` toggle is **enabled and verified by Neon Auth** at the type level: it is\n * on (see {@link ServiceEnabled}) and not the explicit `authProvider: \"external\"` variant\n * (so the default / `\"neon\"` provider). This is the case that requires top-level Neon Auth.\n */\ntype DataApiUsesNeonAuth<DataApi> = ServiceEnabled<DataApi> extends true\n\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t? false\n\t\t: true\n\t: false;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `dataApi` when a Neon-Auth Data\n * API is declared without Neon Auth enabled (see {@link DataApiField}). TypeScript prints the\n * offending value against this string literal — `Type 'true' is not assignable to type\n * '…requires `auth: true`…'` — which points straight at the fix, instead of the opaque\n * `Type 'true' is not assignable to type 'never'` an intersection guard produces.\n *\n * It documents **both** fixes: enabling Neon Auth (`auth: true`), and running the Data API\n * *without* Neon Auth by verifying a third-party IdP (`authProvider: 'external'` + `jwksUrl`).\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type NeonAuthRequiredHint =\n\t\"`dataApi` with Neon Auth (the default `authProvider: 'neon'`) requires Neon Auth, so add `auth: true`. To enable the Data API WITHOUT Neon Auth, verify a third-party IdP instead: `dataApi: { authProvider: 'external', jwksUrl: 'https://your-idp/.well-known/jwks.json' }`\";\n\n/**\n * Static cross-field guard for {@link defineConfig}, expressed as the **type of the `dataApi`\n * field** rather than an intersected requirement on `auth`.\n *\n * - A Neon-Auth Data API (`authProvider: \"neon\"`, the default) with top-level `auth` enabled,\n * or any external Data API: the field keeps its normal `DataApi & DataApiInput` type (the\n * `& DataApiInput` preserves member autocomplete; the `const DataApi` still types the\n * returned {@link Config}).\n * - A Neon-Auth Data API **without** `auth` enabled: the field's expected type collapses to\n * the {@link NeonAuthRequiredHint} message, so the author sees the rule (and the two fixes)\n * right on the `dataApi` value.\n *\n * The runtime `superRefine` in {@link configInputSchema} enforces the same invariant for\n * non-typed (plain-JS) callers, so the behavior is identical — only the type-level message\n * changes.\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type DataApiField<Auth, DataApi> =\n\tDataApiUsesNeonAuth<DataApi> extends true\n\t\t? ServiceEnabled<Auth> extends true\n\t\t\t? DataApi & DataApiInput\n\t\t\t: NeonAuthRequiredHint\n\t\t: DataApi & DataApiInput;\n\n/**\n * Autocomplete bridge for the nested `preview.functions` / `preview.buckets` slug objects.\n *\n * {@link PreviewInput} types those records with a string index signature\n * (`Record<string, FunctionDef>` / `Record<string, BucketDef>`). When `defineConfig` infers\n * `const Preview`, every authored slug becomes a **named** property on the inferred literal\n * (e.g. `{ hello: { name; source } }`), and a named property **shadows** the index signature\n * when the editor computes the contextual type of that slug's value — so the rest of\n * {@link FunctionDef} / {@link BucketDef} (`env`, `dev`, `access`, …) never surfaces as\n * completions inside `hello: { … }` / `uploads: { … }`.\n *\n * Re-declaring each inferred slug's value as `FunctionDef` / `BucketDef` (a *named* member, via\n * a mapped type over the already-inferred keys) puts those members back onto the contextual\n * type without going through an index signature, which restores autocomplete. Intersected with\n * `Preview & PreviewInput` it neither widens what is accepted (the values were already\n * `FunctionDef` / `BucketDef`) nor perturbs the inferred `const Preview` — so slug inference for\n * `BranchTuningFn<Preview>` and the returned {@link Config} is unchanged.\n */\ntype PreviewAutocomplete<Preview> = (Preview extends { functions: infer F }\n\t? { functions: { [Slug in keyof F]: FunctionDef } }\n\t: unknown) &\n\t(Preview extends { buckets: infer B }\n\t\t? { buckets: { [Name in keyof B]: BucketDef } }\n\t\t: unknown);\n\n/**\n * Validate and freeze a Neon Platform branch policy.\n *\n * Used at the top of `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neondatabase/config/v1\";\n *\n * export default defineConfig({\n * auth: true,\n * preview: {\n * functions: {\n * hello: { name: \"Hello\", source: \"./functions/hello.ts\", dev: { port: 8787 } },\n * },\n * },\n * branch: (branch) => ({ protected: branch.name === \"main\" }),\n * });\n * ```\n *\n * The policy is split into a **static** existential set (top-level `auth` / `dataApi`\n * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The\n * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`\n * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function\n * deploy settings), never change what exists.\n *\n * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch\n * being decided for (not a live handle); switch on its facts (`branch.name`,\n * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two\n * modes: against an existing branch (fields populated from Neon) and during pre-create\n * evaluation (`exists: false`, `id` undefined).\n *\n * Pure: no I/O, no side effects. The static parts are validated here; the closure's output\n * is validated every time it is evaluated so errors point at the concrete branch target.\n */\nexport function defineConfig<\n\tconst Auth extends ServiceToggleInput | undefined = undefined,\n\tconst DataApi extends DataApiInput | undefined = undefined,\n\tconst Preview extends PreviewInput | undefined = undefined,\n>(input: {\n\t// Each field is intersected with its concrete interface (not just typed as the bare\n\t// generic). The generic alone — e.g. `preview?: Preview` — gives editors no members to\n\t// complete against in the object-literal position (they see `{} | undefined`), so you\n\t// lose hints for `aiGateway` / `functions` / `buckets`. `& PreviewInput` restores the\n\t// full shape for autocomplete while still inferring the `const` literal that types the\n\t// `branch` closure's slugs (BranchTuningFn<Preview>) and the returned Config.\n\tauth?: Auth & ServiceToggleInput;\n\t// The `dataApi` field carries the Neon-Auth cross-field guard at the type level (see\n\t// `DataApiField`): a Neon-Auth Data API without `auth` enabled surfaces a readable hint\n\t// as the field's expected type instead of collapsing the value to `never`.\n\tdataApi?: DataApiField<Auth, DataApi>;\n\t// `& PreviewInput` restores top-level member hints (aiGateway/functions/buckets);\n\t// `& PreviewAutocomplete<Preview>` restores hints *inside* each function/bucket slug\n\t// object (see `PreviewAutocomplete`), which the bare index signature otherwise hides.\n\tpreview?: Preview & PreviewInput & PreviewAutocomplete<Preview>;\n\tbranch?: BranchTuningFn<Preview>;\n}): Config<Auth, DataApi, Preview> {\n\tif (typeof input === \"function\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig now expects an object, not a function: `export default defineConfig({ auth: true, preview: { … }, branch: (branch) => ({ … }) })`.\",\n\t\t\t\"The static services/preview set moved to the top level; per-branch logic moved into the `branch` closure.\",\n\t\t]);\n\t}\n\tif (input === null || typeof input !== \"object\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig expects a configuration object: `export default defineConfig({ … })`.\",\n\t\t]);\n\t}\n\n\tconst parsed = configInputSchema.safeParse(input);\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\n\treturn Object.freeze({ ...input }) as Config<Auth, DataApi, Preview>;\n}\n\n/**\n * Evaluate a branch policy for a specific branch target and return a normalized config.\n *\n * Merges the static existential set (services + preview functions/buckets) with the\n * per-branch tuning returned by the `branch` closure into the same {@link\n * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.\n */\nexport function resolveConfig(\n\tconfig: Config,\n\tbranch: BranchTarget,\n): ResolvedBranchConfig {\n\tconst tuning = evaluateBranchTuning(config.branch, branch);\n\n\tconst resolved: ResolvedBranchConfig = {\n\t\tauthEnabled: isServiceEnabled(config.auth),\n\t\tdataApiEnabled: isDataApiEnabled(config.dataApi),\n\t};\n\tconst dataApi = resolveDataApi(config.dataApi);\n\tif (dataApi) resolved.dataApi = dataApi;\n\tif (tuning.parent !== undefined) resolved.parent = tuning.parent;\n\tif (tuning.ttl !== undefined) {\n\t\t// `branchTuningSchema` already validated `ttl` with the same `parseBranchTtl`, so\n\t\t// this only converts the validated value to seconds — it cannot fail here.\n\t\tconst parsedTtl = parseBranchTtl(tuning.ttl);\n\t\tif (!(\"error\" in parsedTtl)) resolved.ttlSeconds = parsedTtl.seconds;\n\t}\n\tif (tuning.protected !== undefined) resolved.protected = tuning.protected;\n\tif (tuning.postgres?.computeSettings) {\n\t\tresolved.postgres = {\n\t\t\tcomputeSettings: { ...tuning.postgres.computeSettings },\n\t\t};\n\t}\n\n\tconst preview = resolvePreviewConfig(config.preview, tuning);\n\tif (preview) resolved.preview = preview;\n\n\treturn resolved;\n}\n\n/**\n * Run the `branch` closure (when present) for the target and validate its output. The\n * closure is optional — a fully static policy resolves with empty tuning.\n */\nfunction evaluateBranchTuning(\n\tbranchFn: BranchTuningFn | undefined,\n\ttarget: BranchTarget,\n): BranchTuning {\n\tif (!branchFn) return {};\n\tlet raw: unknown;\n\ttry {\n\t\traw = branchFn(Object.freeze({ ...target }));\n\t} catch (cause) {\n\t\tthrow new ConfigValidationError([\n\t\t\t`Branch policy threw while evaluating branch \"${target.name}\".`,\n\t\t\t(cause as Error)?.message ?? String(cause),\n\t\t]);\n\t}\n\tconst parsed = branchTuningSchema.safeParse(raw ?? {});\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\treturn parsed.data as BranchTuning;\n}\n\nfunction isServiceEnabled(toggle: ServiceToggleInput | undefined): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/** Whether a {@link DataApiInput} is enabled (present object/`true` unless `enabled: false`). */\nfunction isDataApiEnabled(input: DataApiInput | undefined): boolean {\n\tif (input === undefined) return false;\n\tif (typeof input === \"boolean\") return input;\n\treturn input.enabled !== false;\n}\n\n/**\n * Normalize a {@link DataApiInput} into a {@link ResolvedDataApiConfig}, or `undefined` when\n * the Data API is not enabled. `authProvider` defaults to `\"neon\"`; the external-IdP wiring\n * is carried through only for the `\"external\"` provider; `settings` is copied with its\n * `undefined` entries dropped so diffing only considers fields the policy actually set.\n */\nfunction resolveDataApi(\n\tinput: DataApiInput | undefined,\n): ResolvedDataApiConfig | undefined {\n\tif (!isDataApiEnabled(input)) return undefined;\n\tif (typeof input !== \"object\") {\n\t\t// Bare `true`: enabled with Neon Auth and all-default settings.\n\t\treturn { authProvider: \"neon\" };\n\t}\n\tconst authProvider = input.authProvider ?? \"neon\";\n\tconst resolved: ResolvedDataApiConfig = { authProvider };\n\tif (authProvider === \"external\") {\n\t\tif (input.jwksUrl !== undefined) resolved.jwksUrl = input.jwksUrl;\n\t\tif (input.providerName !== undefined)\n\t\t\tresolved.providerName = input.providerName;\n\t\tif (input.jwtAudience !== undefined)\n\t\t\tresolved.jwtAudience = input.jwtAudience;\n\t}\n\tconst settings = normalizeDataApiSettings(input.settings);\n\tif (settings) resolved.settings = settings;\n\treturn resolved;\n}\n\n/** Copy a {@link DataApiSettings}, dropping `undefined` entries; `undefined` when empty. */\nfunction normalizeDataApiSettings(\n\tsettings: DataApiSettings | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\t(out as Record<string, unknown>)[key] = value;\n\t\t}\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Normalize the static {@link PreviewInput} (merged with per-branch function tuning) into a\n * {@link ResolvedPreviewConfig}. Returns `undefined` when the policy declares no `preview`\n * block so the field can be omitted entirely. Function slugs / bucket names come from the\n * record keys.\n */\nfunction resolvePreviewConfig(\n\tpreview: PreviewInput | undefined,\n\ttuning: BranchTuning,\n): ResolvedPreviewConfig | undefined {\n\tif (!preview) return undefined;\n\tconst fnTuning = tuning.preview?.functions ?? {};\n\tconst functions: ResolvedFunctionConfig[] = Object.entries(\n\t\tpreview.functions ?? {},\n\t).map(([slug, def]) =>\n\t\tresolveFunctionConfig(slug, def, fnTuning[slug] ?? {}),\n\t);\n\tconst buckets = Object.entries(preview.buckets ?? {}).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\taccess: def.access ?? \"private\",\n\t\t}),\n\t);\n\treturn {\n\t\tfunctions,\n\t\tbuckets,\n\t\taiGatewayEnabled: isServiceEnabled(preview.aiGateway),\n\t};\n}\n\nfunction resolveFunctionConfig(\n\tslug: string,\n\tdef: FunctionDef,\n\ttuning: FunctionTuning,\n): ResolvedFunctionConfig {\n\treturn {\n\t\tslug,\n\t\tname: def.name,\n\t\tsource: def.source,\n\t\tenv: { ...(def.env ?? {}) },\n\t\truntime: tuning.runtime ?? DEFAULT_FUNCTION_RUNTIME,\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t};\n}\n\n/**\n * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes\n * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the\n * NeonApi adapter.\n */\nexport function normalizeRegion(region: string): string {\n\tif (REGION_PREFIX.test(region)) return region;\n\treturn `aws-${region}`;\n}\n"],"mappings":";;;;;AA2BA,MAAM,2BAA2B;AAEjC,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+GtB,SAAgB,aAId,OAiBiC;CAClC,IAAI,OAAO,UAAU,YACpB,MAAM,IAAI,sBAAsB,CAC/B,mJACA,2GACD,CAAC;CAEF,IAAI,UAAU,QAAQ,OAAO,UAAU,UACtC,MAAM,IAAI,sBAAsB,CAC/B,oFACD,CAAC;CAGF,MAAM,SAAS,kBAAkB,UAAU,KAAK;CAChD,IAAI,CAAC,OAAO,SACX,MAAM,IAAI,sBAAsB,gBAAgB,OAAO,KAAK,CAAC;CAG9D,OAAO,OAAO,OAAO,EAAE,GAAG,MAAM,CAAC;AAClC;;;;;;;;AASA,SAAgB,cACf,QACA,QACuB;CACvB,MAAM,SAAS,qBAAqB,OAAO,QAAQ,MAAM;CAEzD,MAAM,WAAiC;EACtC,aAAa,iBAAiB,OAAO,IAAI;EACzC,gBAAgB,iBAAiB,OAAO,OAAO;CAChD;CACA,MAAM,UAAU,eAAe,OAAO,OAAO;CAC7C,IAAI,SAAS,SAAS,UAAU;CAChC,IAAI,OAAO,WAAW,KAAA,GAAW,SAAS,SAAS,OAAO;CAC1D,IAAI,OAAO,QAAQ,KAAA,GAAW;EAG7B,MAAM,YAAY,eAAe,OAAO,GAAG;EAC3C,IAAI,EAAE,WAAW,YAAY,SAAS,aAAa,UAAU;CAC9D;CACA,IAAI,OAAO,cAAc,KAAA,GAAW,SAAS,YAAY,OAAO;CAChE,IAAI,OAAO,UAAU,iBACpB,SAAS,WAAW,EACnB,iBAAiB,EAAE,GAAG,OAAO,SAAS,gBAAgB,EACvD;CAGD,MAAM,UAAU,qBAAqB,OAAO,SAAS,MAAM;CAC3D,IAAI,SAAS,SAAS,UAAU;CAEhC,OAAO;AACR;;;;;AAMA,SAAS,qBACR,UACA,QACe;CACf,IAAI,CAAC,UAAU,OAAO,CAAC;CACvB,IAAI;CACJ,IAAI;EACH,MAAM,SAAS,OAAO,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC;CAC5C,SAAS,OAAO;EACf,MAAM,IAAI,sBAAsB,CAC/B,gDAAgD,OAAO,KAAK,KAC3D,OAAiB,WAAW,OAAO,KAAK,CAC1C,CAAC;CACF;CACA,MAAM,SAAS,mBAAmB,UAAU,OAAO,CAAC,CAAC;CACrD,IAAI,CAAC,OAAO,SACX,MAAM,IAAI,sBAAsB,gBAAgB,OAAO,KAAK,CAAC;CAE9D,OAAO,OAAO;AACf;AAEA,SAAS,iBAAiB,QAAiD;CAC1E,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,IAAI,OAAO,WAAW,WAAW,OAAO;CACxC,OAAO,OAAO,YAAY;AAC3B;;AAGA,SAAS,iBAAiB,OAA0C;CACnE,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,OAAO,UAAU,WAAW,OAAO;CACvC,OAAO,MAAM,YAAY;AAC1B;;;;;;;AAQA,SAAS,eACR,OACoC;CACpC,IAAI,CAAC,iBAAiB,KAAK,GAAG,OAAO,KAAA;CACrC,IAAI,OAAO,UAAU,UAEpB,OAAO,EAAE,cAAc,OAAO;CAE/B,MAAM,eAAe,MAAM,gBAAgB;CAC3C,MAAM,WAAkC,EAAE,aAAa;CACvD,IAAI,iBAAiB,YAAY;EAChC,IAAI,MAAM,YAAY,KAAA,GAAW,SAAS,UAAU,MAAM;EAC1D,IAAI,MAAM,iBAAiB,KAAA,GAC1B,SAAS,eAAe,MAAM;EAC/B,IAAI,MAAM,gBAAgB,KAAA,GACzB,SAAS,cAAc,MAAM;CAC/B;CACA,MAAM,WAAW,yBAAyB,MAAM,QAAQ;CACxD,IAAI,UAAU,SAAS,WAAW;CAClC,OAAO;AACR;;AAGA,SAAS,yBACR,UAC8B;CAC9B,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,MAAM,MAAuB,CAAC;CAC9B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GACjD,IAAI,UAAU,KAAA,GACb,IAAiC,OAAO;CAG1C,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,MAAM,KAAA;AAC5C;;;;;;;AAQA,SAAS,qBACR,SACA,QACoC;CACpC,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,MAAM,WAAW,OAAO,SAAS,aAAa,CAAC;CAY/C,OAAO;EACN,WAZ2C,OAAO,QAClD,QAAQ,aAAa,CAAC,CACvB,CAAC,CAAC,KAAK,CAAC,MAAM,SACb,sBAAsB,MAAM,KAAK,SAAS,SAAS,CAAC,CAAC,CAS7C;EACR,SARe,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,KACpD,CAAC,MAAM,UAAU;GACjB;GACA,QAAQ,IAAI,UAAU;EACvB,EAIM;EACN,kBAAkB,iBAAiB,QAAQ,SAAS;CACrD;AACD;AAEA,SAAS,sBACR,MACA,KACA,QACyB;CACzB,OAAO;EACN;EACA,MAAM,IAAI;EACV,QAAQ,IAAI;EACZ,KAAK,EAAE,GAAI,IAAI,OAAO,CAAC,EAAG;EAC1B,SAAS,OAAO,WAAW;EAE3B,GAAI,IAAI,MAAM,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;CACnC;AACD;;;;;;AAOA,SAAgB,gBAAgB,QAAwB;CACvD,IAAI,cAAc,KAAK,MAAM,GAAG,OAAO;CACvC,OAAO,OAAO;AACf"}
1
+ {"version":3,"file":"define-config.js","names":[],"sources":["../../src/lib/define-config.ts"],"sourcesContent":["import { parseBranchTtl } from \"./duration.js\";\nimport { ConfigValidationError } from \"./errors.js\";\nimport {\n\tbranchTuningSchema,\n\tconfigInputSchema,\n\tformatZodIssues,\n} from \"./schema.js\";\nimport type {\n\tBranchTarget,\n\tBranchTuning,\n\tBranchTuningFn,\n\tBucketDef,\n\tConfig,\n\tDataApiInput,\n\tDataApiSettings,\n\tFunctionDef,\n\tFunctionTuning,\n\tPreviewInput,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceEnabled,\n\tServiceToggleInput,\n} from \"./types.js\";\n\n/** Default deploy parameters applied to functions that omit them in `neon.ts`. */\nconst DEFAULT_FUNCTION_RUNTIME = \"nodejs24\" as const;\n\nconst REGION_PREFIX = /^(aws|azure|gcp)-/;\n\n/**\n * Whether a `dataApi` toggle is **enabled and verified by Neon Auth** at the type level: it is\n * on (see {@link ServiceEnabled}) and not the explicit `authProvider: \"external\"` variant\n * (so the default / `\"neon\"` provider). This is the case that requires top-level Neon Auth.\n */\ntype DataApiUsesNeonAuth<DataApi> = ServiceEnabled<DataApi> extends true\n\t? [DataApi] extends [{ authProvider: \"external\" }]\n\t\t? false\n\t\t: true\n\t: false;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `dataApi` when a Neon-Auth Data\n * API is declared without Neon Auth enabled (see {@link DataApiField}). TypeScript prints the\n * offending value against this string literal — `Type 'true' is not assignable to type\n * '…requires `auth: true`…'` — which points straight at the fix, instead of the opaque\n * `Type 'true' is not assignable to type 'never'` an intersection guard produces.\n *\n * It documents **both** fixes: enabling Neon Auth (`auth: true`), and running the Data API\n * *without* Neon Auth by verifying a third-party IdP (`authProvider: 'external'` + `jwksUrl`).\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type NeonAuthRequiredHint =\n\t\"`dataApi` with Neon Auth (the default `authProvider: 'neon'`) requires Neon Auth, so add `auth: true`. To enable the Data API WITHOUT Neon Auth, verify a third-party IdP instead: `dataApi: { authProvider: 'external', jwksUrl: 'https://your-idp/.well-known/jwks.json' }`\";\n\n/**\n * Static cross-field guard for {@link defineConfig}, expressed as the **type of the `dataApi`\n * field** rather than an intersected requirement on `auth`.\n *\n * - A Neon-Auth Data API (`authProvider: \"neon\"`, the default) with top-level `auth` enabled,\n * or any external Data API: the field keeps its normal `DataApi & DataApiInput` type (the\n * `& DataApiInput` preserves member autocomplete; the `const DataApi` still types the\n * returned {@link Config}).\n * - A Neon-Auth Data API **without** `auth` enabled: the field's expected type collapses to\n * the {@link NeonAuthRequiredHint} message, so the author sees the rule (and the two fixes)\n * right on the `dataApi` value.\n *\n * The runtime `superRefine` in {@link configInputSchema} enforces the same invariant for\n * non-typed (plain-JS) callers, so the behavior is identical — only the type-level message\n * changes.\n */\n// Exported (type-only) for the type tests in `define-config.test-d.ts`; intentionally not\n// re-exported from `v1.ts` / `index.ts`, so it stays an internal implementation detail.\nexport type DataApiField<Auth, DataApi> =\n\tDataApiUsesNeonAuth<DataApi> extends true\n\t\t? ServiceEnabled<Auth> extends true\n\t\t\t? DataApi & DataApiInput\n\t\t\t: NeonAuthRequiredHint\n\t\t: DataApi & DataApiInput;\n\n/**\n * Autocomplete bridge for the nested `preview.functions` / `preview.buckets` slug objects.\n *\n * {@link PreviewInput} types those records with a string index signature\n * (`Record<string, FunctionDef>` / `Record<string, BucketDef>`). When `defineConfig` infers\n * `const Preview`, every authored slug becomes a **named** property on the inferred literal\n * (e.g. `{ hello: { name; source } }`), and a named property **shadows** the index signature\n * when the editor computes the contextual type of that slug's value — so the rest of\n * {@link FunctionDef} / {@link BucketDef} (`env`, `dev`, `access`, …) never surfaces as\n * completions inside `hello: { … }` / `uploads: { … }`.\n *\n * Re-declaring each inferred slug's value as `FunctionDef` / `BucketDef` (a *named* member, via\n * a mapped type over the already-inferred keys) puts those members back onto the contextual\n * type without going through an index signature, which restores autocomplete. Intersected with\n * `Preview & PreviewInput` it neither widens what is accepted (the values were already\n * `FunctionDef` / `BucketDef`) nor perturbs the inferred `const Preview` — so slug inference for\n * `BranchTuningFn<Preview>` and the returned {@link Config} is unchanged.\n */\ntype PreviewAutocomplete<Preview> = (Preview extends { functions: infer F }\n\t? { functions: { [Slug in keyof F]: FunctionDef } }\n\t: unknown) &\n\t(Preview extends { buckets: infer B }\n\t\t? { buckets: { [Name in keyof B]: BucketDef } }\n\t\t: unknown);\n\n/**\n * Validate and freeze a Neon Platform branch policy.\n *\n * Used at the top of `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neon/config/v1\";\n *\n * export default defineConfig({\n * auth: true,\n * preview: {\n * functions: {\n * hello: { name: \"Hello\", source: \"./functions/hello.ts\", dev: { port: 8787 } },\n * },\n * },\n * branch: (branch) => ({ protected: branch.name === \"main\" }),\n * });\n * ```\n *\n * The policy is split into a **static** existential set (top-level `auth` / `dataApi`\n * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The\n * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`\n * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function\n * deploy settings), never change what exists.\n *\n * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch\n * being decided for (not a live handle); switch on its facts (`branch.name`,\n * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two\n * modes: against an existing branch (fields populated from Neon) and during pre-create\n * evaluation (`exists: false`, `id` undefined).\n *\n * Pure: no I/O, no side effects. The static parts are validated here; the closure's output\n * is validated every time it is evaluated so errors point at the concrete branch target.\n */\nexport function defineConfig<\n\tconst Auth extends ServiceToggleInput | undefined = undefined,\n\tconst DataApi extends DataApiInput | undefined = undefined,\n\tconst Preview extends PreviewInput | undefined = undefined,\n>(input: {\n\t// Each field is intersected with its concrete interface (not just typed as the bare\n\t// generic). The generic alone — e.g. `preview?: Preview` — gives editors no members to\n\t// complete against in the object-literal position (they see `{} | undefined`), so you\n\t// lose hints for `aiGateway` / `functions` / `buckets`. `& PreviewInput` restores the\n\t// full shape for autocomplete while still inferring the `const` literal that types the\n\t// `branch` closure's slugs (BranchTuningFn<Preview>) and the returned Config.\n\tauth?: Auth & ServiceToggleInput;\n\t// The `dataApi` field carries the Neon-Auth cross-field guard at the type level (see\n\t// `DataApiField`): a Neon-Auth Data API without `auth` enabled surfaces a readable hint\n\t// as the field's expected type instead of collapsing the value to `never`.\n\tdataApi?: DataApiField<Auth, DataApi>;\n\t// `& PreviewInput` restores top-level member hints (aiGateway/functions/buckets);\n\t// `& PreviewAutocomplete<Preview>` restores hints *inside* each function/bucket slug\n\t// object (see `PreviewAutocomplete`), which the bare index signature otherwise hides.\n\tpreview?: Preview & PreviewInput & PreviewAutocomplete<Preview>;\n\tbranch?: BranchTuningFn<Preview>;\n}): Config<Auth, DataApi, Preview> {\n\tif (typeof input === \"function\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig now expects an object, not a function: `export default defineConfig({ auth: true, preview: { … }, branch: (branch) => ({ … }) })`.\",\n\t\t\t\"The static services/preview set moved to the top level; per-branch logic moved into the `branch` closure.\",\n\t\t]);\n\t}\n\tif (input === null || typeof input !== \"object\") {\n\t\tthrow new ConfigValidationError([\n\t\t\t\"defineConfig expects a configuration object: `export default defineConfig({ … })`.\",\n\t\t]);\n\t}\n\n\tconst parsed = configInputSchema.safeParse(input);\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\n\treturn Object.freeze({ ...input }) as Config<Auth, DataApi, Preview>;\n}\n\n/**\n * Evaluate a branch policy for a specific branch target and return a normalized config.\n *\n * Merges the static existential set (services + preview functions/buckets) with the\n * per-branch tuning returned by the `branch` closure into the same {@link\n * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.\n */\nexport function resolveConfig(\n\tconfig: Config,\n\tbranch: BranchTarget,\n): ResolvedBranchConfig {\n\tconst tuning = evaluateBranchTuning(config.branch, branch);\n\n\tconst resolved: ResolvedBranchConfig = {\n\t\tauthEnabled: isServiceEnabled(config.auth),\n\t\tdataApiEnabled: isDataApiEnabled(config.dataApi),\n\t};\n\tconst dataApi = resolveDataApi(config.dataApi);\n\tif (dataApi) resolved.dataApi = dataApi;\n\tif (tuning.parent !== undefined) resolved.parent = tuning.parent;\n\tif (tuning.ttl !== undefined) {\n\t\t// `branchTuningSchema` already validated `ttl` with the same `parseBranchTtl`, so\n\t\t// this only converts the validated value to seconds — it cannot fail here.\n\t\tconst parsedTtl = parseBranchTtl(tuning.ttl);\n\t\tif (!(\"error\" in parsedTtl)) resolved.ttlSeconds = parsedTtl.seconds;\n\t}\n\tif (tuning.protected !== undefined) resolved.protected = tuning.protected;\n\tif (tuning.postgres?.computeSettings) {\n\t\tresolved.postgres = {\n\t\t\tcomputeSettings: { ...tuning.postgres.computeSettings },\n\t\t};\n\t}\n\n\tconst preview = resolvePreviewConfig(config.preview, tuning);\n\tif (preview) resolved.preview = preview;\n\n\treturn resolved;\n}\n\n/**\n * Run the `branch` closure (when present) for the target and validate its output. The\n * closure is optional — a fully static policy resolves with empty tuning.\n */\nfunction evaluateBranchTuning(\n\tbranchFn: BranchTuningFn | undefined,\n\ttarget: BranchTarget,\n): BranchTuning {\n\tif (!branchFn) return {};\n\tlet raw: unknown;\n\ttry {\n\t\traw = branchFn(Object.freeze({ ...target }));\n\t} catch (cause) {\n\t\tthrow new ConfigValidationError([\n\t\t\t`Branch policy threw while evaluating branch \"${target.name}\".`,\n\t\t\t(cause as Error)?.message ?? String(cause),\n\t\t]);\n\t}\n\tconst parsed = branchTuningSchema.safeParse(raw ?? {});\n\tif (!parsed.success) {\n\t\tthrow new ConfigValidationError(formatZodIssues(parsed.error));\n\t}\n\treturn parsed.data as BranchTuning;\n}\n\nfunction isServiceEnabled(toggle: ServiceToggleInput | undefined): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/** Whether a {@link DataApiInput} is enabled (present object/`true` unless `enabled: false`). */\nfunction isDataApiEnabled(input: DataApiInput | undefined): boolean {\n\tif (input === undefined) return false;\n\tif (typeof input === \"boolean\") return input;\n\treturn input.enabled !== false;\n}\n\n/**\n * Normalize a {@link DataApiInput} into a {@link ResolvedDataApiConfig}, or `undefined` when\n * the Data API is not enabled. `authProvider` defaults to `\"neon\"`; the external-IdP wiring\n * is carried through only for the `\"external\"` provider; `settings` is copied with its\n * `undefined` entries dropped so diffing only considers fields the policy actually set.\n */\nfunction resolveDataApi(\n\tinput: DataApiInput | undefined,\n): ResolvedDataApiConfig | undefined {\n\tif (!isDataApiEnabled(input)) return undefined;\n\tif (typeof input !== \"object\") {\n\t\t// Bare `true`: enabled with Neon Auth and all-default settings.\n\t\treturn { authProvider: \"neon\" };\n\t}\n\tconst authProvider = input.authProvider ?? \"neon\";\n\tconst resolved: ResolvedDataApiConfig = { authProvider };\n\tif (authProvider === \"external\") {\n\t\tif (input.jwksUrl !== undefined) resolved.jwksUrl = input.jwksUrl;\n\t\tif (input.providerName !== undefined)\n\t\t\tresolved.providerName = input.providerName;\n\t\tif (input.jwtAudience !== undefined)\n\t\t\tresolved.jwtAudience = input.jwtAudience;\n\t}\n\tconst settings = normalizeDataApiSettings(input.settings);\n\tif (settings) resolved.settings = settings;\n\treturn resolved;\n}\n\n/** Copy a {@link DataApiSettings}, dropping `undefined` entries; `undefined` when empty. */\nfunction normalizeDataApiSettings(\n\tsettings: DataApiSettings | undefined,\n): DataApiSettings | undefined {\n\tif (!settings) return undefined;\n\tconst out: DataApiSettings = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\t(out as Record<string, unknown>)[key] = value;\n\t\t}\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Normalize the static {@link PreviewInput} (merged with per-branch function tuning) into a\n * {@link ResolvedPreviewConfig}. Returns `undefined` when the policy declares no `preview`\n * block so the field can be omitted entirely. Function slugs / bucket names come from the\n * record keys.\n */\nfunction resolvePreviewConfig(\n\tpreview: PreviewInput | undefined,\n\ttuning: BranchTuning,\n): ResolvedPreviewConfig | undefined {\n\tif (!preview) return undefined;\n\tconst fnTuning = tuning.preview?.functions ?? {};\n\tconst functions: ResolvedFunctionConfig[] = Object.entries(\n\t\tpreview.functions ?? {},\n\t).map(([slug, def]) =>\n\t\tresolveFunctionConfig(slug, def, fnTuning[slug] ?? {}),\n\t);\n\tconst buckets = Object.entries(preview.buckets ?? {}).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\taccess: def.access ?? \"private\",\n\t\t}),\n\t);\n\treturn {\n\t\tfunctions,\n\t\tbuckets,\n\t\taiGatewayEnabled: isServiceEnabled(preview.aiGateway),\n\t};\n}\n\nfunction resolveFunctionConfig(\n\tslug: string,\n\tdef: FunctionDef,\n\ttuning: FunctionTuning,\n): ResolvedFunctionConfig {\n\treturn {\n\t\tslug,\n\t\tname: def.name,\n\t\tsource: def.source,\n\t\tenv: { ...(def.env ?? {}) },\n\t\truntime: tuning.runtime ?? DEFAULT_FUNCTION_RUNTIME,\n\t\t// Passed through untouched (no defaults); only `neon dev` reads it.\n\t\t...(def.dev ? { dev: def.dev } : {}),\n\t};\n}\n\n/**\n * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes\n * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the\n * NeonApi adapter.\n */\nexport function normalizeRegion(region: string): string {\n\tif (REGION_PREFIX.test(region)) return region;\n\treturn `aws-${region}`;\n}\n"],"mappings":"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"}
package/dist/lib/diff.js CHANGED
@@ -38,7 +38,7 @@ function diffConfig(config, remote, options) {
38
38
  * The AI Gateway is intentionally NOT planned here: it is always available on a branch
39
39
  * (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan
40
40
  * step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway
41
- * env vars (`@neondatabase/env`). There is nothing to create, and nothing to probe.
41
+ * env vars (`@neon/env`). There is nothing to create, and nothing to probe.
42
42
  *
43
43
  * Functions are always (re-)deployed: deployments are versioned and the newest becomes
44
44
  * active, so each push ships the current source. There is no separate create step — Neon
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","names":[],"sources":["../../src/lib/diff.ts"],"sourcesContent":["import type {\n\tEnableDataApiInput,\n\tNeonBranchSnapshot,\n\tNeonBucketSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n} from \"./types.js\";\n\n/**\n * A planned action to perform a single mutation against the Neon API. The diff engine\n * produces a list of these for `pushConfig` to execute (or report).\n */\nexport type PlanStep =\n\t| {\n\t\t\tkind: \"update-branch-ttl\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\texpiresAt: string | null;\n\t }\n\t| {\n\t\t\tkind: \"update-branch-protected\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tprotected: boolean;\n\t }\n\t| {\n\t\t\tkind: \"update-endpoint\";\n\t\t\tprojectId: string;\n\t\t\tbranchName: string;\n\t\t\tendpointId: string;\n\t\t\tsettings: ComputeSettings;\n\t }\n\t| {\n\t\t\tkind: \"enable-auth\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName?: string;\n\t }\n\t| {\n\t\t\tkind: \"enable-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\t/** Create-time auth wiring + initial settings from the policy. */\n\t\t\tinput?: EnableDataApiInput;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Reconcile the runtime settings of an already-enabled Data API integration.\n\t\t\t * Only `settings` are mutable post-create, so this is the lone Data API\n\t\t\t * *update* step — and it is an override (requires `updateExisting`).\n\t\t\t */\n\t\t\tkind: \"update-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\tsettings: DataApiSettings;\n\t }\n\t| {\n\t\t\tkind: \"create-bucket\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tbucketName: string;\n\t\t\taccessLevel: BucketAccessLevel;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Deploy code to a function. Planned for every desired function — deployments are\n\t\t\t * versioned and the newest becomes active, so a push ships the current source each\n\t\t\t * time. Neon has no separate \"create function\" endpoint: the first deployment to a\n\t\t\t * slug creates the function. `functionExists` therefore only drives whether this\n\t\t\t * surfaces as a `create` (first deploy) or an `update` (re-deploy).\n\t\t\t */\n\t\t\tkind: \"deploy-function\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfn: ResolvedFunctionConfig;\n\t\t\t/** Whether the function already existed remotely when the plan was computed. */\n\t\t\tfunctionExists: boolean;\n\t };\n\nexport interface RemoteServiceState {\n\tdatabaseName: string;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Current Data API runtime settings, when the integration is enabled and the API reports\n\t * them (SubZero only). `null`/absent means \"not reported\" — settings drift can't be\n\t * computed, so no update step is planned.\n\t */\n\tdataApiSettings?: DataApiSettings | null;\n}\n\n/**\n * Snapshot of the branch's current Preview-feature state. Absent (`undefined`) when the\n * policy has no `preview` block — `pushConfig` only fetches this when needed.\n */\nexport interface RemotePreviewState {\n\tbuckets: NeonBucketSnapshot[];\n\tfunctions: NeonFunctionSnapshot[];\n}\n\nexport interface RemoteState {\n\tprojectId: string;\n\tbranch: NeonBranchSnapshot;\n\tendpoint?: NeonEndpointSnapshot;\n\tservices: RemoteServiceState;\n\tpreview?: RemotePreviewState;\n}\n\nexport interface DiffOptions {\n\t/**\n\t * Apply mutable drift on the selected branch as plan steps instead of conflicts.\n\t * Default: `false`.\n\t */\n\tupdateExisting: boolean;\n}\n\nexport interface DiffResult {\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\n/**\n * Diff desired branch policy against the selected remote branch. Pure function.\n */\nexport function diffConfig(\n\tconfig: ResolvedBranchConfig,\n\tremote: RemoteState,\n\toptions: DiffOptions,\n): DiffResult {\n\tconst conflicts: ConflictReport[] = [];\n\tconst plan: PlanStep[] = [];\n\tdiffBranchConfig({ config, remote, options, plan, conflicts });\n\tdiffServices({ config, remote, options, plan, conflicts });\n\tdiffPreview({ config, remote, plan });\n\treturn { plan, conflicts };\n}\n\n/**\n * Plan Preview features (functions, buckets). Like {@link diffServices}, this is\n * **additive**: it creates desired buckets and (re-)deploys functions, but never deletes\n * them. Teardown is destructive, so it stays explicit/manual — matching the existing\n * auth / dataApi behaviour.\n *\n * The AI Gateway is intentionally NOT planned here: it is always available on a branch\n * (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan\n * step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway\n * env vars (`@neondatabase/env`). There is nothing to create, and nothing to probe.\n *\n * Functions are always (re-)deployed: deployments are versioned and the newest becomes\n * active, so each push ships the current source. There is no separate create step — Neon\n * creates the function on its first deployment — so a single `deploy-function` step is\n * emitted per desired function, carrying `functionExists` so the apply can report it as a\n * create (first deploy) or an update (re-deploy).\n */\nfunction diffPreview(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\tplan: PlanStep[];\n}): void {\n\tconst { config, remote, plan } = args;\n\tconst preview = config.preview;\n\tif (!preview) return;\n\t// `remote.preview` is only fetched when the policy has a preview block; treat a missing\n\t// snapshot as \"nothing exists yet\" so the diff is still well-defined.\n\tconst state: RemotePreviewState = remote.preview ?? {\n\t\tbuckets: [],\n\t\tfunctions: [],\n\t};\n\n\tfor (const bucket of preview.buckets) {\n\t\tif (state.buckets.some((b) => b.name === bucket.name)) continue;\n\t\tplan.push({\n\t\t\tkind: \"create-bucket\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tbucketName: bucket.name,\n\t\t\taccessLevel: bucket.access,\n\t\t});\n\t}\n\n\tfor (const fn of preview.functions) {\n\t\tconst exists = state.functions.some((f) => f.slug === fn.slug);\n\t\t// Neon creates the function on its first deployment (there is no separate create\n\t\t// endpoint), so always emit a single deploy step and let `functionExists` decide\n\t\t// whether it is reported as a create or an update.\n\t\tplan.push({\n\t\t\tkind: \"deploy-function\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tfn,\n\t\t\tfunctionExists: exists,\n\t\t});\n\t}\n}\n\n/**\n * Plan branch-scoped integrations. Enabling is additive (no existing resource to override).\n * The Data API is the one integration that also has a reconcilable *update*: its runtime\n * `settings` can drift once enabled, and reconciling them is an override (gated on\n * `updateExisting`, like compute/TTL/protected). The auth provider / JWKS wiring is fixed at\n * enable time, so it is never updated here. Disabling stays explicit/manual (destructive).\n */\nfunction diffServices(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tif (config.authEnabled && !state.authEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-auth\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t};\n\t\tif (state.databaseName) step.databaseName = state.databaseName;\n\t\tplan.push(step);\n\t}\n\tif (config.dataApiEnabled) {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t}\n}\n\n/**\n * Plan the Data API: a first-time **enable** (carrying the create-time auth wiring +\n * settings), or — when it already exists — a **settings update** if the policy's settings\n * drift from the remote. The update is an override: applied as a plan step under\n * `updateExisting`, otherwise reported as a conflict.\n */\nfunction diffDataApi(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tconst desired = config.dataApi;\n\n\tif (!state.dataApiEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t};\n\t\tconst input = desired ? enableInputFromResolved(desired) : undefined;\n\t\tif (input) step.input = input;\n\t\tplan.push(step);\n\t\treturn;\n\t}\n\n\t// Already enabled: the only reconcilable change is its runtime settings.\n\tconst desiredSettings = desired?.settings;\n\tif (!desiredSettings) return;\n\tif (!dataApiSettingsDiffer(desiredSettings, state.dataApiSettings)) return;\n\n\tif (options.updateExisting) {\n\t\tplan.push({\n\t\t\tkind: \"update-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t\tsettings: desiredSettings,\n\t\t});\n\t} else {\n\t\tconflicts.push({\n\t\t\tkind: \"branch\",\n\t\t\tidentifier: remote.branch.name,\n\t\t\tfield: \"dataApi.settings\",\n\t\t\tcurrent: state.dataApiSettings ?? undefined,\n\t\t\tdesired: desiredSettings,\n\t\t\treason: \"Existing Data API has different settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t});\n\t}\n}\n\n/** Build the create-time {@link EnableDataApiInput} from a resolved Data API config. */\nfunction enableInputFromResolved(\n\tresolved: ResolvedDataApiConfig,\n): EnableDataApiInput {\n\tconst input: EnableDataApiInput = { authProvider: resolved.authProvider };\n\tif (resolved.jwksUrl !== undefined) input.jwksUrl = resolved.jwksUrl;\n\tif (resolved.providerName !== undefined)\n\t\tinput.providerName = resolved.providerName;\n\tif (resolved.jwtAudience !== undefined)\n\t\tinput.jwtAudience = resolved.jwtAudience;\n\tif (resolved.settings) input.settings = resolved.settings;\n\treturn input;\n}\n\n/** The camelCase keys of {@link DataApiSettings}, used to compare desired vs remote settings. */\nconst DATA_API_SETTING_KEYS = [\n\t\"dbAggregatesEnabled\",\n\t\"dbAnonRole\",\n\t\"dbExtraSearchPath\",\n\t\"dbMaxRows\",\n\t\"dbSchemas\",\n\t\"jwtRoleClaimKey\",\n\t\"jwtCacheMaxLifetime\",\n\t\"openapiMode\",\n\t\"serverCorsAllowedOrigins\",\n\t\"serverTimingEnabled\",\n] as const satisfies ReadonlyArray<keyof DataApiSettings>;\n\n/**\n * Whether the policy's Data API `settings` differ from the remote ones. Only the keys the\n * policy actually set are compared (so unset fields never count as drift). When the remote\n * settings are not reported (`null`/absent — non-SubZero), drift can't be computed and this\n * returns `false` so no spurious update is planned.\n */\nfunction dataApiSettingsDiffer(\n\tdesired: DataApiSettings,\n\tcurrent: DataApiSettings | null | undefined,\n): boolean {\n\tif (!current) return false;\n\tfor (const key of DATA_API_SETTING_KEYS) {\n\t\tif (desired[key] === undefined) continue;\n\t\tif (JSON.stringify(desired[key]) !== JSON.stringify(current[key])) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface BranchConfigArgs {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\nfunction diffBranchConfig(args: BranchConfigArgs): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst branchName = remote.branch.name;\n\tconst computeSettings = config.postgres?.computeSettings;\n\n\tif (computeSettings) {\n\t\tconst endpoint = remote.endpoint;\n\t\tif (!endpoint) {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"endpoint\",\n\t\t\t\tcurrent: undefined,\n\t\t\t\tdesired: computeSettings,\n\t\t\t\treason: \"Branch has no read-write endpoint; cannot apply compute settings.\",\n\t\t\t});\n\t\t} else {\n\t\t\tconst drift = computeDriftBetween(computeSettings, endpoint);\n\t\t\tif (drift) {\n\t\t\t\tif (options.updateExisting) {\n\t\t\t\t\tplan.push({\n\t\t\t\t\t\tkind: \"update-endpoint\",\n\t\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\t\tbranchName,\n\t\t\t\t\t\tendpointId: endpoint.id,\n\t\t\t\t\t\tsettings: computeSettings,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\t\tcurrent: drift.current,\n\t\t\t\t\t\tdesired: drift.desired,\n\t\t\t\t\t\treason: \"Existing branch has different compute settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (\n\t\tconfig.protected !== undefined &&\n\t\tconfig.protected !== remote.branch.protected\n\t) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"update-branch-protected\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName,\n\t\t\t\tprotected: config.protected,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"protected\",\n\t\t\t\tcurrent: remote.branch.protected,\n\t\t\t\tdesired: config.protected,\n\t\t\t\treason: \"Existing branch has a different `protected` flag. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (config.ttlSeconds !== undefined) {\n\t\tconst current = remote.branch.expiresAt\n\t\t\t? Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.round(\n\t\t\t\t\t\t(Date.parse(remote.branch.expiresAt) - Date.now()) /\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tif (\n\t\t\tcurrent === undefined ||\n\t\t\tMath.abs(current - config.ttlSeconds) > 30\n\t\t) {\n\t\t\tconst expiresAt = new Date(\n\t\t\t\tDate.now() + config.ttlSeconds * 1000,\n\t\t\t).toISOString();\n\t\t\tif (options.updateExisting) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-branch-ttl\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName,\n\t\t\t\t\texpiresAt,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconflicts.push({\n\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\tfield: \"ttl\",\n\t\t\t\t\tcurrent: remote.branch.expiresAt,\n\t\t\t\t\tdesired: expiresAt,\n\t\t\t\t\treason: \"Existing branch has a different TTL. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction computeDriftBetween(\n\tdesired: ComputeSettings,\n\tendpoint: NeonEndpointSnapshot,\n): {\n\tcurrent: Partial<ComputeSettings>;\n\tdesired: Partial<ComputeSettings>;\n} | null {\n\tconst currentDrift: Partial<ComputeSettings> = {};\n\tconst desiredDrift: Partial<ComputeSettings> = {};\n\tlet drift = false;\n\n\tif (\n\t\tdesired.autoscalingLimitMinCu !== undefined &&\n\t\tdesired.autoscalingLimitMinCu !== endpoint.autoscalingLimitMinCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;\n\t\tdesiredDrift.autoscalingLimitMinCu = desired.autoscalingLimitMinCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.autoscalingLimitMaxCu !== undefined &&\n\t\tdesired.autoscalingLimitMaxCu !== endpoint.autoscalingLimitMaxCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;\n\t\tdesiredDrift.autoscalingLimitMaxCu = desired.autoscalingLimitMaxCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.suspendTimeout !== undefined &&\n\t\tdesired.suspendTimeout !== endpoint.suspendTimeout\n\t) {\n\t\tcurrentDrift.suspendTimeout = endpoint.suspendTimeout;\n\t\tdesiredDrift.suspendTimeout = desired.suspendTimeout;\n\t\tdrift = true;\n\t}\n\treturn drift ? { current: currentDrift, desired: desiredDrift } : null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"diff.js","names":[],"sources":["../../src/lib/diff.ts"],"sourcesContent":["import type {\n\tEnableDataApiInput,\n\tNeonBranchSnapshot,\n\tNeonBucketSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionSnapshot,\n} from \"./neon-api.js\";\nimport type {\n\tBucketAccessLevel,\n\tComputeSettings,\n\tConflictReport,\n\tDataApiSettings,\n\tResolvedBranchConfig,\n\tResolvedDataApiConfig,\n\tResolvedFunctionConfig,\n} from \"./types.js\";\n\n/**\n * A planned action to perform a single mutation against the Neon API. The diff engine\n * produces a list of these for `pushConfig` to execute (or report).\n */\nexport type PlanStep =\n\t| {\n\t\t\tkind: \"update-branch-ttl\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\texpiresAt: string | null;\n\t }\n\t| {\n\t\t\tkind: \"update-branch-protected\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tprotected: boolean;\n\t }\n\t| {\n\t\t\tkind: \"update-endpoint\";\n\t\t\tprojectId: string;\n\t\t\tbranchName: string;\n\t\t\tendpointId: string;\n\t\t\tsettings: ComputeSettings;\n\t }\n\t| {\n\t\t\tkind: \"enable-auth\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName?: string;\n\t }\n\t| {\n\t\t\tkind: \"enable-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\t/** Create-time auth wiring + initial settings from the policy. */\n\t\t\tinput?: EnableDataApiInput;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Reconcile the runtime settings of an already-enabled Data API integration.\n\t\t\t * Only `settings` are mutable post-create, so this is the lone Data API\n\t\t\t * *update* step — and it is an override (requires `updateExisting`).\n\t\t\t */\n\t\t\tkind: \"update-data-api\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tdatabaseName: string;\n\t\t\tsettings: DataApiSettings;\n\t }\n\t| {\n\t\t\tkind: \"create-bucket\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tbucketName: string;\n\t\t\taccessLevel: BucketAccessLevel;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * Deploy code to a function. Planned for every desired function — deployments are\n\t\t\t * versioned and the newest becomes active, so a push ships the current source each\n\t\t\t * time. Neon has no separate \"create function\" endpoint: the first deployment to a\n\t\t\t * slug creates the function. `functionExists` therefore only drives whether this\n\t\t\t * surfaces as a `create` (first deploy) or an `update` (re-deploy).\n\t\t\t */\n\t\t\tkind: \"deploy-function\";\n\t\t\tprojectId: string;\n\t\t\tbranchId: string;\n\t\t\tbranchName: string;\n\t\t\tfn: ResolvedFunctionConfig;\n\t\t\t/** Whether the function already existed remotely when the plan was computed. */\n\t\t\tfunctionExists: boolean;\n\t };\n\nexport interface RemoteServiceState {\n\tdatabaseName: string;\n\tauthEnabled: boolean;\n\tdataApiEnabled: boolean;\n\t/**\n\t * Current Data API runtime settings, when the integration is enabled and the API reports\n\t * them (SubZero only). `null`/absent means \"not reported\" — settings drift can't be\n\t * computed, so no update step is planned.\n\t */\n\tdataApiSettings?: DataApiSettings | null;\n}\n\n/**\n * Snapshot of the branch's current Preview-feature state. Absent (`undefined`) when the\n * policy has no `preview` block — `pushConfig` only fetches this when needed.\n */\nexport interface RemotePreviewState {\n\tbuckets: NeonBucketSnapshot[];\n\tfunctions: NeonFunctionSnapshot[];\n}\n\nexport interface RemoteState {\n\tprojectId: string;\n\tbranch: NeonBranchSnapshot;\n\tendpoint?: NeonEndpointSnapshot;\n\tservices: RemoteServiceState;\n\tpreview?: RemotePreviewState;\n}\n\nexport interface DiffOptions {\n\t/**\n\t * Apply mutable drift on the selected branch as plan steps instead of conflicts.\n\t * Default: `false`.\n\t */\n\tupdateExisting: boolean;\n}\n\nexport interface DiffResult {\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\n/**\n * Diff desired branch policy against the selected remote branch. Pure function.\n */\nexport function diffConfig(\n\tconfig: ResolvedBranchConfig,\n\tremote: RemoteState,\n\toptions: DiffOptions,\n): DiffResult {\n\tconst conflicts: ConflictReport[] = [];\n\tconst plan: PlanStep[] = [];\n\tdiffBranchConfig({ config, remote, options, plan, conflicts });\n\tdiffServices({ config, remote, options, plan, conflicts });\n\tdiffPreview({ config, remote, plan });\n\treturn { plan, conflicts };\n}\n\n/**\n * Plan Preview features (functions, buckets). Like {@link diffServices}, this is\n * **additive**: it creates desired buckets and (re-)deploys functions, but never deletes\n * them. Teardown is destructive, so it stays explicit/manual — matching the existing\n * auth / dataApi behaviour.\n *\n * The AI Gateway is intentionally NOT planned here: it is always available on a branch\n * (credential-gated, not per-branch provisioned), so `preview.aiGateway` produces no plan\n * step — it only drives the branch credential's `ai_gateway:invoke` scope and the gateway\n * env vars (`@neon/env`). There is nothing to create, and nothing to probe.\n *\n * Functions are always (re-)deployed: deployments are versioned and the newest becomes\n * active, so each push ships the current source. There is no separate create step — Neon\n * creates the function on its first deployment — so a single `deploy-function` step is\n * emitted per desired function, carrying `functionExists` so the apply can report it as a\n * create (first deploy) or an update (re-deploy).\n */\nfunction diffPreview(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\tplan: PlanStep[];\n}): void {\n\tconst { config, remote, plan } = args;\n\tconst preview = config.preview;\n\tif (!preview) return;\n\t// `remote.preview` is only fetched when the policy has a preview block; treat a missing\n\t// snapshot as \"nothing exists yet\" so the diff is still well-defined.\n\tconst state: RemotePreviewState = remote.preview ?? {\n\t\tbuckets: [],\n\t\tfunctions: [],\n\t};\n\n\tfor (const bucket of preview.buckets) {\n\t\tif (state.buckets.some((b) => b.name === bucket.name)) continue;\n\t\tplan.push({\n\t\t\tkind: \"create-bucket\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tbucketName: bucket.name,\n\t\t\taccessLevel: bucket.access,\n\t\t});\n\t}\n\n\tfor (const fn of preview.functions) {\n\t\tconst exists = state.functions.some((f) => f.slug === fn.slug);\n\t\t// Neon creates the function on its first deployment (there is no separate create\n\t\t// endpoint), so always emit a single deploy step and let `functionExists` decide\n\t\t// whether it is reported as a create or an update.\n\t\tplan.push({\n\t\t\tkind: \"deploy-function\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tfn,\n\t\t\tfunctionExists: exists,\n\t\t});\n\t}\n}\n\n/**\n * Plan branch-scoped integrations. Enabling is additive (no existing resource to override).\n * The Data API is the one integration that also has a reconcilable *update*: its runtime\n * `settings` can drift once enabled, and reconciling them is an override (gated on\n * `updateExisting`, like compute/TTL/protected). The auth provider / JWKS wiring is fixed at\n * enable time, so it is never updated here. Disabling stays explicit/manual (destructive).\n */\nfunction diffServices(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tif (config.authEnabled && !state.authEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-auth\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t};\n\t\tif (state.databaseName) step.databaseName = state.databaseName;\n\t\tplan.push(step);\n\t}\n\tif (config.dataApiEnabled) {\n\t\tdiffDataApi({ config, remote, options, plan, conflicts });\n\t}\n}\n\n/**\n * Plan the Data API: a first-time **enable** (carrying the create-time auth wiring +\n * settings), or — when it already exists — a **settings update** if the policy's settings\n * drift from the remote. The update is an override: applied as a plan step under\n * `updateExisting`, otherwise reported as a conflict.\n */\nfunction diffDataApi(args: {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst state = remote.services;\n\tconst desired = config.dataApi;\n\n\tif (!state.dataApiEnabled) {\n\t\tconst step: PlanStep = {\n\t\t\tkind: \"enable-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t};\n\t\tconst input = desired ? enableInputFromResolved(desired) : undefined;\n\t\tif (input) step.input = input;\n\t\tplan.push(step);\n\t\treturn;\n\t}\n\n\t// Already enabled: the only reconcilable change is its runtime settings.\n\tconst desiredSettings = desired?.settings;\n\tif (!desiredSettings) return;\n\tif (!dataApiSettingsDiffer(desiredSettings, state.dataApiSettings)) return;\n\n\tif (options.updateExisting) {\n\t\tplan.push({\n\t\t\tkind: \"update-data-api\",\n\t\t\tprojectId: remote.projectId,\n\t\t\tbranchId: remote.branch.id,\n\t\t\tbranchName: remote.branch.name,\n\t\t\tdatabaseName: state.databaseName,\n\t\t\tsettings: desiredSettings,\n\t\t});\n\t} else {\n\t\tconflicts.push({\n\t\t\tkind: \"branch\",\n\t\t\tidentifier: remote.branch.name,\n\t\t\tfield: \"dataApi.settings\",\n\t\t\tcurrent: state.dataApiSettings ?? undefined,\n\t\t\tdesired: desiredSettings,\n\t\t\treason: \"Existing Data API has different settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t});\n\t}\n}\n\n/** Build the create-time {@link EnableDataApiInput} from a resolved Data API config. */\nfunction enableInputFromResolved(\n\tresolved: ResolvedDataApiConfig,\n): EnableDataApiInput {\n\tconst input: EnableDataApiInput = { authProvider: resolved.authProvider };\n\tif (resolved.jwksUrl !== undefined) input.jwksUrl = resolved.jwksUrl;\n\tif (resolved.providerName !== undefined)\n\t\tinput.providerName = resolved.providerName;\n\tif (resolved.jwtAudience !== undefined)\n\t\tinput.jwtAudience = resolved.jwtAudience;\n\tif (resolved.settings) input.settings = resolved.settings;\n\treturn input;\n}\n\n/** The camelCase keys of {@link DataApiSettings}, used to compare desired vs remote settings. */\nconst DATA_API_SETTING_KEYS = [\n\t\"dbAggregatesEnabled\",\n\t\"dbAnonRole\",\n\t\"dbExtraSearchPath\",\n\t\"dbMaxRows\",\n\t\"dbSchemas\",\n\t\"jwtRoleClaimKey\",\n\t\"jwtCacheMaxLifetime\",\n\t\"openapiMode\",\n\t\"serverCorsAllowedOrigins\",\n\t\"serverTimingEnabled\",\n] as const satisfies ReadonlyArray<keyof DataApiSettings>;\n\n/**\n * Whether the policy's Data API `settings` differ from the remote ones. Only the keys the\n * policy actually set are compared (so unset fields never count as drift). When the remote\n * settings are not reported (`null`/absent — non-SubZero), drift can't be computed and this\n * returns `false` so no spurious update is planned.\n */\nfunction dataApiSettingsDiffer(\n\tdesired: DataApiSettings,\n\tcurrent: DataApiSettings | null | undefined,\n): boolean {\n\tif (!current) return false;\n\tfor (const key of DATA_API_SETTING_KEYS) {\n\t\tif (desired[key] === undefined) continue;\n\t\tif (JSON.stringify(desired[key]) !== JSON.stringify(current[key])) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface BranchConfigArgs {\n\tconfig: ResolvedBranchConfig;\n\tremote: RemoteState;\n\toptions: DiffOptions;\n\tplan: PlanStep[];\n\tconflicts: ConflictReport[];\n}\n\nfunction diffBranchConfig(args: BranchConfigArgs): void {\n\tconst { config, remote, options, plan, conflicts } = args;\n\tconst branchName = remote.branch.name;\n\tconst computeSettings = config.postgres?.computeSettings;\n\n\tif (computeSettings) {\n\t\tconst endpoint = remote.endpoint;\n\t\tif (!endpoint) {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"endpoint\",\n\t\t\t\tcurrent: undefined,\n\t\t\t\tdesired: computeSettings,\n\t\t\t\treason: \"Branch has no read-write endpoint; cannot apply compute settings.\",\n\t\t\t});\n\t\t} else {\n\t\t\tconst drift = computeDriftBetween(computeSettings, endpoint);\n\t\t\tif (drift) {\n\t\t\t\tif (options.updateExisting) {\n\t\t\t\t\tplan.push({\n\t\t\t\t\t\tkind: \"update-endpoint\",\n\t\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\t\tbranchName,\n\t\t\t\t\t\tendpointId: endpoint.id,\n\t\t\t\t\t\tsettings: computeSettings,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\t\tfield: \"computeSettings\",\n\t\t\t\t\t\tcurrent: drift.current,\n\t\t\t\t\t\tdesired: drift.desired,\n\t\t\t\t\t\treason: \"Existing branch has different compute settings. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (\n\t\tconfig.protected !== undefined &&\n\t\tconfig.protected !== remote.branch.protected\n\t) {\n\t\tif (options.updateExisting) {\n\t\t\tplan.push({\n\t\t\t\tkind: \"update-branch-protected\",\n\t\t\t\tprojectId: remote.projectId,\n\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\tbranchName,\n\t\t\t\tprotected: config.protected,\n\t\t\t});\n\t\t} else {\n\t\t\tconflicts.push({\n\t\t\t\tkind: \"branch\",\n\t\t\t\tidentifier: branchName,\n\t\t\t\tfield: \"protected\",\n\t\t\t\tcurrent: remote.branch.protected,\n\t\t\t\tdesired: config.protected,\n\t\t\t\treason: \"Existing branch has a different `protected` flag. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (config.ttlSeconds !== undefined) {\n\t\tconst current = remote.branch.expiresAt\n\t\t\t? Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.round(\n\t\t\t\t\t\t(Date.parse(remote.branch.expiresAt) - Date.now()) /\n\t\t\t\t\t\t\t1000,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t: undefined;\n\t\tif (\n\t\t\tcurrent === undefined ||\n\t\t\tMath.abs(current - config.ttlSeconds) > 30\n\t\t) {\n\t\t\tconst expiresAt = new Date(\n\t\t\t\tDate.now() + config.ttlSeconds * 1000,\n\t\t\t).toISOString();\n\t\t\tif (options.updateExisting) {\n\t\t\t\tplan.push({\n\t\t\t\t\tkind: \"update-branch-ttl\",\n\t\t\t\t\tprojectId: remote.projectId,\n\t\t\t\t\tbranchId: remote.branch.id,\n\t\t\t\t\tbranchName,\n\t\t\t\t\texpiresAt,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconflicts.push({\n\t\t\t\t\tkind: \"branch\",\n\t\t\t\t\tidentifier: branchName,\n\t\t\t\t\tfield: \"ttl\",\n\t\t\t\t\tcurrent: remote.branch.expiresAt,\n\t\t\t\t\tdesired: expiresAt,\n\t\t\t\t\treason: \"Existing branch has a different TTL. Pass `updateExisting: true` (SDK) or `--update-existing` (CLI) to apply.\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction computeDriftBetween(\n\tdesired: ComputeSettings,\n\tendpoint: NeonEndpointSnapshot,\n): {\n\tcurrent: Partial<ComputeSettings>;\n\tdesired: Partial<ComputeSettings>;\n} | null {\n\tconst currentDrift: Partial<ComputeSettings> = {};\n\tconst desiredDrift: Partial<ComputeSettings> = {};\n\tlet drift = false;\n\n\tif (\n\t\tdesired.autoscalingLimitMinCu !== undefined &&\n\t\tdesired.autoscalingLimitMinCu !== endpoint.autoscalingLimitMinCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMinCu = endpoint.autoscalingLimitMinCu;\n\t\tdesiredDrift.autoscalingLimitMinCu = desired.autoscalingLimitMinCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.autoscalingLimitMaxCu !== undefined &&\n\t\tdesired.autoscalingLimitMaxCu !== endpoint.autoscalingLimitMaxCu\n\t) {\n\t\tcurrentDrift.autoscalingLimitMaxCu = endpoint.autoscalingLimitMaxCu;\n\t\tdesiredDrift.autoscalingLimitMaxCu = desired.autoscalingLimitMaxCu;\n\t\tdrift = true;\n\t}\n\tif (\n\t\tdesired.suspendTimeout !== undefined &&\n\t\tdesired.suspendTimeout !== endpoint.suspendTimeout\n\t) {\n\t\tcurrentDrift.suspendTimeout = endpoint.suspendTimeout;\n\t\tdesiredDrift.suspendTimeout = desired.suspendTimeout;\n\t\tdrift = true;\n\t}\n\treturn drift ? { current: currentDrift, desired: desiredDrift } : null;\n}\n"],"mappings":"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"}
@@ -47,7 +47,7 @@ declare const ErrorCode: {
47
47
  };
48
48
  type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
49
49
  /**
50
- * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers
50
+ * Base class for all errors thrown by `@neon/config`. Always extend this so callers
51
51
  * can catch every package-thrown error with a single `instanceof` check.
52
52
  *
53
53
  * Optional `details` carries structured context that the CLI prints under `--debug` and
@@ -44,7 +44,7 @@ const ErrorCode = {
44
44
  };
45
45
  const ISSUE_URL = "https://github.com/neondatabase/neon-pkgs/issues/new";
46
46
  /**
47
- * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers
47
+ * Base class for all errors thrown by `@neon/config`. Always extend this so callers
48
48
  * can catch every package-thrown error with a single `instanceof` check.
49
49
  *
50
50
  * Optional `details` carries structured context that the CLI prints under `--debug` and
@@ -83,7 +83,7 @@ function isPlatformError(value) {
83
83
  * is supposed to fix something on their end.
84
84
  */
85
85
  function bugReportFooter() {
86
- return `\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;
86
+ return `\nThis indicates a bug in @neon/config. Please file an issue: ${ISSUE_URL}`;
87
87
  }
88
88
  /**
89
89
  * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neon/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neon/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
@@ -84,7 +84,7 @@ async function importModule(absPath) {
84
84
  const lower = absPath.toLowerCase();
85
85
  if (!(lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts"))) return import(pathToFileURL(absPath).href);
86
86
  const createJiti = extractCreateJiti(await import("jiti"));
87
- if (!createJiti) throw new ConfigLoadError(["jiti is required to load TypeScript config files but could not be initialised.", "Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config."].join(" "));
87
+ if (!createJiti) throw new ConfigLoadError(["jiti is required to load TypeScript config files but could not be initialised.", "Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neon/config."].join(" "));
88
88
  return createJiti(pathToFileURL(absPath).href, {
89
89
  interopDefault: true,
90
90
  moduleCache: false
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError, isPlatformError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\t// `defineConfig()` runs at module-eval time, so a config the user got *wrong*\n\t\t// (a bad function slug, an unknown key, an invalid duration, …) throws a\n\t\t// PlatformError from inside this import. That error already pinpoints the exact\n\t\t// field and reason, so surface it verbatim — burying it under the generic\n\t\t// \"this is usually a TypeScript syntax error\" hint sent users hunting for a\n\t\t// syntax bug that isn't there. The hint is reserved for genuine evaluation\n\t\t// failures (syntax errors, missing deps, thrown runtime exceptions).\n\t\tif (isPlatformError(cause)) {\n\t\t\tthrow cause;\n\t\t}\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError, isPlatformError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\t// `defineConfig()` runs at module-eval time, so a config the user got *wrong*\n\t\t// (a bad function slug, an unknown key, an invalid duration, …) throws a\n\t\t// PlatformError from inside this import. That error already pinpoints the exact\n\t\t// field and reason, so surface it verbatim — burying it under the generic\n\t\t// \"this is usually a TypeScript syntax error\" hint sent users hunting for a\n\t\t// syntax bug that isn't there. The hint is reserved for genuine evaluation\n\t\t// failures (syntax errors, missing deps, thrown runtime exceptions).\n\t\tif (isPlatformError(cause)) {\n\t\t\tthrow cause;\n\t\t}\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neon/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ interface CreateNeonAuthRestInput {
6
6
  database_name?: string;
7
7
  }
8
8
  /**
9
- * Adapt `@neondatabase/api-client` to the narrow {@link NeonApi} façade used by the rest of
9
+ * Adapt `@neon/sdk` (raw layer) to the narrow {@link NeonApi} façade used by the rest of
10
10
  * this package. Constructs are restricted to whole-object read/write of just the fields we
11
11
  * model in {@link Config}; anything else stays untouched on the remote.
12
12
  */
@@ -1 +1 @@
1
- {"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAyOU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AAyCC;AAeD;;;;AAES,iBA1DO,iBAAA,CA0DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AA84BV;AAiFA;AA6DA;AAoBA;EAAuC,aAAA,CAAA,EAAA;IAAQ,WAAA,CAAA,EAAA,MAAA;IAAsB,cAAA,CAAA,EAAA,MAAA;IAAQ,UAAA,CAAA,EAAA,MAAA;EAwBvD,CAAA;CAAY,CAAA,EAtnC9B,OAsnC8B;UAxlCxB,WAAA,CAwlC8B;aAAW,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBA3kCpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBA84BK,2BAAA;;;;;;;;;;;;iBAiFA,uBAAA;iBA6DA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}
1
+ {"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAiRU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AA4CC;AAeD;;;;AAES,iBA7DO,iBAAA,CA6DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AAk9BV;AAiFA;AA6DA;AAoBA;EAAuC,aAAA,CAAA,EAAA;IAAQ,WAAA,CAAA,EAAA,MAAA;IAAsB,cAAA,CAAA,EAAA,MAAA;IAAQ,UAAA,CAAA,EAAA,MAAA;EAwBvD,CAAA;CAAY,CAAA,EA7rC9B,OA6rC8B;UA5pCxB,WAAA,CA4pC8B;aAAW,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBA/oCpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBAk9BK,2BAAA;;;;;;;;;;;;iBAiFA,uBAAA;iBA6DA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}