@neondatabase/config 0.0.0 → 0.1.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 (90) hide show
  1. package/LICENSE.md +178 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +8 -0
  4. package/dist/lib/auth.d.ts +63 -0
  5. package/dist/lib/auth.d.ts.map +1 -0
  6. package/dist/lib/auth.js +93 -0
  7. package/dist/lib/auth.js.map +1 -0
  8. package/dist/lib/define-config.d.ts +43 -0
  9. package/dist/lib/define-config.d.ts.map +1 -0
  10. package/dist/lib/define-config.js +111 -0
  11. package/dist/lib/define-config.js.map +1 -0
  12. package/dist/lib/diff.d.ts +109 -0
  13. package/dist/lib/diff.d.ts.map +1 -0
  14. package/dist/lib/diff.js +205 -0
  15. package/dist/lib/diff.js.map +1 -0
  16. package/dist/lib/duration.d.ts +46 -0
  17. package/dist/lib/duration.d.ts.map +1 -0
  18. package/dist/lib/duration.js +96 -0
  19. package/dist/lib/duration.js.map +1 -0
  20. package/dist/lib/errors.d.ts +129 -0
  21. package/dist/lib/errors.d.ts.map +1 -0
  22. package/dist/lib/errors.js +168 -0
  23. package/dist/lib/errors.js.map +1 -0
  24. package/dist/lib/loader.d.ts +44 -0
  25. package/dist/lib/loader.d.ts.map +1 -0
  26. package/dist/lib/loader.js +119 -0
  27. package/dist/lib/loader.js.map +1 -0
  28. package/dist/lib/neon-api-real.d.ts +45 -0
  29. package/dist/lib/neon-api-real.d.ts.map +1 -0
  30. package/dist/lib/neon-api-real.js +582 -0
  31. package/dist/lib/neon-api-real.js.map +1 -0
  32. package/dist/lib/neon-api.d.ts +262 -0
  33. package/dist/lib/neon-api.d.ts.map +1 -0
  34. package/dist/lib/neon-api.js +1 -0
  35. package/dist/lib/patterns.d.ts +43 -0
  36. package/dist/lib/patterns.d.ts.map +1 -0
  37. package/dist/lib/patterns.js +76 -0
  38. package/dist/lib/patterns.js.map +1 -0
  39. package/dist/lib/schema.d.ts +109 -0
  40. package/dist/lib/schema.d.ts.map +1 -0
  41. package/dist/lib/schema.js +199 -0
  42. package/dist/lib/schema.js.map +1 -0
  43. package/dist/lib/types.d.ts +259 -0
  44. package/dist/lib/types.d.ts.map +1 -0
  45. package/dist/lib/types.js +1 -0
  46. package/dist/lib/wrap-neon-error.d.ts +30 -0
  47. package/dist/lib/wrap-neon-error.d.ts.map +1 -0
  48. package/dist/lib/wrap-neon-error.js +139 -0
  49. package/dist/lib/wrap-neon-error.js.map +1 -0
  50. package/dist/v1.d.ts +132 -0
  51. package/dist/v1.d.ts.map +1 -0
  52. package/dist/v1.js +69 -0
  53. package/dist/v1.js.map +1 -0
  54. package/package.json +67 -17
  55. package/.env.example +0 -5
  56. package/e2e/errors.e2e.test.ts +0 -52
  57. package/e2e/helpers.ts +0 -205
  58. package/e2e/load-env.ts +0 -29
  59. package/e2e/setup.ts +0 -24
  60. package/src/index.ts +0 -5
  61. package/src/lib/auth.test.ts +0 -166
  62. package/src/lib/auth.ts +0 -124
  63. package/src/lib/define-config.test.ts +0 -161
  64. package/src/lib/define-config.ts +0 -152
  65. package/src/lib/diff.test.ts +0 -142
  66. package/src/lib/diff.ts +0 -391
  67. package/src/lib/duration.test.ts +0 -105
  68. package/src/lib/duration.ts +0 -147
  69. package/src/lib/errors.test.ts +0 -26
  70. package/src/lib/errors.ts +0 -220
  71. package/src/lib/fake-neon-api.ts +0 -782
  72. package/src/lib/loader.test.ts +0 -35
  73. package/src/lib/loader.ts +0 -215
  74. package/src/lib/neon-api-real.test.ts +0 -72
  75. package/src/lib/neon-api-real.ts +0 -1123
  76. package/src/lib/neon-api.ts +0 -356
  77. package/src/lib/patterns.test.ts +0 -80
  78. package/src/lib/patterns.ts +0 -98
  79. package/src/lib/schema.test.ts +0 -88
  80. package/src/lib/schema.ts +0 -252
  81. package/src/lib/test-utils.ts +0 -83
  82. package/src/lib/types.ts +0 -268
  83. package/src/lib/wrap-neon-error.test.ts +0 -145
  84. package/src/lib/wrap-neon-error.ts +0 -204
  85. package/src/v1.test.ts +0 -33
  86. package/src/v1.ts +0 -148
  87. package/tsconfig.json +0 -4
  88. package/tsdown.config.ts +0 -19
  89. package/vitest.config.ts +0 -19
  90. package/vitest.e2e.config.ts +0 -29
@@ -0,0 +1,582 @@
1
+ import { ErrorCode, PlatformError } from "./errors.js";
2
+ import { formatSuspendTimeout, parseSuspendTimeout } from "./duration.js";
3
+ import { wrapNeonError } from "./wrap-neon-error.js";
4
+ import { z } from "zod";
5
+ import { EndpointType, createApiClient } from "@neondatabase/api-client";
6
+ //#region src/lib/neon-api-real.ts
7
+ const DEFAULT_NEON_API_BASE_URL = "https://console.neon.tech/api/v2";
8
+ const neonAuthResponseSchema = z.object({
9
+ auth_provider_project_id: z.string(),
10
+ pub_client_key: z.string().optional(),
11
+ secret_server_key: z.string().optional(),
12
+ jwks_url: z.string(),
13
+ base_url: z.string().optional()
14
+ });
15
+ const bucketSchema = z.object({
16
+ name: z.string(),
17
+ access_level: z.string().optional()
18
+ });
19
+ const bucketResponseSchema = z.object({ bucket: bucketSchema });
20
+ const bucketsListResponseSchema = z.object({ buckets: z.array(bucketSchema) });
21
+ const functionDeploymentSchema = z.object({
22
+ id: z.number(),
23
+ status: z.string()
24
+ });
25
+ const neonFunctionSchema = z.object({
26
+ id: z.string(),
27
+ slug: z.string(),
28
+ name: z.string(),
29
+ invocation_url: z.string(),
30
+ active_deployment: functionDeploymentSchema.optional()
31
+ });
32
+ const functionResponseSchema = z.object({ function: neonFunctionSchema });
33
+ const functionsListResponseSchema = z.object({ functions: z.array(neonFunctionSchema) });
34
+ const functionDeploymentResponseSchema = z.object({ deployment: functionDeploymentSchema });
35
+ /**
36
+ * Adapt `@neondatabase/api-client` to the narrow {@link NeonApi} façade used by the rest of
37
+ * this package. Constructs are restricted to whole-object read/write of just the fields we
38
+ * model in {@link Config}; anything else stays untouched on the remote.
39
+ */
40
+ function createRealNeonApi(options) {
41
+ if (!options.apiKey || options.apiKey.trim() === "") throw new PlatformError(ErrorCode.MissingApiKey, ["createRealNeonApi requires a non-empty `apiKey`.", "Generate one at https://console.neon.tech/app/settings/api-keys and pass it as { apiKey: process.env.NEON_API_KEY }."].join(" "));
42
+ return new RealNeonApi(createApiClient({
43
+ apiKey: options.apiKey,
44
+ ...options.baseUrl ? { baseURL: options.baseUrl } : {}
45
+ }), {
46
+ maxAttempts: options.retryOnLocked?.maxAttempts ?? 12,
47
+ initialDelayMs: options.retryOnLocked?.initialDelayMs ?? 250,
48
+ maxDelayMs: options.retryOnLocked?.maxDelayMs ?? 5e3
49
+ }, {
50
+ apiKey: options.apiKey,
51
+ baseUrl: options.baseUrl ?? DEFAULT_NEON_API_BASE_URL
52
+ });
53
+ }
54
+ /**
55
+ * Retry a function whenever it throws an HTTP 423 (Locked) — Neon's signal that a prior
56
+ * mutation on the same resource is still in flight. Uses exponential backoff capped at
57
+ * `maxDelayMs`. Any other error (and the last attempt) propagates.
58
+ *
59
+ * Exported only for tests; production callers go through the wrapped {@link NeonApi}.
60
+ */
61
+ async function retryOnLocked(fn, config) {
62
+ let delay = config.initialDelayMs;
63
+ let lastError;
64
+ for (let attempt = 1; attempt <= config.maxAttempts; attempt++) try {
65
+ return await fn();
66
+ } catch (err) {
67
+ lastError = err;
68
+ if (readHttpStatusFromError(err) !== 423 || attempt === config.maxAttempts) throw err;
69
+ await sleep(delay);
70
+ delay = Math.min(delay * 2, config.maxDelayMs);
71
+ }
72
+ throw lastError;
73
+ }
74
+ function readHttpStatusFromError(err) {
75
+ if (err === null || typeof err !== "object") return void 0;
76
+ const response = err.response;
77
+ if (response === null || typeof response !== "object") return void 0;
78
+ const status = response.status;
79
+ return typeof status === "number" ? status : void 0;
80
+ }
81
+ function sleep(ms) {
82
+ return new Promise((resolve) => setTimeout(resolve, ms));
83
+ }
84
+ var RealNeonApi = class {
85
+ client;
86
+ retryConfig;
87
+ restConfig;
88
+ constructor(client, retryConfig, restConfig) {
89
+ this.client = client;
90
+ this.retryConfig = retryConfig;
91
+ this.restConfig = restConfig;
92
+ }
93
+ retry(fn) {
94
+ return retryOnLocked(fn, this.retryConfig);
95
+ }
96
+ async call(op, fn, options = {}) {
97
+ try {
98
+ return options.mutating ? await this.retry(fn) : await fn();
99
+ } catch (err) {
100
+ throw wrapNeonError(err, options.projectId ? {
101
+ op,
102
+ projectId: options.projectId
103
+ } : { op });
104
+ }
105
+ }
106
+ async listProjects(filter) {
107
+ return this.call(filter.orgId ? `listProjects(org=${filter.orgId})` : "listProjects", async () => {
108
+ const projects = [];
109
+ let cursor;
110
+ while (true) {
111
+ const res = await this.client.listProjects({
112
+ ...filter.orgId ? { org_id: filter.orgId } : {},
113
+ ...cursor ? { cursor } : {},
114
+ limit: 100
115
+ });
116
+ projects.push(...res.data.projects);
117
+ const next = res.data.pagination?.next;
118
+ if (!next || next === cursor) break;
119
+ cursor = next;
120
+ }
121
+ return projects.map(projectToSnapshot);
122
+ });
123
+ }
124
+ async getProject(projectId) {
125
+ return this.call(`getProject(${projectId})`, async () => {
126
+ return projectToSnapshot((await this.client.getProject(projectId)).data.project);
127
+ }, { projectId });
128
+ }
129
+ async createProject(input) {
130
+ const body = { project: {
131
+ name: input.name,
132
+ region_id: input.regionId,
133
+ ...input.pgVersion !== void 0 ? { pg_version: input.pgVersion } : {},
134
+ ...input.orgId ? { org_id: input.orgId } : {},
135
+ ...input.defaultEndpointSettings ? { default_endpoint_settings: computeSettingsToDefaults(input.defaultEndpointSettings) } : {},
136
+ ...input.defaultBranchName ? { branch: { name: input.defaultBranchName } } : {}
137
+ } };
138
+ return this.call(`createProject(${input.name})`, async () => {
139
+ return projectToSnapshot((await this.client.createProject(body)).data.project);
140
+ }, { mutating: true });
141
+ }
142
+ async updateProject(projectId, input) {
143
+ const body = { project: {
144
+ ...input.name !== void 0 ? { name: input.name } : {},
145
+ ...input.defaultEndpointSettings ? { default_endpoint_settings: computeSettingsToDefaults(input.defaultEndpointSettings) } : {}
146
+ } };
147
+ return this.call(`updateProject(${projectId})`, async () => {
148
+ return projectToSnapshot((await this.client.updateProject(projectId, body)).data.project);
149
+ }, {
150
+ projectId,
151
+ mutating: true
152
+ });
153
+ }
154
+ async listBranches(projectId) {
155
+ return this.call(`listBranches(${projectId})`, async () => {
156
+ const branches = [];
157
+ let cursor;
158
+ while (true) {
159
+ const res = await this.client.listProjectBranches({
160
+ projectId,
161
+ limit: 100,
162
+ ...cursor ? { cursor } : {}
163
+ });
164
+ branches.push(...res.data.branches);
165
+ const next = res.data.pagination?.next;
166
+ if (!next || next === cursor) break;
167
+ cursor = next;
168
+ }
169
+ return branches.map(branchToSnapshot);
170
+ }, { projectId });
171
+ }
172
+ async createBranch(projectId, input) {
173
+ const endpointOptions = input.computeSettings ? {
174
+ type: EndpointType.ReadWrite,
175
+ ...computeSettingsToEndpointOptions(input.computeSettings)
176
+ } : { type: EndpointType.ReadWrite };
177
+ const body = {
178
+ branch: {
179
+ name: input.name,
180
+ ...input.parentId ? { parent_id: input.parentId } : {},
181
+ ...input.expiresAt ? { expires_at: input.expiresAt } : {},
182
+ ...input.protected !== void 0 ? { protected: input.protected } : {}
183
+ },
184
+ endpoints: [endpointOptions]
185
+ };
186
+ return this.call(`createBranch(${projectId}/${input.name})`, async () => {
187
+ const res = await this.client.createProjectBranch(projectId, body);
188
+ return {
189
+ branch: branchToSnapshot(res.data.branch),
190
+ endpoints: (res.data.endpoints ?? []).map(endpointToSnapshot)
191
+ };
192
+ }, {
193
+ projectId,
194
+ mutating: true
195
+ });
196
+ }
197
+ async updateBranch(projectId, branchId, input) {
198
+ const branch = {};
199
+ if (input.name !== void 0) branch.name = input.name;
200
+ if (input.expiresAt !== void 0) branch.expires_at = input.expiresAt;
201
+ if (input.protected !== void 0) branch.protected = input.protected;
202
+ return this.call(`updateBranch(${projectId}/${branchId})`, async () => {
203
+ return branchToSnapshot((await this.client.updateProjectBranch(projectId, branchId, { branch })).data.branch);
204
+ }, {
205
+ projectId,
206
+ mutating: true
207
+ });
208
+ }
209
+ async listEndpoints(projectId) {
210
+ return this.call(`listEndpoints(${projectId})`, async () => {
211
+ return (await this.client.listProjectEndpoints(projectId)).data.endpoints.map(endpointToSnapshot);
212
+ }, { projectId });
213
+ }
214
+ async updateEndpoint(projectId, endpointId, settings) {
215
+ const endpoint = computeSettingsToEndpointOptions(settings);
216
+ return this.call(`updateEndpoint(${projectId}/${endpointId})`, async () => {
217
+ return endpointToSnapshot((await this.client.updateProjectEndpoint(projectId, endpointId, { endpoint })).data.endpoint);
218
+ }, {
219
+ projectId,
220
+ mutating: true
221
+ });
222
+ }
223
+ async listBranchRoles(projectId, branchId) {
224
+ return this.call(`listBranchRoles(${projectId}/${branchId})`, async () => {
225
+ return (await this.client.listProjectBranchRoles(projectId, branchId)).data.roles.map(roleToSnapshot);
226
+ }, { projectId });
227
+ }
228
+ async listBranchDatabases(projectId, branchId) {
229
+ return this.call(`listBranchDatabases(${projectId}/${branchId})`, async () => {
230
+ return (await this.client.listProjectBranchDatabases(projectId, branchId)).data.databases.map(databaseToSnapshot);
231
+ }, { projectId });
232
+ }
233
+ async getConnectionUri(projectId, input) {
234
+ const op = `getConnectionUri(${projectId}/${input.databaseName}@${input.roleName}${input.pooled ? " pooled" : ""})`;
235
+ const pooled = input.pooled === true;
236
+ return this.call(op, async () => {
237
+ return { uri: (await this.client.getConnectionUri({
238
+ projectId,
239
+ database_name: input.databaseName,
240
+ role_name: input.roleName,
241
+ ...input.branchId ? { branch_id: input.branchId } : {},
242
+ ...input.endpointId ? { endpoint_id: input.endpointId } : {},
243
+ pooled
244
+ })).data.uri };
245
+ }, { projectId });
246
+ }
247
+ async getNeonAuth(projectId, branchId) {
248
+ try {
249
+ return await this.call(`getNeonAuth(${projectId}/${branchId})`, async () => {
250
+ return neonAuthResponseToSnapshot((await this.client.getNeonAuth(projectId, branchId)).data);
251
+ }, { projectId });
252
+ } catch (err) {
253
+ if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return null;
254
+ throw err;
255
+ }
256
+ }
257
+ async enableNeonAuth(projectId, branchId, input = {}) {
258
+ try {
259
+ return await this.call(`enableNeonAuth(${projectId}/${branchId})`, async () => {
260
+ const data = await this.postJson(`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/auth`, createNeonAuthRestInput(input));
261
+ return neonAuthResponseToSnapshot(neonAuthResponseSchema.parse(data));
262
+ }, {
263
+ projectId,
264
+ mutating: true
265
+ });
266
+ } catch (err) {
267
+ if (err instanceof PlatformError && err.code === ErrorCode.Conflict) {
268
+ const existing = await this.getNeonAuth(projectId, branchId);
269
+ if (existing) return existing;
270
+ }
271
+ throw err;
272
+ }
273
+ }
274
+ async postJson(path, body) {
275
+ return this.request("POST", path, {
276
+ headers: { "Content-Type": "application/json" },
277
+ body: JSON.stringify(body)
278
+ });
279
+ }
280
+ async getJson(path) {
281
+ return this.request("GET", path);
282
+ }
283
+ async deleteJson(path) {
284
+ return this.request("DELETE", path);
285
+ }
286
+ /**
287
+ * Upload a built function bundle via `multipart/form-data` to the deploy endpoint.
288
+ * Sends the bundle as the `file` field plus the deploy params Neon requires.
289
+ */
290
+ async postMultipart(path, input) {
291
+ const form = new FormData();
292
+ form.set("file", new Blob([input.bundle], { type: "application/zip" }), "bundle.zip");
293
+ form.set("memory_mib", String(input.memoryMib));
294
+ form.set("concurrency", "1");
295
+ form.set("runtime", input.runtime);
296
+ for (const [key, value] of Object.entries(input.environment)) form.set(`environment[${key}]`, value);
297
+ return this.request("POST", path, { body: form });
298
+ }
299
+ async request(method, path, init = {}) {
300
+ const url = `${this.restConfig.baseUrl.replace(/\/+$/, "")}${path}`;
301
+ const res = await fetch(url, {
302
+ method,
303
+ headers: {
304
+ Authorization: `Bearer ${this.restConfig.apiKey}`,
305
+ ...init.headers ?? {}
306
+ },
307
+ ...init.body !== void 0 ? { body: init.body } : {}
308
+ });
309
+ const data = await readJsonBody(res);
310
+ if (!res.ok) throw { response: {
311
+ status: res.status,
312
+ data
313
+ } };
314
+ return data;
315
+ }
316
+ async getNeonDataApi(projectId, branchId, databaseName) {
317
+ try {
318
+ return await this.call(`getNeonDataApi(${projectId}/${branchId}/${databaseName})`, async () => {
319
+ return { url: (await this.client.getProjectBranchDataApi(projectId, branchId, databaseName)).data.url };
320
+ }, { projectId });
321
+ } catch (err) {
322
+ if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return null;
323
+ throw err;
324
+ }
325
+ }
326
+ async enableProjectBranchDataApi(projectId, branchId, databaseName) {
327
+ try {
328
+ return await this.call(`enableProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`, async () => {
329
+ return { url: (await this.client.createProjectBranchDataApi(projectId, branchId, databaseName, {})).data.url };
330
+ }, {
331
+ projectId,
332
+ mutating: true
333
+ });
334
+ } catch (err) {
335
+ if (err instanceof PlatformError && err.code === ErrorCode.Conflict) {
336
+ const existing = await this.getNeonDataApi(projectId, branchId, databaseName);
337
+ if (existing) return existing;
338
+ }
339
+ throw err;
340
+ }
341
+ }
342
+ async listBranchBuckets(projectId, branchId) {
343
+ return this.call(`listBranchBuckets(${projectId}/${branchId})`, async () => {
344
+ const data = await this.getJson(branchPreviewPath(projectId, branchId, "buckets"));
345
+ return bucketsListResponseSchema.parse(data).buckets.map(bucketToSnapshot);
346
+ }, { projectId });
347
+ }
348
+ async createBranchBucket(projectId, branchId, input) {
349
+ return this.call(`createBranchBucket(${projectId}/${branchId}/${input.name})`, async () => {
350
+ const data = await this.postJson(branchPreviewPath(projectId, branchId, "buckets"), {
351
+ name: input.name,
352
+ ...input.accessLevel ? { access_level: input.accessLevel } : {}
353
+ });
354
+ return bucketToSnapshot(bucketResponseSchema.parse(data).bucket);
355
+ }, {
356
+ projectId,
357
+ mutating: true
358
+ });
359
+ }
360
+ async deleteBranchBucket(projectId, branchId, bucketName) {
361
+ await this.call(`deleteBranchBucket(${projectId}/${branchId}/${bucketName})`, async () => {
362
+ await this.deleteJson(`${branchPreviewPath(projectId, branchId, "buckets")}/${encodeURIComponent(bucketName)}`);
363
+ }, {
364
+ projectId,
365
+ mutating: true
366
+ });
367
+ }
368
+ async listBranchFunctions(projectId, branchId) {
369
+ return this.call(`listBranchFunctions(${projectId}/${branchId})`, async () => {
370
+ const data = await this.getJson(branchPreviewPath(projectId, branchId, "functions"));
371
+ return functionsListResponseSchema.parse(data).functions.map(functionToSnapshot);
372
+ }, { projectId });
373
+ }
374
+ async createBranchFunction(projectId, branchId, input) {
375
+ return this.call(`createBranchFunction(${projectId}/${branchId}/${input.slug})`, async () => {
376
+ const data = await this.postJson(branchPreviewPath(projectId, branchId, "functions"), {
377
+ slug: input.slug,
378
+ name: input.name
379
+ });
380
+ return functionToSnapshot(functionResponseSchema.parse(data).function);
381
+ }, {
382
+ projectId,
383
+ mutating: true
384
+ });
385
+ }
386
+ async deleteBranchFunction(projectId, branchId, slug) {
387
+ await this.call(`deleteBranchFunction(${projectId}/${branchId}/${slug})`, async () => {
388
+ await this.deleteJson(`${branchPreviewPath(projectId, branchId, "functions")}/${encodeURIComponent(slug)}`);
389
+ }, {
390
+ projectId,
391
+ mutating: true
392
+ });
393
+ }
394
+ async deployBranchFunction(projectId, branchId, slug, input) {
395
+ return this.call(`deployBranchFunction(${projectId}/${branchId}/${slug})`, async () => {
396
+ const data = await this.postMultipart(`${branchPreviewPath(projectId, branchId, "functions")}/${encodeURIComponent(slug)}/deployments`, input);
397
+ return deploymentToSnapshot(functionDeploymentResponseSchema.parse(data).deployment);
398
+ }, {
399
+ projectId,
400
+ mutating: true
401
+ });
402
+ }
403
+ async getAiGatewayEnabled(projectId, branchId) {
404
+ try {
405
+ return await this.call(`getAiGatewayEnabled(${projectId}/${branchId})`, async () => {
406
+ return aiGatewayEnabledFromResponse(await this.getJson(aiGatewayPath(projectId, branchId)));
407
+ }, { projectId });
408
+ } catch (err) {
409
+ if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return false;
410
+ throw err;
411
+ }
412
+ }
413
+ async enableAiGateway(projectId, branchId) {
414
+ await this.call(`enableAiGateway(${projectId}/${branchId})`, async () => {
415
+ await this.postJson(aiGatewayPath(projectId, branchId), { enabled: true });
416
+ }, {
417
+ projectId,
418
+ mutating: true
419
+ });
420
+ }
421
+ async disableAiGateway(projectId, branchId) {
422
+ await this.call(`disableAiGateway(${projectId}/${branchId})`, async () => {
423
+ await this.deleteJson(aiGatewayPath(projectId, branchId));
424
+ }, {
425
+ projectId,
426
+ mutating: true
427
+ });
428
+ }
429
+ };
430
+ function branchPreviewPath(projectId, branchId, resource) {
431
+ return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/${resource}`;
432
+ }
433
+ function aiGatewayPath(projectId, branchId) {
434
+ return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/ai-gateway`;
435
+ }
436
+ function bucketToSnapshot(bucket) {
437
+ return {
438
+ name: bucket.name,
439
+ accessLevel: normalizeBucketAccessLevel(bucket.access_level)
440
+ };
441
+ }
442
+ /**
443
+ * The Neon API returns `access_level` as a free-form string (per the API guidelines:
444
+ * responses use plain strings, not enums). Map the known values onto our union and treat
445
+ * anything else as `private` — the safe default for an unrecognised access level.
446
+ */
447
+ function normalizeBucketAccessLevel(value) {
448
+ return value === "public_read" ? "public_read" : "private";
449
+ }
450
+ function functionToSnapshot(fn) {
451
+ const snapshot = {
452
+ id: fn.id,
453
+ slug: fn.slug,
454
+ name: fn.name,
455
+ invocationUrl: fn.invocation_url
456
+ };
457
+ if (fn.active_deployment) snapshot.activeDeploymentId = fn.active_deployment.id;
458
+ return snapshot;
459
+ }
460
+ function deploymentToSnapshot(deployment) {
461
+ return {
462
+ id: deployment.id,
463
+ status: normalizeDeploymentStatus(deployment.status)
464
+ };
465
+ }
466
+ function normalizeDeploymentStatus(value) {
467
+ switch (value) {
468
+ case "pending":
469
+ case "building":
470
+ case "completed":
471
+ case "failed": return value;
472
+ default: return "pending";
473
+ }
474
+ }
475
+ function aiGatewayEnabledFromResponse(data) {
476
+ if (data !== null && typeof data === "object" && "enabled" in data) return data.enabled === true;
477
+ return false;
478
+ }
479
+ function neonAuthResponseToSnapshot(data) {
480
+ const snapshot = {
481
+ projectId: data.auth_provider_project_id,
482
+ jwksUrl: data.jwks_url
483
+ };
484
+ if (data.pub_client_key !== void 0) snapshot.publishableClientKey = data.pub_client_key;
485
+ if (data.secret_server_key !== void 0) snapshot.secretServerKey = data.secret_server_key;
486
+ if (data.base_url) snapshot.baseUrl = data.base_url;
487
+ return snapshot;
488
+ }
489
+ function createNeonAuthRestInput(input) {
490
+ return {
491
+ auth_provider: "better_auth",
492
+ ...input.databaseName ? { database_name: input.databaseName } : {}
493
+ };
494
+ }
495
+ async function readJsonBody(res) {
496
+ const text = await res.text();
497
+ if (text.trim() === "") return {};
498
+ return JSON.parse(text);
499
+ }
500
+ function projectToSnapshot(project) {
501
+ const defaults = project.default_endpoint_settings;
502
+ const snapshot = {
503
+ id: project.id,
504
+ name: project.name,
505
+ regionId: project.region_id,
506
+ pgVersion: project.pg_version
507
+ };
508
+ if (project.org_id) snapshot.orgId = project.org_id;
509
+ if (defaults) {
510
+ const compute = defaultsToComputeSettings(defaults);
511
+ if (compute) snapshot.defaultEndpointSettings = compute;
512
+ }
513
+ return snapshot;
514
+ }
515
+ function branchToSnapshot(branch) {
516
+ const snapshot = {
517
+ id: branch.id,
518
+ name: branch.name,
519
+ isDefault: branch.default,
520
+ protected: branch.protected === true
521
+ };
522
+ if (branch.parent_id) snapshot.parentId = branch.parent_id;
523
+ if (branch.expires_at) snapshot.expiresAt = branch.expires_at;
524
+ return snapshot;
525
+ }
526
+ function endpointToSnapshot(endpoint) {
527
+ return {
528
+ id: endpoint.id,
529
+ branchId: endpoint.branch_id,
530
+ type: endpoint.type === EndpointType.ReadOnly ? "read_only" : "read_write",
531
+ autoscalingLimitMinCu: endpoint.autoscaling_limit_min_cu,
532
+ autoscalingLimitMaxCu: endpoint.autoscaling_limit_max_cu,
533
+ suspendTimeout: formatSuspendTimeout(endpoint.suspend_timeout_seconds)
534
+ };
535
+ }
536
+ function roleToSnapshot(role) {
537
+ return {
538
+ name: role.name,
539
+ branchId: role.branch_id,
540
+ protected: role.protected ?? false
541
+ };
542
+ }
543
+ function databaseToSnapshot(database) {
544
+ return {
545
+ name: database.name,
546
+ branchId: database.branch_id,
547
+ ownerName: database.owner_name
548
+ };
549
+ }
550
+ function computeSettingsToDefaults(settings) {
551
+ const out = {};
552
+ if (settings.autoscalingLimitMinCu !== void 0) out.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;
553
+ if (settings.autoscalingLimitMaxCu !== void 0) out.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;
554
+ if (settings.suspendTimeout !== void 0) {
555
+ const parsed = parseSuspendTimeout(settings.suspendTimeout);
556
+ if ("error" in parsed) throw new PlatformError(ErrorCode.InvalidConfig, `Invalid suspendTimeout: ${parsed.error}`);
557
+ out.suspend_timeout_seconds = parsed.seconds;
558
+ }
559
+ return out;
560
+ }
561
+ function computeSettingsToEndpointOptions(settings) {
562
+ const out = {};
563
+ if (settings.autoscalingLimitMinCu !== void 0) out.autoscaling_limit_min_cu = settings.autoscalingLimitMinCu;
564
+ if (settings.autoscalingLimitMaxCu !== void 0) out.autoscaling_limit_max_cu = settings.autoscalingLimitMaxCu;
565
+ if (settings.suspendTimeout !== void 0) {
566
+ const parsed = parseSuspendTimeout(settings.suspendTimeout);
567
+ if ("error" in parsed) throw new PlatformError(ErrorCode.InvalidConfig, `Invalid suspendTimeout: ${parsed.error}`);
568
+ out.suspend_timeout_seconds = parsed.seconds;
569
+ }
570
+ return out;
571
+ }
572
+ function defaultsToComputeSettings(defaults) {
573
+ const out = {};
574
+ if (defaults.autoscaling_limit_min_cu !== void 0) out.autoscalingLimitMinCu = defaults.autoscaling_limit_min_cu;
575
+ if (defaults.autoscaling_limit_max_cu !== void 0) out.autoscalingLimitMaxCu = defaults.autoscaling_limit_max_cu;
576
+ if (defaults.suspend_timeout_seconds !== void 0) out.suspendTimeout = formatSuspendTimeout(defaults.suspend_timeout_seconds);
577
+ return Object.keys(out).length > 0 ? out : void 0;
578
+ }
579
+ //#endregion
580
+ export { createNeonAuthRestInput, createRealNeonApi, retryOnLocked };
581
+
582
+ //# sourceMappingURL=neon-api-real.js.map