alchemy 0.12.6 → 0.12.8

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 (80) hide show
  1. package/lib/cloudflare/ai-gateway.d.ts.map +1 -1
  2. package/lib/cloudflare/ai-gateway.js +1 -2
  3. package/lib/cloudflare/ai-gateway.js.map +1 -1
  4. package/lib/cloudflare/bindings.d.ts +2 -1
  5. package/lib/cloudflare/bindings.d.ts.map +1 -1
  6. package/lib/cloudflare/bound.d.ts +2 -1
  7. package/lib/cloudflare/bound.d.ts.map +1 -1
  8. package/lib/cloudflare/bucket.d.ts +8 -2
  9. package/lib/cloudflare/bucket.d.ts.map +1 -1
  10. package/lib/cloudflare/bucket.js +1 -1
  11. package/lib/cloudflare/bucket.js.map +1 -1
  12. package/lib/cloudflare/d1-database.d.ts +8 -2
  13. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  14. package/lib/cloudflare/d1-database.js +2 -2
  15. package/lib/cloudflare/d1-database.js.map +1 -1
  16. package/lib/cloudflare/event-source.d.ts +26 -0
  17. package/lib/cloudflare/event-source.d.ts.map +1 -0
  18. package/lib/cloudflare/event-source.js +9 -0
  19. package/lib/cloudflare/event-source.js.map +1 -0
  20. package/lib/cloudflare/hyperdrive.d.ts +216 -0
  21. package/lib/cloudflare/hyperdrive.d.ts.map +1 -0
  22. package/lib/cloudflare/hyperdrive.js +184 -0
  23. package/lib/cloudflare/hyperdrive.js.map +1 -0
  24. package/lib/cloudflare/index.d.ts +2 -0
  25. package/lib/cloudflare/index.d.ts.map +1 -1
  26. package/lib/cloudflare/index.js +2 -0
  27. package/lib/cloudflare/index.js.map +1 -1
  28. package/lib/cloudflare/queue-consumer.d.ts +162 -0
  29. package/lib/cloudflare/queue-consumer.d.ts.map +1 -0
  30. package/lib/cloudflare/queue-consumer.js +197 -0
  31. package/lib/cloudflare/queue-consumer.js.map +1 -0
  32. package/lib/cloudflare/queue.d.ts +15 -3
  33. package/lib/cloudflare/queue.d.ts.map +1 -1
  34. package/lib/cloudflare/queue.js +8 -2
  35. package/lib/cloudflare/queue.js.map +1 -1
  36. package/lib/cloudflare/website.d.ts.map +1 -1
  37. package/lib/cloudflare/website.js +8 -10
  38. package/lib/cloudflare/website.js.map +1 -1
  39. package/lib/cloudflare/worker.d.ts +13 -2
  40. package/lib/cloudflare/worker.d.ts.map +1 -1
  41. package/lib/cloudflare/worker.js +75 -3
  42. package/lib/cloudflare/worker.js.map +1 -1
  43. package/lib/cloudflare/wrangler.json.d.ts +13 -3
  44. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  45. package/lib/cloudflare/wrangler.json.js +27 -5
  46. package/lib/cloudflare/wrangler.json.js.map +1 -1
  47. package/lib/neon/api-error.d.ts +31 -0
  48. package/lib/neon/api-error.d.ts.map +1 -0
  49. package/lib/neon/api-error.js +73 -0
  50. package/lib/neon/api-error.js.map +1 -0
  51. package/lib/neon/api.d.ts +70 -0
  52. package/lib/neon/api.d.ts.map +1 -0
  53. package/lib/neon/api.js +128 -0
  54. package/lib/neon/api.js.map +1 -0
  55. package/lib/neon/index.d.ts +4 -0
  56. package/lib/neon/index.d.ts.map +1 -0
  57. package/lib/neon/index.js +4 -0
  58. package/lib/neon/index.js.map +1 -0
  59. package/lib/neon/project.d.ts +345 -0
  60. package/lib/neon/project.d.ts.map +1 -0
  61. package/lib/neon/project.js +305 -0
  62. package/lib/neon/project.js.map +1 -0
  63. package/package.json +2 -1
  64. package/src/cloudflare/ai-gateway.ts +0 -1
  65. package/src/cloudflare/bindings.ts +4 -2
  66. package/src/cloudflare/bound.ts +20 -17
  67. package/src/cloudflare/bucket.ts +9 -2
  68. package/src/cloudflare/d1-database.ts +10 -3
  69. package/src/cloudflare/event-source.ts +33 -0
  70. package/src/cloudflare/hyperdrive.ts +375 -0
  71. package/src/cloudflare/index.ts +2 -0
  72. package/src/cloudflare/queue-consumer.ts +433 -0
  73. package/src/cloudflare/queue.ts +83 -63
  74. package/src/cloudflare/website.ts +9 -13
  75. package/src/cloudflare/worker.ts +109 -5
  76. package/src/cloudflare/wrangler.json.ts +52 -8
  77. package/src/neon/api-error.ts +81 -0
  78. package/src/neon/api.ts +178 -0
  79. package/src/neon/index.ts +3 -0
  80. package/src/neon/project.ts +870 -0
@@ -0,0 +1,870 @@
1
+ import { alchemy } from "../alchemy.js";
2
+ import type { Context } from "../context.js";
3
+ import { Resource } from "../resource.js";
4
+ import type { Secret } from "../secret.js";
5
+ import { handleApiError } from "./api-error.js";
6
+ import { createNeonApi, type NeonApiOptions } from "./api.js";
7
+
8
+ /**
9
+ * A Neon region where projects can be provisioned
10
+ */
11
+ export type NeonRegion =
12
+ | "aws-us-east-1"
13
+ | "aws-us-east-2"
14
+ | "aws-us-west-2"
15
+ | "aws-eu-central-1"
16
+ | "aws-eu-west-2"
17
+ | "aws-ap-southeast-1"
18
+ | "aws-ap-southeast-2"
19
+ | "aws-sa-east-1"
20
+ | "azure-eastus2"
21
+ | "azure-westus3"
22
+ | "azure-gwc";
23
+
24
+ /**
25
+ * Properties for creating or updating a Neon project
26
+ */
27
+ export interface NeonProjectProps extends NeonApiOptions {
28
+ /**
29
+ * Name of the project
30
+ */
31
+ name: string;
32
+
33
+ /**
34
+ * Region where the project will be provisioned
35
+ * @default "aws-us-east-1"
36
+ */
37
+ region_id?: NeonRegion;
38
+
39
+ /**
40
+ * PostgreSQL version to use
41
+ * @default 15
42
+ */
43
+ pg_version?: 14 | 15 | 16;
44
+
45
+ /**
46
+ * Whether to create a default branch and endpoint
47
+ * @default true
48
+ */
49
+ default_endpoint?: boolean;
50
+
51
+ /**
52
+ * Default branch name
53
+ * @default "main"
54
+ */
55
+ default_branch_name?: string;
56
+
57
+ /**
58
+ * Existing project ID to update
59
+ * Used internally during update operations
60
+ * @internal
61
+ */
62
+ existing_project_id?: string;
63
+ }
64
+
65
+ /**
66
+ * A Neon database
67
+ */
68
+ export interface NeonDatabase {
69
+ /**
70
+ * Database ID
71
+ */
72
+ id: number;
73
+
74
+ /**
75
+ * ID of the branch this database belongs to
76
+ */
77
+ branch_id: string;
78
+
79
+ /**
80
+ * Database name
81
+ */
82
+ name: string;
83
+
84
+ /**
85
+ * Name of the database owner role
86
+ */
87
+ owner_name: string;
88
+
89
+ /**
90
+ * Time at which the database was created
91
+ */
92
+ created_at: string;
93
+
94
+ /**
95
+ * Time at which the database was last updated
96
+ */
97
+ updated_at: string;
98
+ }
99
+
100
+ /**
101
+ * A Neon database role
102
+ */
103
+ export interface NeonRole {
104
+ /**
105
+ * ID of the branch this role belongs to
106
+ */
107
+ branch_id: string;
108
+
109
+ /**
110
+ * Role name
111
+ */
112
+ name: string;
113
+
114
+ /**
115
+ * Role password (only included during creation)
116
+ */
117
+ password?: string;
118
+
119
+ /**
120
+ * Whether this role is protected from deletion
121
+ */
122
+ protected: boolean;
123
+
124
+ /**
125
+ * Time at which the role was created
126
+ */
127
+ created_at: string;
128
+
129
+ /**
130
+ * Time at which the role was last updated
131
+ */
132
+ updated_at: string;
133
+ }
134
+
135
+ /**
136
+ * A Neon branch
137
+ */
138
+ export interface NeonBranch {
139
+ /**
140
+ * Branch ID
141
+ */
142
+ id: string;
143
+
144
+ /**
145
+ * ID of the project this branch belongs to
146
+ */
147
+ project_id: string;
148
+
149
+ /**
150
+ * Branch name
151
+ */
152
+ name: string;
153
+
154
+ /**
155
+ * Current state of the branch
156
+ */
157
+ current_state: string;
158
+
159
+ /**
160
+ * Pending state of the branch
161
+ */
162
+ pending_state: string;
163
+
164
+ /**
165
+ * Time at which the branch was created
166
+ */
167
+ created_at: string;
168
+
169
+ /**
170
+ * Time at which the branch was last updated
171
+ */
172
+ updated_at: string;
173
+ }
174
+
175
+ /**
176
+ * A Neon compute endpoint
177
+ */
178
+ export interface NeonEndpoint {
179
+ /**
180
+ * Endpoint ID
181
+ */
182
+ id: string;
183
+
184
+ /**
185
+ * Host for connecting to this endpoint
186
+ */
187
+ host: string;
188
+
189
+ /**
190
+ * ID of the project this endpoint belongs to
191
+ */
192
+ project_id: string;
193
+
194
+ /**
195
+ * ID of the branch this endpoint belongs to
196
+ */
197
+ branch_id: string;
198
+
199
+ /**
200
+ * Endpoint type (read_write, read_only)
201
+ */
202
+ type: string;
203
+
204
+ /**
205
+ * Current state of the endpoint
206
+ */
207
+ current_state: string;
208
+
209
+ /**
210
+ * Pending state of the endpoint
211
+ */
212
+ pending_state: string;
213
+
214
+ /**
215
+ * Region ID where this endpoint is provisioned
216
+ */
217
+ region_id: string;
218
+
219
+ /**
220
+ * Minimum compute units for autoscaling
221
+ */
222
+ autoscaling_limit_min_cu: number;
223
+
224
+ /**
225
+ * Maximum compute units for autoscaling
226
+ */
227
+ autoscaling_limit_max_cu: number;
228
+
229
+ /**
230
+ * Whether connection pooler is enabled
231
+ */
232
+ pooler_enabled: boolean;
233
+
234
+ /**
235
+ * Connection pooler mode
236
+ */
237
+ pooler_mode: string;
238
+
239
+ /**
240
+ * Whether this endpoint is disabled
241
+ */
242
+ disabled: boolean;
243
+
244
+ /**
245
+ * Whether passwordless access is enabled
246
+ */
247
+ passwordless_access: boolean;
248
+
249
+ /**
250
+ * Time at which the endpoint was created
251
+ */
252
+ created_at: string;
253
+
254
+ /**
255
+ * Time at which the endpoint was last updated
256
+ */
257
+ updated_at: string;
258
+
259
+ /**
260
+ * Proxy host for this endpoint
261
+ */
262
+ proxy_host: string;
263
+
264
+ /**
265
+ * Endpoint settings
266
+ */
267
+ settings: {
268
+ /**
269
+ * PostgreSQL settings
270
+ */
271
+ pg_settings: Record<string, any>;
272
+ };
273
+ }
274
+
275
+ /**
276
+ * A Neon connection URI
277
+ */
278
+ export interface NeonConnectionUri {
279
+ /**
280
+ * Connection URI string
281
+ */
282
+ connection_uri: Secret;
283
+
284
+ /**
285
+ * Connection parameters
286
+ */
287
+ connection_parameters: {
288
+ database: string;
289
+ host: string;
290
+ port: number;
291
+ user: string;
292
+ password: Secret;
293
+ };
294
+ }
295
+
296
+ /**
297
+ * A Neon operation
298
+ */
299
+ export interface NeonOperation {
300
+ /**
301
+ * Operation ID
302
+ */
303
+ id: string;
304
+
305
+ /**
306
+ * ID of the project this operation belongs to
307
+ */
308
+ project_id: string;
309
+
310
+ /**
311
+ * ID of the branch this operation affects, if applicable
312
+ */
313
+ branch_id?: string;
314
+
315
+ /**
316
+ * ID of the endpoint this operation affects, if applicable
317
+ */
318
+ endpoint_id?: string;
319
+
320
+ /**
321
+ * Action being performed
322
+ */
323
+ action: string;
324
+
325
+ /**
326
+ * Current status of the operation
327
+ */
328
+ status: string;
329
+
330
+ /**
331
+ * Number of failures encountered
332
+ */
333
+ failures_count: number;
334
+
335
+ /**
336
+ * Time at which the operation was created
337
+ */
338
+ created_at: string;
339
+
340
+ /**
341
+ * Time at which the operation was last updated
342
+ */
343
+ updated_at: string;
344
+ }
345
+
346
+ /**
347
+ * API response structure for Neon projects
348
+ */
349
+ interface NeonApiResponse {
350
+ project: {
351
+ id: string;
352
+ name: string;
353
+ region_id: string;
354
+ pg_version: number;
355
+ created_at: string;
356
+ updated_at: string;
357
+ proxy_host?: string;
358
+ [key: string]: any;
359
+ };
360
+ connection_uris?: Array<{
361
+ connection_uri: string;
362
+ connection_parameters: {
363
+ database: string;
364
+ host: string;
365
+ port: number;
366
+ user: string;
367
+ password: string;
368
+ };
369
+ }>;
370
+ roles?: Array<{
371
+ branch_id: string;
372
+ name: string;
373
+ password?: string;
374
+ protected: boolean;
375
+ created_at: string;
376
+ updated_at: string;
377
+ }>;
378
+ databases?: Array<{
379
+ id: number;
380
+ branch_id: string;
381
+ name: string;
382
+ owner_name: string;
383
+ created_at: string;
384
+ updated_at: string;
385
+ }>;
386
+ operations?: Array<{
387
+ id: string;
388
+ project_id: string;
389
+ branch_id?: string;
390
+ endpoint_id?: string;
391
+ action: string;
392
+ status: string;
393
+ failures_count: number;
394
+ created_at: string;
395
+ updated_at: string;
396
+ }>;
397
+ branch?: {
398
+ id: string;
399
+ project_id: string;
400
+ name: string;
401
+ current_state: string;
402
+ pending_state: string;
403
+ created_at: string;
404
+ updated_at: string;
405
+ };
406
+ endpoints?: Array<{
407
+ id: string;
408
+ host: string;
409
+ project_id: string;
410
+ branch_id: string;
411
+ type: string;
412
+ current_state: string;
413
+ pending_state: string;
414
+ region_id: string;
415
+ autoscaling_limit_min_cu: number;
416
+ autoscaling_limit_max_cu: number;
417
+ pooler_enabled: boolean;
418
+ pooler_mode: string;
419
+ disabled: boolean;
420
+ passwordless_access: boolean;
421
+ created_at: string;
422
+ updated_at: string;
423
+ proxy_host: string;
424
+ settings: {
425
+ pg_settings: Record<string, any>;
426
+ };
427
+ }>;
428
+ }
429
+
430
+ /**
431
+ * Output returned after Neon project creation/update
432
+ * IMPORTANT: The interface name MUST match the exported resource name
433
+ */
434
+ export interface NeonProject
435
+ extends Resource<"neon::Project">,
436
+ Omit<NeonProjectProps, "apiKey" | "existing_project_id"> {
437
+ /**
438
+ * The ID of the project
439
+ */
440
+ id: string;
441
+
442
+ /**
443
+ * Time at which the project was created
444
+ */
445
+ created_at: string;
446
+
447
+ /**
448
+ * Time at which the project was last updated
449
+ */
450
+ updated_at: string;
451
+
452
+ /**
453
+ * Hostname for proxy access
454
+ */
455
+ proxy_host?: string;
456
+
457
+ /**
458
+ * Connection URIs for the databases
459
+ */
460
+ connection_uris: [NeonConnectionUri, ...NeonConnectionUri[]];
461
+
462
+ /**
463
+ * Database roles created with the project
464
+ */
465
+ roles: [NeonRole, ...NeonRole[]];
466
+
467
+ /**
468
+ * Databases created with the project
469
+ */
470
+ databases?: [NeonDatabase, ...NeonDatabase[]];
471
+
472
+ /**
473
+ * Default branch information
474
+ */
475
+ branch?: NeonBranch;
476
+
477
+ /**
478
+ * Compute endpoints for the project
479
+ */
480
+ endpoints: [NeonEndpoint, ...NeonEndpoint[]];
481
+ }
482
+
483
+ /**
484
+ * Creates a Neon serverless PostgreSQL project.
485
+ *
486
+ * @example
487
+ * // Create a basic Neon project with default settings:
488
+ * const project = await NeonProject("my-project", {
489
+ * name: "My Project"
490
+ * });
491
+ *
492
+ * @example
493
+ * // Create a Neon project in a specific region with a specific PostgreSQL version:
494
+ * const euProject = await NeonProject("my-eu-project", {
495
+ * name: "My EU Project",
496
+ * region_id: "aws-eu-west-1",
497
+ * pg_version: 16,
498
+ * apiKey: alchemy.secret(process.env.NEON_API_KEY)
499
+ * });
500
+ *
501
+ * @example
502
+ * // Create a Neon project with a custom default branch name:
503
+ * const devProject = await NeonProject("dev-project", {
504
+ * name: "Development Project",
505
+ * default_branch_name: "development"
506
+ * });
507
+ */
508
+ export const NeonProject = Resource(
509
+ "neon::Project",
510
+ async function (
511
+ this: Context<NeonProject>,
512
+ id: string,
513
+ props: NeonProjectProps
514
+ ): Promise<NeonProject> {
515
+ const api = createNeonApi(props);
516
+ const projectId = props.existing_project_id || this.output?.id;
517
+
518
+ if (this.phase === "delete") {
519
+ try {
520
+ // Check if the project exists before attempting to delete
521
+ if (projectId) {
522
+ const deleteResponse = await api.delete(`/projects/${projectId}`);
523
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
524
+ await handleApiError(deleteResponse, "delete", "project", id);
525
+ }
526
+ }
527
+ } catch (error) {
528
+ console.error(`Error deleting Neon project ${id}:`, error);
529
+ throw error;
530
+ }
531
+ return this.destroy();
532
+ }
533
+
534
+ let response: NeonApiResponse;
535
+
536
+ try {
537
+ if (this.phase === "update" && projectId) {
538
+ // Update existing project
539
+ // Neon only allows updating the project name
540
+ const projectResponse = await api.patch(`/projects/${projectId}`, {
541
+ project: {
542
+ name: props.name,
543
+ },
544
+ });
545
+
546
+ if (!projectResponse.ok) {
547
+ await handleApiError(projectResponse, "update", "project", id);
548
+ }
549
+
550
+ const initialData = await projectResponse.json();
551
+
552
+ // Reify project properties to get complete data
553
+ response = await getProject(
554
+ api,
555
+ projectId,
556
+ initialData as Partial<NeonApiResponse>
557
+ );
558
+ } else {
559
+ // Check if a project with this ID already exists
560
+ if (projectId) {
561
+ const getResponse = await api.get(`/projects/${projectId}`);
562
+ if (getResponse.ok) {
563
+ // Project exists, update it
564
+ const projectResponse = await api.patch(`/projects/${projectId}`, {
565
+ project: {
566
+ name: props.name,
567
+ },
568
+ });
569
+
570
+ if (!projectResponse.ok) {
571
+ await handleApiError(projectResponse, "update", "project", id);
572
+ }
573
+
574
+ const initialData = await projectResponse.json();
575
+ // Reify project properties to get complete data
576
+ response = await getProject(
577
+ api,
578
+ projectId,
579
+ initialData as Partial<NeonApiResponse>
580
+ );
581
+ } else if (getResponse.status !== 404) {
582
+ // Unexpected error during GET check
583
+ await handleApiError(getResponse, "get", "project", id);
584
+ throw new Error("Failed to check if project exists");
585
+ } else {
586
+ // Project doesn't exist, create new
587
+ response = await createNewProject(api, props);
588
+ }
589
+ } else {
590
+ // No output ID, create new project
591
+ response = await createNewProject(api, props);
592
+ }
593
+ }
594
+
595
+ // Wait for any pending operations to complete
596
+ if (response.operations && response.operations.length > 0) {
597
+ await waitForOperations(api, response.operations);
598
+ }
599
+
600
+ // Get the latest project state after operations complete
601
+ if (response.project?.id) {
602
+ // Reify project properties to get complete data
603
+ response = await getProject(api, response.project.id, response);
604
+ }
605
+
606
+ return this({
607
+ id: response.project.id,
608
+ name: response.project.name,
609
+ region_id: response.project.region_id as NeonRegion,
610
+ pg_version: response.project.pg_version as 14 | 15 | 16,
611
+ created_at: response.project.created_at,
612
+ updated_at: response.project.updated_at,
613
+ proxy_host: response.project.proxy_host,
614
+ // Pass through the provided props except apiKey (which is sensitive)
615
+ default_endpoint: props.default_endpoint,
616
+ default_branch_name: props.default_branch_name,
617
+ baseUrl: props.baseUrl,
618
+ // Add all available data
619
+ // @ts-ignore - api ensures they're non-empty
620
+ connection_uris: response.connection_uris,
621
+ // @ts-ignore
622
+ roles: response.roles,
623
+ // @ts-ignore
624
+ databases: response.databases,
625
+ // @ts-ignore
626
+ branch: response.branch,
627
+ // @ts-ignore
628
+ endpoints: response.endpoints,
629
+ });
630
+ } catch (error) {
631
+ console.error(`Error ${this.phase} Neon project '${id}':`, error);
632
+ throw error;
633
+ }
634
+ }
635
+ );
636
+
637
+ /**
638
+ * Helper function to create a new Neon project
639
+ */
640
+ async function createNewProject(
641
+ api: any,
642
+ props: NeonProjectProps
643
+ ): Promise<NeonApiResponse> {
644
+ const defaultEndpoint = props.default_endpoint ?? true;
645
+ const projectResponse = await api.post("/projects", {
646
+ project: {
647
+ name: props.name,
648
+ region_id: props.region_id || "aws-us-east-1",
649
+ pg_version: props.pg_version || 15,
650
+ default_endpoint: defaultEndpoint,
651
+ branch: defaultEndpoint
652
+ ? { name: props.default_branch_name || "main" }
653
+ : undefined,
654
+ },
655
+ });
656
+
657
+ if (!projectResponse.ok) {
658
+ await handleApiError(projectResponse, "create", "project");
659
+ }
660
+
661
+ return (await projectResponse.json()) as NeonApiResponse;
662
+ }
663
+
664
+ /**
665
+ * Helper function to get complete project details by fetching all related data
666
+ *
667
+ * @param api The Neon API client
668
+ * @param projectId The project ID
669
+ * @param initialData Initial project data (optional)
670
+ * @returns Complete project data with all related resources
671
+ */
672
+ async function getProject(
673
+ api: any,
674
+ projectId: string,
675
+ initialData: Partial<NeonApiResponse> = {}
676
+ ): Promise<NeonApiResponse> {
677
+ // Get the latest project details
678
+ const updatedData = await getProjectDetails(api, projectId);
679
+
680
+ // Start with a copy of the initial data
681
+ let responseData = { ...initialData };
682
+
683
+ // Check if we have a branch ID from the initial data
684
+ const branchId = initialData.branch?.id;
685
+
686
+ if (branchId) {
687
+ // Get the branch details
688
+ const branchData = await getBranchDetails(api, projectId, branchId);
689
+
690
+ // Update with the latest branch data
691
+ responseData.branch = branchData.branch;
692
+
693
+ // Also fetch the latest endpoint details for this branch
694
+ const endpointData = await getEndpointDetails(api, projectId, branchId);
695
+
696
+ // Update with the latest endpoint data if available
697
+ if (endpointData.endpoints && endpointData.endpoints.length > 0) {
698
+ responseData.endpoints = endpointData.endpoints;
699
+ }
700
+ }
701
+
702
+ // Preserve all data from the original response
703
+ // Only update properties that might have changed during operations
704
+ return {
705
+ ...responseData,
706
+ connection_uris: (
707
+ updatedData.connection_uris || responseData.connection_uris
708
+ )?.map((uri) => ({
709
+ connection_uri: alchemy.secret(uri.connection_uri),
710
+ connection_parameters: {
711
+ database: uri.connection_parameters.database,
712
+ host: uri.connection_parameters.host,
713
+ port: uri.connection_parameters.port ?? 5432,
714
+ user: uri.connection_parameters.user ?? "neondb_owner",
715
+ password: alchemy.secret(uri.connection_parameters.password),
716
+ },
717
+ })),
718
+ project: updatedData.project,
719
+ branch: updatedData.branch || responseData.branch,
720
+ endpoints: updatedData.endpoints || responseData.endpoints,
721
+ } as NeonApiResponse;
722
+ }
723
+
724
+ /**
725
+ * Wait for operations to complete
726
+ *
727
+ * @param api The Neon API client
728
+ * @param operations Operations to wait for
729
+ * @throws Error if an operation fails or times out
730
+ * @returns Promise that resolves when all operations complete
731
+ */
732
+ async function waitForOperations(
733
+ api: any,
734
+ operations: Array<{
735
+ id: string;
736
+ project_id: string;
737
+ status: string;
738
+ action: string;
739
+ }>
740
+ ): Promise<void> {
741
+ const pendingOperations = operations.filter(
742
+ (op) => op.status !== "finished" && op.status !== "failed"
743
+ );
744
+
745
+ if (pendingOperations.length === 0) {
746
+ return;
747
+ }
748
+
749
+ // Maximum wait time in milliseconds (5 minutes)
750
+ const maxWaitTime = 5 * 60 * 1000;
751
+ // Initial delay between retries in milliseconds
752
+ const initialRetryDelay = 500;
753
+ // Maximum delay between retries
754
+ const maxRetryDelay = 10000;
755
+ // Backoff factor for exponential backoff
756
+ const backoffFactor = 1.5;
757
+
758
+ for (const operation of pendingOperations) {
759
+ let totalWaitTime = 0;
760
+ let retryDelay = initialRetryDelay;
761
+ let operationStatus = operation.status;
762
+
763
+ while (
764
+ operationStatus !== "finished" &&
765
+ operationStatus !== "failed" &&
766
+ totalWaitTime < maxWaitTime
767
+ ) {
768
+ // Wait before checking again with exponential backoff
769
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
770
+ totalWaitTime += retryDelay;
771
+
772
+ // Increase delay for next retry with exponential backoff, up to max
773
+ retryDelay = Math.min(retryDelay * backoffFactor, maxRetryDelay);
774
+
775
+ // Check operation status
776
+ const operationResponse = await api.get(
777
+ `/projects/${operation.project_id}/operations/${operation.id}`
778
+ );
779
+
780
+ if (operationResponse.ok) {
781
+ const operationData = await operationResponse.json();
782
+ operationStatus = operationData.operation?.status;
783
+ } else {
784
+ throw new Error(
785
+ `Failed to check operation ${operation.id} status: HTTP ${operationResponse.status}`
786
+ );
787
+ }
788
+ }
789
+
790
+ if (operationStatus === "failed") {
791
+ throw new Error(`Operation ${operation.id} (${operation.action}) failed`);
792
+ } else if (totalWaitTime >= maxWaitTime) {
793
+ throw new Error(
794
+ `Timeout waiting for operation ${operation.id} (${operation.action}) to complete`
795
+ );
796
+ }
797
+ }
798
+
799
+ // Explicitly return when all operations are complete
800
+ return;
801
+ }
802
+
803
+ /**
804
+ * Get the latest project details
805
+ *
806
+ * @param api The Neon API client
807
+ * @param projectId The project ID
808
+ * @returns Project details including branch and endpoints
809
+ * @throws Error if project details cannot be retrieved
810
+ */
811
+ async function getProjectDetails(
812
+ api: any,
813
+ projectId: string
814
+ ): Promise<NeonApiResponse> {
815
+ const response = await api.get(`/projects/${projectId}`);
816
+
817
+ if (!response.ok) {
818
+ throw new Error(`Failed to get project details: HTTP ${response.status}`);
819
+ }
820
+
821
+ return (await response.json()) as NeonApiResponse;
822
+ }
823
+
824
+ /**
825
+ * Get the latest branch details
826
+ *
827
+ * @param api The Neon API client
828
+ * @param projectId The project ID
829
+ * @param branchId The branch ID
830
+ * @returns Branch details
831
+ * @throws Error if branch details cannot be retrieved
832
+ */
833
+ async function getBranchDetails(
834
+ api: any,
835
+ projectId: string,
836
+ branchId: string
837
+ ): Promise<{ branch: NeonBranch }> {
838
+ const response = await api.get(`/projects/${projectId}/branches/${branchId}`);
839
+
840
+ if (!response.ok) {
841
+ throw new Error(`Failed to get branch details: HTTP ${response.status}`);
842
+ }
843
+
844
+ return (await response.json()) as { branch: NeonBranch };
845
+ }
846
+
847
+ /**
848
+ * Get the latest endpoint details for a branch
849
+ *
850
+ * @param api The Neon API client
851
+ * @param projectId The project ID
852
+ * @param branchId The branch ID
853
+ * @returns Endpoint details for the branch
854
+ * @throws Error if endpoint details cannot be retrieved
855
+ */
856
+ async function getEndpointDetails(
857
+ api: any,
858
+ projectId: string,
859
+ branchId: string
860
+ ): Promise<{ endpoints: NeonEndpoint[] }> {
861
+ const response = await api.get(
862
+ `/projects/${projectId}/branches/${branchId}/endpoints`
863
+ );
864
+
865
+ if (!response.ok) {
866
+ throw new Error(`Failed to get endpoint details: HTTP ${response.status}`);
867
+ }
868
+
869
+ return (await response.json()) as { endpoints: NeonEndpoint[] };
870
+ }