@neon/config 0.0.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.
Files changed (60) hide show
  1. package/LICENSE.md +178 -0
  2. package/README.md +148 -0
  3. package/dist/index.d.ts +11 -0
  4. package/dist/index.js +10 -0
  5. package/dist/lib/auth.d.ts +67 -0
  6. package/dist/lib/auth.d.ts.map +1 -0
  7. package/dist/lib/auth.js +107 -0
  8. package/dist/lib/auth.js.map +1 -0
  9. package/dist/lib/credentials.d.ts +37 -0
  10. package/dist/lib/credentials.d.ts.map +1 -0
  11. package/dist/lib/credentials.js +30 -0
  12. package/dist/lib/credentials.js.map +1 -0
  13. package/dist/lib/define-config.d.ts +123 -0
  14. package/dist/lib/define-config.d.ts.map +1 -0
  15. package/dist/lib/define-config.js +168 -0
  16. package/dist/lib/define-config.js.map +1 -0
  17. package/dist/lib/diff.d.ts +120 -0
  18. package/dist/lib/diff.d.ts.map +1 -0
  19. package/dist/lib/diff.js +284 -0
  20. package/dist/lib/diff.js.map +1 -0
  21. package/dist/lib/duration.d.ts +68 -0
  22. package/dist/lib/duration.d.ts.map +1 -0
  23. package/dist/lib/duration.js +111 -0
  24. package/dist/lib/duration.js.map +1 -0
  25. package/dist/lib/errors.d.ts +140 -0
  26. package/dist/lib/errors.d.ts.map +1 -0
  27. package/dist/lib/errors.js +185 -0
  28. package/dist/lib/errors.js.map +1 -0
  29. package/dist/lib/loader.d.ts +44 -0
  30. package/dist/lib/loader.d.ts.map +1 -0
  31. package/dist/lib/loader.js +120 -0
  32. package/dist/lib/loader.js.map +1 -0
  33. package/dist/lib/neon-api-real.d.ts +92 -0
  34. package/dist/lib/neon-api-real.d.ts.map +1 -0
  35. package/dist/lib/neon-api-real.js +957 -0
  36. package/dist/lib/neon-api-real.js.map +1 -0
  37. package/dist/lib/neon-api.d.ts +373 -0
  38. package/dist/lib/neon-api.d.ts.map +1 -0
  39. package/dist/lib/neon-api.js +1 -0
  40. package/dist/lib/patterns.d.ts +43 -0
  41. package/dist/lib/patterns.d.ts.map +1 -0
  42. package/dist/lib/patterns.js +76 -0
  43. package/dist/lib/patterns.js.map +1 -0
  44. package/dist/lib/schema.d.ts +215 -0
  45. package/dist/lib/schema.d.ts.map +1 -0
  46. package/dist/lib/schema.js +284 -0
  47. package/dist/lib/schema.js.map +1 -0
  48. package/dist/lib/types.d.ts +546 -0
  49. package/dist/lib/types.d.ts.map +1 -0
  50. package/dist/lib/types.js +18 -0
  51. package/dist/lib/types.js.map +1 -0
  52. package/dist/lib/wrap-neon-error.d.ts +30 -0
  53. package/dist/lib/wrap-neon-error.d.ts.map +1 -0
  54. package/dist/lib/wrap-neon-error.js +139 -0
  55. package/dist/lib/wrap-neon-error.js.map +1 -0
  56. package/dist/v1.d.ts +211 -0
  57. package/dist/v1.d.ts.map +1 -0
  58. package/dist/v1.js +82 -0
  59. package/dist/v1.js.map +1 -0
  60. package/package.json +57 -18
@@ -0,0 +1,123 @@
1
+ import { BranchTarget, BranchTuningFn, BucketDef, Config, DataApiInput, FunctionDef, PreviewInput, ResolvedBranchConfig, ServiceEnabled, ServiceToggleInput } from "./types.js";
2
+
3
+ //#region src/lib/define-config.d.ts
4
+
5
+ /**
6
+ * Whether a `dataApi` toggle is **enabled and verified by Neon Auth** at the type level: it is
7
+ * on (see {@link ServiceEnabled}) and not the explicit `authProvider: "external"` variant
8
+ * (so the default / `"neon"` provider). This is the case that requires top-level Neon Auth.
9
+ */
10
+ type DataApiUsesNeonAuth<DataApi> = ServiceEnabled<DataApi> extends true ? [DataApi] extends [{
11
+ authProvider: "external";
12
+ }] ? false : true : false;
13
+ /**
14
+ * Human-readable hint surfaced as the **expected type** of `dataApi` when a Neon-Auth Data
15
+ * API is declared without Neon Auth enabled (see {@link DataApiField}). TypeScript prints the
16
+ * offending value against this string literal — `Type 'true' is not assignable to type
17
+ * '…requires `auth: true`…'` — which points straight at the fix, instead of the opaque
18
+ * `Type 'true' is not assignable to type 'never'` an intersection guard produces.
19
+ *
20
+ * It documents **both** fixes: enabling Neon Auth (`auth: true`), and running the Data API
21
+ * *without* Neon Auth by verifying a third-party IdP (`authProvider: 'external'` + `jwksUrl`).
22
+ */
23
+ type NeonAuthRequiredHint = "`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' }`";
24
+ /**
25
+ * Static cross-field guard for {@link defineConfig}, expressed as the **type of the `dataApi`
26
+ * field** rather than an intersected requirement on `auth`.
27
+ *
28
+ * - A Neon-Auth Data API (`authProvider: "neon"`, the default) with top-level `auth` enabled,
29
+ * or any external Data API: the field keeps its normal `DataApi & DataApiInput` type (the
30
+ * `& DataApiInput` preserves member autocomplete; the `const DataApi` still types the
31
+ * returned {@link Config}).
32
+ * - A Neon-Auth Data API **without** `auth` enabled: the field's expected type collapses to
33
+ * the {@link NeonAuthRequiredHint} message, so the author sees the rule (and the two fixes)
34
+ * right on the `dataApi` value.
35
+ *
36
+ * The runtime `superRefine` in {@link configInputSchema} enforces the same invariant for
37
+ * non-typed (plain-JS) callers, so the behavior is identical — only the type-level message
38
+ * changes.
39
+ */
40
+ type DataApiField<Auth, DataApi> = DataApiUsesNeonAuth<DataApi> extends true ? ServiceEnabled<Auth> extends true ? DataApi & DataApiInput : NeonAuthRequiredHint : DataApi & DataApiInput;
41
+ /**
42
+ * Autocomplete bridge for the nested `preview.functions` / `preview.buckets` slug objects.
43
+ *
44
+ * {@link PreviewInput} types those records with a string index signature
45
+ * (`Record<string, FunctionDef>` / `Record<string, BucketDef>`). When `defineConfig` infers
46
+ * `const Preview`, every authored slug becomes a **named** property on the inferred literal
47
+ * (e.g. `{ hello: { name; source } }`), and a named property **shadows** the index signature
48
+ * when the editor computes the contextual type of that slug's value — so the rest of
49
+ * {@link FunctionDef} / {@link BucketDef} (`env`, `dev`, `access`, …) never surfaces as
50
+ * completions inside `hello: { … }` / `uploads: { … }`.
51
+ *
52
+ * Re-declaring each inferred slug's value as `FunctionDef` / `BucketDef` (a *named* member, via
53
+ * a mapped type over the already-inferred keys) puts those members back onto the contextual
54
+ * type without going through an index signature, which restores autocomplete. Intersected with
55
+ * `Preview & PreviewInput` it neither widens what is accepted (the values were already
56
+ * `FunctionDef` / `BucketDef`) nor perturbs the inferred `const Preview` — so slug inference for
57
+ * `BranchTuningFn<Preview>` and the returned {@link Config} is unchanged.
58
+ */
59
+ type PreviewAutocomplete<Preview> = (Preview extends {
60
+ functions: infer F;
61
+ } ? {
62
+ functions: { [Slug in keyof F]: FunctionDef };
63
+ } : unknown) & (Preview extends {
64
+ buckets: infer B;
65
+ } ? {
66
+ buckets: { [Name in keyof B]: BucketDef };
67
+ } : unknown);
68
+ /**
69
+ * Validate and freeze a Neon Platform branch policy.
70
+ *
71
+ * Used at the top of `neon.ts`:
72
+ * ```ts
73
+ * import { defineConfig } from "@neon/config/v1";
74
+ *
75
+ * export default defineConfig({
76
+ * auth: true,
77
+ * preview: {
78
+ * functions: {
79
+ * hello: { name: "Hello", source: "./functions/hello.ts", dev: { port: 8787 } },
80
+ * },
81
+ * },
82
+ * branch: (branch) => ({ protected: branch.name === "main" }),
83
+ * });
84
+ * ```
85
+ *
86
+ * The policy is split into a **static** existential set (top-level `auth` / `dataApi`
87
+ * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The
88
+ * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`
89
+ * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function
90
+ * deploy settings), never change what exists.
91
+ *
92
+ * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch
93
+ * being decided for (not a live handle); switch on its facts (`branch.name`,
94
+ * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two
95
+ * modes: against an existing branch (fields populated from Neon) and during pre-create
96
+ * evaluation (`exists: false`, `id` undefined).
97
+ *
98
+ * Pure: no I/O, no side effects. The static parts are validated here; the closure's output
99
+ * is validated every time it is evaluated so errors point at the concrete branch target.
100
+ */
101
+ declare function defineConfig<const Auth extends ServiceToggleInput | undefined = undefined, const DataApi extends DataApiInput | undefined = undefined, const Preview extends PreviewInput | undefined = undefined>(input: {
102
+ auth?: Auth & ServiceToggleInput;
103
+ dataApi?: DataApiField<Auth, DataApi>;
104
+ preview?: Preview & PreviewInput & PreviewAutocomplete<Preview>;
105
+ branch?: BranchTuningFn<Preview>;
106
+ }): Config<Auth, DataApi, Preview>;
107
+ /**
108
+ * Evaluate a branch policy for a specific branch target and return a normalized config.
109
+ *
110
+ * Merges the static existential set (services + preview functions/buckets) with the
111
+ * per-branch tuning returned by the `branch` closure into the same {@link
112
+ * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.
113
+ */
114
+ declare function resolveConfig(config: Config, branch: BranchTarget): ResolvedBranchConfig;
115
+ /**
116
+ * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes
117
+ * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the
118
+ * NeonApi adapter.
119
+ */
120
+ declare function normalizeRegion(region: string): string;
121
+ //#endregion
122
+ export { DataApiField, NeonAuthRequiredHint, defineConfig, normalizeRegion, resolveConfig };
123
+ //# sourceMappingURL=define-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.d.ts","names":[],"sources":["../../src/lib/define-config.ts"],"mappings":";;;;;;AAwBoB;;;KAYf,mBAA+B,CAAA,OAAA,CAAA,GAAA,cAAA,CAAe,OAAf,CAAA,SAAA,IAAA,GAAA,CAChC,OADgC,CAAA,SAAA,CAAA;cAChC,EAAA,UAAA;AAAO,CAAA,CAAA,GAAA,KAAA,GAAA,IAAA,GAAA,KAAA;AAiBX;AAqBA;;;;;;;;;AAKI,KA1BQ,oBAAA,GA0BR,+QAAA;;AAAsB;AAAC;;;;;;;;AAwBkB;AAoC7C;;;;;AAWQ,KA5EI,YA4EJ,CAAA,IAAA,EAAA,OAAA,CAAA,GA3EP,mBA2EO,CA3Ea,OA2Eb,CAAA,SAAA,IAAA,GA1EJ,cA0EI,CA1EW,IA0EX,CAAA,SAAA,IAAA,GAzEH,OAyEG,GAzEO,YAyEP,GAxEH,oBAwEG,GAvEJ,OAuEI,GAvEM,YAuEN;;;;;;;;;;;;;;;AAUE;AA4BV;;;KAzFK,mBA2FI,CAAA,OAAA,CAAA,GAAA,CA3F4B,OA2F5B,SAAA;WACN,EAAA,KAAA,EAAA;AAAoB,CAAA,GAAA;EAgKP,SAAA,EAAA,iBA3PiB,IAAI;gBAEnC;;;4BAC8B,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCpB,gCACI,kEACG,4DACA;SAQf,OAAO;YAIJ,aAAa,MAAM;YAInB,UAAU,eAAe,oBAAoB;WAC9C,eAAe;IACrB,OAAO,MAAM,SAAS;;;;;;;;iBA4BV,aAAA,SACP,gBACA,eACN;;;;;;iBAgKa,eAAA"}
@@ -0,0 +1,168 @@
1
+ import { ConfigValidationError } from "./errors.js";
2
+ import { parseBranchTtl } from "./duration.js";
3
+ import { branchTuningSchema, configInputSchema, formatZodIssues } from "./schema.js";
4
+ //#region src/lib/define-config.ts
5
+ /** Default deploy parameters applied to functions that omit them in `neon.ts`. */
6
+ const DEFAULT_FUNCTION_RUNTIME = "nodejs24";
7
+ const REGION_PREFIX = /^(aws|azure|gcp)-/;
8
+ /**
9
+ * Validate and freeze a Neon Platform branch policy.
10
+ *
11
+ * Used at the top of `neon.ts`:
12
+ * ```ts
13
+ * import { defineConfig } from "@neon/config/v1";
14
+ *
15
+ * export default defineConfig({
16
+ * auth: true,
17
+ * preview: {
18
+ * functions: {
19
+ * hello: { name: "Hello", source: "./functions/hello.ts", dev: { port: 8787 } },
20
+ * },
21
+ * },
22
+ * branch: (branch) => ({ protected: branch.name === "main" }),
23
+ * });
24
+ * ```
25
+ *
26
+ * The policy is split into a **static** existential set (top-level `auth` / `dataApi`
27
+ * toggles and the beta `preview` block) and a **dynamic** per-branch `branch` closure. The
28
+ * static half determines which secrets exist — so `NeonEnv<typeof config>` and `parseEnv`
29
+ * are exact — while the closure can only *tune* a branch (lifecycle, compute, per-function
30
+ * deploy settings), never change what exists.
31
+ *
32
+ * The `branch` callback receives a read-only {@link BranchTarget} descriptor of the branch
33
+ * being decided for (not a live handle); switch on its facts (`branch.name`,
34
+ * `branch.isDefault`, `branch.exists`, …) and **return** the desired tuning. It runs in two
35
+ * modes: against an existing branch (fields populated from Neon) and during pre-create
36
+ * evaluation (`exists: false`, `id` undefined).
37
+ *
38
+ * Pure: no I/O, no side effects. The static parts are validated here; the closure's output
39
+ * is validated every time it is evaluated so errors point at the concrete branch target.
40
+ */
41
+ function defineConfig(input) {
42
+ if (typeof input === "function") throw new ConfigValidationError(["defineConfig now expects an object, not a function: `export default defineConfig({ auth: true, preview: { … }, branch: (branch) => ({ … }) })`.", "The static services/preview set moved to the top level; per-branch logic moved into the `branch` closure."]);
43
+ if (input === null || typeof input !== "object") throw new ConfigValidationError(["defineConfig expects a configuration object: `export default defineConfig({ … })`."]);
44
+ const parsed = configInputSchema.safeParse(input);
45
+ if (!parsed.success) throw new ConfigValidationError(formatZodIssues(parsed.error));
46
+ return Object.freeze({ ...input });
47
+ }
48
+ /**
49
+ * Evaluate a branch policy for a specific branch target and return a normalized config.
50
+ *
51
+ * Merges the static existential set (services + preview functions/buckets) with the
52
+ * per-branch tuning returned by the `branch` closure into the same {@link
53
+ * ResolvedBranchConfig} the rest of the runtime (diff / push / fetchEnv) consumes.
54
+ */
55
+ function resolveConfig(config, branch) {
56
+ const tuning = evaluateBranchTuning(config.branch, branch);
57
+ const resolved = {
58
+ authEnabled: isServiceEnabled(config.auth),
59
+ dataApiEnabled: isDataApiEnabled(config.dataApi)
60
+ };
61
+ const dataApi = resolveDataApi(config.dataApi);
62
+ if (dataApi) resolved.dataApi = dataApi;
63
+ if (tuning.parent !== void 0) resolved.parent = tuning.parent;
64
+ if (tuning.ttl !== void 0) {
65
+ const parsedTtl = parseBranchTtl(tuning.ttl);
66
+ if (!("error" in parsedTtl)) resolved.ttlSeconds = parsedTtl.seconds;
67
+ }
68
+ if (tuning.protected !== void 0) resolved.protected = tuning.protected;
69
+ if (tuning.postgres?.computeSettings) resolved.postgres = { computeSettings: { ...tuning.postgres.computeSettings } };
70
+ const preview = resolvePreviewConfig(config.preview, tuning);
71
+ if (preview) resolved.preview = preview;
72
+ return resolved;
73
+ }
74
+ /**
75
+ * Run the `branch` closure (when present) for the target and validate its output. The
76
+ * closure is optional — a fully static policy resolves with empty tuning.
77
+ */
78
+ function evaluateBranchTuning(branchFn, target) {
79
+ if (!branchFn) return {};
80
+ let raw;
81
+ try {
82
+ raw = branchFn(Object.freeze({ ...target }));
83
+ } catch (cause) {
84
+ throw new ConfigValidationError([`Branch policy threw while evaluating branch "${target.name}".`, cause?.message ?? String(cause)]);
85
+ }
86
+ const parsed = branchTuningSchema.safeParse(raw ?? {});
87
+ if (!parsed.success) throw new ConfigValidationError(formatZodIssues(parsed.error));
88
+ return parsed.data;
89
+ }
90
+ function isServiceEnabled(toggle) {
91
+ if (toggle === void 0) return false;
92
+ if (typeof toggle === "boolean") return toggle;
93
+ return toggle.enabled !== false;
94
+ }
95
+ /** Whether a {@link DataApiInput} is enabled (present object/`true` unless `enabled: false`). */
96
+ function isDataApiEnabled(input) {
97
+ if (input === void 0) return false;
98
+ if (typeof input === "boolean") return input;
99
+ return input.enabled !== false;
100
+ }
101
+ /**
102
+ * Normalize a {@link DataApiInput} into a {@link ResolvedDataApiConfig}, or `undefined` when
103
+ * the Data API is not enabled. `authProvider` defaults to `"neon"`; the external-IdP wiring
104
+ * is carried through only for the `"external"` provider; `settings` is copied with its
105
+ * `undefined` entries dropped so diffing only considers fields the policy actually set.
106
+ */
107
+ function resolveDataApi(input) {
108
+ if (!isDataApiEnabled(input)) return void 0;
109
+ if (typeof input !== "object") return { authProvider: "neon" };
110
+ const authProvider = input.authProvider ?? "neon";
111
+ const resolved = { authProvider };
112
+ if (authProvider === "external") {
113
+ if (input.jwksUrl !== void 0) resolved.jwksUrl = input.jwksUrl;
114
+ if (input.providerName !== void 0) resolved.providerName = input.providerName;
115
+ if (input.jwtAudience !== void 0) resolved.jwtAudience = input.jwtAudience;
116
+ }
117
+ const settings = normalizeDataApiSettings(input.settings);
118
+ if (settings) resolved.settings = settings;
119
+ return resolved;
120
+ }
121
+ /** Copy a {@link DataApiSettings}, dropping `undefined` entries; `undefined` when empty. */
122
+ function normalizeDataApiSettings(settings) {
123
+ if (!settings) return void 0;
124
+ const out = {};
125
+ for (const [key, value] of Object.entries(settings)) if (value !== void 0) out[key] = value;
126
+ return Object.keys(out).length > 0 ? out : void 0;
127
+ }
128
+ /**
129
+ * Normalize the static {@link PreviewInput} (merged with per-branch function tuning) into a
130
+ * {@link ResolvedPreviewConfig}. Returns `undefined` when the policy declares no `preview`
131
+ * block so the field can be omitted entirely. Function slugs / bucket names come from the
132
+ * record keys.
133
+ */
134
+ function resolvePreviewConfig(preview, tuning) {
135
+ if (!preview) return void 0;
136
+ const fnTuning = tuning.preview?.functions ?? {};
137
+ return {
138
+ functions: Object.entries(preview.functions ?? {}).map(([slug, def]) => resolveFunctionConfig(slug, def, fnTuning[slug] ?? {})),
139
+ buckets: Object.entries(preview.buckets ?? {}).map(([name, def]) => ({
140
+ name,
141
+ access: def.access ?? "private"
142
+ })),
143
+ aiGatewayEnabled: isServiceEnabled(preview.aiGateway)
144
+ };
145
+ }
146
+ function resolveFunctionConfig(slug, def, tuning) {
147
+ return {
148
+ slug,
149
+ name: def.name,
150
+ source: def.source,
151
+ env: { ...def.env ?? {} },
152
+ runtime: tuning.runtime ?? DEFAULT_FUNCTION_RUNTIME,
153
+ ...def.dev ? { dev: def.dev } : {}
154
+ };
155
+ }
156
+ /**
157
+ * Normalize a region identifier to Neon's `<cloud>-<region>` format. When the user writes
158
+ * `us-east-1` we assume `aws-us-east-1`. Pure helper used by both the validator and the
159
+ * NeonApi adapter.
160
+ */
161
+ function normalizeRegion(region) {
162
+ if (REGION_PREFIX.test(region)) return region;
163
+ return `aws-${region}`;
164
+ }
165
+ //#endregion
166
+ export { defineConfig, normalizeRegion, resolveConfig };
167
+
168
+ //# sourceMappingURL=define-config.js.map
@@ -0,0 +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 \"@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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"}
@@ -0,0 +1,120 @@
1
+ import { BucketAccessLevel, ComputeSettings, ConflictReport, DataApiSettings, ResolvedBranchConfig, ResolvedFunctionConfig } from "./types.js";
2
+ import { EnableDataApiInput, NeonBranchSnapshot, NeonBucketSnapshot, NeonEndpointSnapshot, NeonFunctionSnapshot } from "./neon-api.js";
3
+
4
+ //#region src/lib/diff.d.ts
5
+
6
+ /**
7
+ * A planned action to perform a single mutation against the Neon API. The diff engine
8
+ * produces a list of these for `pushConfig` to execute (or report).
9
+ */
10
+ type PlanStep = {
11
+ kind: "update-branch-ttl";
12
+ projectId: string;
13
+ branchId: string;
14
+ branchName: string;
15
+ expiresAt: string | null;
16
+ } | {
17
+ kind: "update-branch-protected";
18
+ projectId: string;
19
+ branchId: string;
20
+ branchName: string;
21
+ protected: boolean;
22
+ } | {
23
+ kind: "update-endpoint";
24
+ projectId: string;
25
+ branchName: string;
26
+ endpointId: string;
27
+ settings: ComputeSettings;
28
+ } | {
29
+ kind: "enable-auth";
30
+ projectId: string;
31
+ branchId: string;
32
+ branchName: string;
33
+ databaseName?: string;
34
+ } | {
35
+ kind: "enable-data-api";
36
+ projectId: string;
37
+ branchId: string;
38
+ branchName: string;
39
+ databaseName: string;
40
+ /** Create-time auth wiring + initial settings from the policy. */
41
+ input?: EnableDataApiInput;
42
+ } | {
43
+ /**
44
+ * Reconcile the runtime settings of an already-enabled Data API integration.
45
+ * Only `settings` are mutable post-create, so this is the lone Data API
46
+ * *update* step — and it is an override (requires `updateExisting`).
47
+ */
48
+ kind: "update-data-api";
49
+ projectId: string;
50
+ branchId: string;
51
+ branchName: string;
52
+ databaseName: string;
53
+ settings: DataApiSettings;
54
+ } | {
55
+ kind: "create-bucket";
56
+ projectId: string;
57
+ branchId: string;
58
+ branchName: string;
59
+ bucketName: string;
60
+ accessLevel: BucketAccessLevel;
61
+ } | {
62
+ /**
63
+ * Deploy code to a function. Planned for every desired function — deployments are
64
+ * versioned and the newest becomes active, so a push ships the current source each
65
+ * time. Neon has no separate "create function" endpoint: the first deployment to a
66
+ * slug creates the function. `functionExists` therefore only drives whether this
67
+ * surfaces as a `create` (first deploy) or an `update` (re-deploy).
68
+ */
69
+ kind: "deploy-function";
70
+ projectId: string;
71
+ branchId: string;
72
+ branchName: string;
73
+ fn: ResolvedFunctionConfig;
74
+ /** Whether the function already existed remotely when the plan was computed. */
75
+ functionExists: boolean;
76
+ };
77
+ interface RemoteServiceState {
78
+ databaseName: string;
79
+ authEnabled: boolean;
80
+ dataApiEnabled: boolean;
81
+ /**
82
+ * Current Data API runtime settings, when the integration is enabled and the API reports
83
+ * them (SubZero only). `null`/absent means "not reported" — settings drift can't be
84
+ * computed, so no update step is planned.
85
+ */
86
+ dataApiSettings?: DataApiSettings | null;
87
+ }
88
+ /**
89
+ * Snapshot of the branch's current Preview-feature state. Absent (`undefined`) when the
90
+ * policy has no `preview` block — `pushConfig` only fetches this when needed.
91
+ */
92
+ interface RemotePreviewState {
93
+ buckets: NeonBucketSnapshot[];
94
+ functions: NeonFunctionSnapshot[];
95
+ }
96
+ interface RemoteState {
97
+ projectId: string;
98
+ branch: NeonBranchSnapshot;
99
+ endpoint?: NeonEndpointSnapshot;
100
+ services: RemoteServiceState;
101
+ preview?: RemotePreviewState;
102
+ }
103
+ interface DiffOptions {
104
+ /**
105
+ * Apply mutable drift on the selected branch as plan steps instead of conflicts.
106
+ * Default: `false`.
107
+ */
108
+ updateExisting: boolean;
109
+ }
110
+ interface DiffResult {
111
+ plan: PlanStep[];
112
+ conflicts: ConflictReport[];
113
+ }
114
+ /**
115
+ * Diff desired branch policy against the selected remote branch. Pure function.
116
+ */
117
+ declare function diffConfig(config: ResolvedBranchConfig, remote: RemoteState, options: DiffOptions): DiffResult;
118
+ //#endregion
119
+ export { DiffOptions, DiffResult, PlanStep, RemotePreviewState, RemoteServiceState, RemoteState, diffConfig };
120
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","names":[],"sources":["../../src/lib/diff.ts"],"mappings":";;;;;;;AAqBA;;AAoBa,KApBD,QAAA,GAoBC;MAgBF,EAAA,mBAAA;WAaE,EAAA,MAAA;UAQG,EAAA,MAAA;YAcT,EAAA,MAAA;EAAsB,SAAA,EAAA,MAAA,GAAA,IAAA;AAK7B,CAAA,GAAiB;EAgBA,IAAA,EAAA,yBAAkB;EAAA,SAAA,EAAA,MAAA;UACzB,EAAA,MAAA;YACE,EAAA,MAAA;EAAoB,SAAA,EAAA,OAAA;AAGhC,CAAA,GAAiB;EAAW,IAAA,EAAA,iBAAA;WAEnB,EAAA,MAAA;YACG,EAAA,MAAA;YACD,EAAA,MAAA;UACA,EAlFE,eAkFF;AAAkB,CAAA,GAAA;EAGZ,IAAA,EAAA,aAAW;EAQX,SAAA,EAAA,MAAU;EAAA,QAAA,EAAA,MAAA;YACpB,EAAA,MAAA;cACK,CAAA,EAAA,MAAA;AAAc,CAAA,GAAA;EAMV,IAAA,EAAA,iBAAU;EAAA,SAAA,EAAA,MAAA;UACjB,EAAA,MAAA;YACA,EAAA,MAAA;cACC,EAAA,MAAA;;EACG,KAAA,CAAA,EAzFF,kBAyFE;;;;;;;;;;;;YA5EA;;;;;;;eAQG;;;;;;;;;;;;;MAcT;;;;UAKU,kBAAA;;;;;;;;;oBASE;;;;;;UAOF,kBAAA;WACP;aACE;;UAGK,WAAA;;UAER;aACG;YACD;YACA;;UAGM,WAAA;;;;;;;UAQA,UAAA;QACV;aACK;;;;;iBAMI,UAAA,SACP,8BACA,sBACC,cACP"}