alchemy 0.12.6 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cloudflare/ai-gateway.d.ts.map +1 -1
- package/lib/cloudflare/ai-gateway.js +1 -2
- package/lib/cloudflare/ai-gateway.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +2 -1
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +216 -0
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -0
- package/lib/cloudflare/hyperdrive.js +184 -0
- package/lib/cloudflare/hyperdrive.js.map +1 -0
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/worker.js +8 -0
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/neon/api-error.d.ts +31 -0
- package/lib/neon/api-error.d.ts.map +1 -0
- package/lib/neon/api-error.js +73 -0
- package/lib/neon/api-error.js.map +1 -0
- package/lib/neon/api.d.ts +70 -0
- package/lib/neon/api.d.ts.map +1 -0
- package/lib/neon/api.js +128 -0
- package/lib/neon/api.js.map +1 -0
- package/lib/neon/index.d.ts +4 -0
- package/lib/neon/index.d.ts.map +1 -0
- package/lib/neon/index.js +4 -0
- package/lib/neon/index.js.map +1 -0
- package/lib/neon/project.d.ts +345 -0
- package/lib/neon/project.d.ts.map +1 -0
- package/lib/neon/project.js +305 -0
- package/lib/neon/project.js.map +1 -0
- package/package.json +2 -1
- package/src/cloudflare/ai-gateway.ts +0 -1
- package/src/cloudflare/bindings.ts +4 -2
- package/src/cloudflare/bound.ts +20 -17
- package/src/cloudflare/hyperdrive.ts +375 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/worker.ts +7 -0
- package/src/neon/api-error.ts +81 -0
- package/src/neon/api.ts +178 -0
- package/src/neon/index.ts +3 -0
- package/src/neon/project.ts +870 -0
|
@@ -9,6 +9,7 @@ import type { Assets } from "./assets.js";
|
|
|
9
9
|
import type { R2Bucket } from "./bucket.js";
|
|
10
10
|
import type { D1Database } from "./d1-database.js";
|
|
11
11
|
import type { DurableObjectNamespace } from "./durable-object-namespace.js";
|
|
12
|
+
import type { Hyperdrive } from "./hyperdrive.js";
|
|
12
13
|
import type { KVNamespace } from "./kv-namespace.js";
|
|
13
14
|
import type { Pipeline } from "./pipeline.js";
|
|
14
15
|
import type { Queue } from "./queue.js";
|
|
@@ -24,9 +25,11 @@ export type Bindings = {
|
|
|
24
25
|
* L2 Binding Resources.
|
|
25
26
|
*/
|
|
26
27
|
export type Binding =
|
|
28
|
+
| AiGateway
|
|
27
29
|
| Assets
|
|
28
30
|
| D1Database
|
|
29
31
|
| DurableObjectNamespace
|
|
32
|
+
| Hyperdrive
|
|
30
33
|
| KVNamespace
|
|
31
34
|
| Pipeline
|
|
32
35
|
| Queue
|
|
@@ -35,8 +38,7 @@ export type Binding =
|
|
|
35
38
|
| string
|
|
36
39
|
| VectorizeIndex
|
|
37
40
|
| Worker
|
|
38
|
-
| Workflow
|
|
39
|
-
| AiGateway;
|
|
41
|
+
| Workflow;
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Union type for all Worker binding types (API spec)
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Binding } from "./bindings.js";
|
|
|
6
6
|
import type { R2Bucket as _R2Bucket } from "./bucket.js";
|
|
7
7
|
import type { D1Database as _D1Database } from "./d1-database.js";
|
|
8
8
|
import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace.js";
|
|
9
|
+
import type { Hyperdrive as _Hyperdrive } from "./hyperdrive.js";
|
|
9
10
|
import type { KVNamespace as _KVNamespace } from "./kv-namespace.js";
|
|
10
11
|
import type { Pipeline as _Pipeline } from "./pipeline.js";
|
|
11
12
|
import type { Queue as _Queue } from "./queue.js";
|
|
@@ -23,20 +24,22 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace
|
|
|
23
24
|
? R2Bucket
|
|
24
25
|
: T extends _AiGateway
|
|
25
26
|
? AiGateway
|
|
26
|
-
: T extends
|
|
27
|
-
?
|
|
28
|
-
: T extends
|
|
29
|
-
?
|
|
30
|
-
: T extends
|
|
31
|
-
?
|
|
32
|
-
: T extends
|
|
33
|
-
?
|
|
34
|
-
: T extends
|
|
35
|
-
?
|
|
36
|
-
: T extends
|
|
37
|
-
?
|
|
38
|
-
: T extends
|
|
39
|
-
?
|
|
40
|
-
: T extends
|
|
41
|
-
?
|
|
42
|
-
:
|
|
27
|
+
: T extends _Hyperdrive
|
|
28
|
+
? Hyperdrive
|
|
29
|
+
: T extends Secret
|
|
30
|
+
? string
|
|
31
|
+
: T extends Assets
|
|
32
|
+
? Service
|
|
33
|
+
: T extends _Workflow<infer P>
|
|
34
|
+
? Workflow<P>
|
|
35
|
+
: T extends _D1Database
|
|
36
|
+
? D1Database
|
|
37
|
+
: T extends _VectorizeIndex
|
|
38
|
+
? VectorizeIndex
|
|
39
|
+
: T extends _Queue
|
|
40
|
+
? Queue
|
|
41
|
+
: T extends _Pipeline<infer R>
|
|
42
|
+
? Pipeline<R>
|
|
43
|
+
: T extends string
|
|
44
|
+
? string
|
|
45
|
+
: Service;
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type { Context } from "../context.js";
|
|
2
|
+
import { Resource } from "../resource.js";
|
|
3
|
+
import type { Secret } from "../secret.js";
|
|
4
|
+
import { handleApiError } from "./api-error.js";
|
|
5
|
+
import { createCloudflareApi, type CloudflareApiOptions } from "./api.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Origin configuration for a PostgreSQL database connection
|
|
9
|
+
*/
|
|
10
|
+
export interface HyperdriveOrigin {
|
|
11
|
+
/**
|
|
12
|
+
* Database name
|
|
13
|
+
*/
|
|
14
|
+
database: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Database host
|
|
18
|
+
*/
|
|
19
|
+
host: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Database password
|
|
23
|
+
* Use alchemy.secret() to securely store this value
|
|
24
|
+
*/
|
|
25
|
+
password: Secret;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Database port
|
|
29
|
+
* @default 5432
|
|
30
|
+
*/
|
|
31
|
+
port?: number;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Connection scheme
|
|
35
|
+
* @default "postgres"
|
|
36
|
+
*/
|
|
37
|
+
scheme?: "postgres";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Database user
|
|
41
|
+
*/
|
|
42
|
+
user: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Origin configuration for a database connection with access tokens
|
|
47
|
+
*/
|
|
48
|
+
export interface HyperdriveOriginWithAccess {
|
|
49
|
+
/**
|
|
50
|
+
* Access client ID
|
|
51
|
+
*/
|
|
52
|
+
access_client_id: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Access client secret
|
|
56
|
+
* Use alchemy.secret() to securely store this value
|
|
57
|
+
*/
|
|
58
|
+
access_client_secret: Secret;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Database host
|
|
62
|
+
*/
|
|
63
|
+
host: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Database name
|
|
67
|
+
*/
|
|
68
|
+
database: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Database port
|
|
72
|
+
* @default 5432
|
|
73
|
+
*/
|
|
74
|
+
port?: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Connection scheme
|
|
78
|
+
* @default "postgres"
|
|
79
|
+
*/
|
|
80
|
+
scheme?: "postgres";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Database user
|
|
84
|
+
*/
|
|
85
|
+
user: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Caching configuration for Hyperdrive
|
|
90
|
+
*/
|
|
91
|
+
export interface HyperdriveCaching {
|
|
92
|
+
/**
|
|
93
|
+
* Whether caching is disabled
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* mTLS configuration for Hyperdrive
|
|
101
|
+
*/
|
|
102
|
+
export interface HyperdriveMtls {
|
|
103
|
+
/**
|
|
104
|
+
* CA certificate ID
|
|
105
|
+
*/
|
|
106
|
+
ca_certificate_id?: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* mTLS certificate ID
|
|
110
|
+
*/
|
|
111
|
+
mtls_certificate_id?: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* SSL mode
|
|
115
|
+
* @default "verify-full"
|
|
116
|
+
*/
|
|
117
|
+
sslmode?: "verify-ca" | "verify-full";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Properties for creating or updating a Cloudflare Hyperdrive.
|
|
122
|
+
*/
|
|
123
|
+
export interface HyperdriveProps extends CloudflareApiOptions {
|
|
124
|
+
/**
|
|
125
|
+
* Name of the Hyperdrive configuration
|
|
126
|
+
*/
|
|
127
|
+
name: string;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Database connection origin configuration
|
|
131
|
+
*/
|
|
132
|
+
origin: HyperdriveOrigin | HyperdriveOriginWithAccess;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Caching configuration
|
|
136
|
+
*/
|
|
137
|
+
caching?: HyperdriveCaching;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* mTLS configuration
|
|
141
|
+
*/
|
|
142
|
+
mtls?: HyperdriveMtls;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* UUID of the hyperdrive (only used for update/delete operations)
|
|
146
|
+
* This is provided by Cloudflare and is different from the resource ID
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
hyperdriveId?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Output returned after Cloudflare Hyperdrive creation/update.
|
|
154
|
+
* IMPORTANT: The interface name MUST match the exported resource name.
|
|
155
|
+
*/
|
|
156
|
+
export interface Hyperdrive
|
|
157
|
+
extends Resource<"cloudflare::Hyperdrive">,
|
|
158
|
+
Omit<HyperdriveProps, "origin"> {
|
|
159
|
+
/**
|
|
160
|
+
* The ID of the resource
|
|
161
|
+
*/
|
|
162
|
+
id: string;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The Cloudflare-generated UUID of the hyperdrive
|
|
166
|
+
*/
|
|
167
|
+
hyperdriveId: string;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Database connection origin configuration
|
|
171
|
+
*/
|
|
172
|
+
origin: HyperdriveOrigin | HyperdriveOriginWithAccess;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resource type identifier for binding.
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
type: "hyperdrive";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Represents a Cloudflare Hyperdrive configuration.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* // Create a basic Hyperdrive connection to a PostgreSQL database
|
|
186
|
+
* const basicHyperdrive = await Hyperdrive("my-postgres-db", {
|
|
187
|
+
* name: "my-postgres-db",
|
|
188
|
+
* origin: {
|
|
189
|
+
* database: "postgres",
|
|
190
|
+
* host: "database.example.com",
|
|
191
|
+
* password: alchemy.secret("your-password"),
|
|
192
|
+
* port: 5432,
|
|
193
|
+
* user: "postgres"
|
|
194
|
+
* }
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* // Create a Hyperdrive with caching disabled
|
|
199
|
+
* const noCacheHyperdrive = await Hyperdrive("no-cache-db", {
|
|
200
|
+
* name: "no-cache-db",
|
|
201
|
+
* origin: {
|
|
202
|
+
* database: "postgres",
|
|
203
|
+
* host: "database.example.com",
|
|
204
|
+
* password: alchemy.secret(process.env.DB_PASSWORD),
|
|
205
|
+
* port: 5432,
|
|
206
|
+
* user: "postgres"
|
|
207
|
+
* },
|
|
208
|
+
* caching: {
|
|
209
|
+
* disabled: true
|
|
210
|
+
* }
|
|
211
|
+
* });
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* // Create a Hyperdrive with mTLS configuration
|
|
215
|
+
* const mtlsHyperdrive = await Hyperdrive("secure-db", {
|
|
216
|
+
* name: "secure-db",
|
|
217
|
+
* origin: {
|
|
218
|
+
* database: "postgres",
|
|
219
|
+
* host: "database.example.com",
|
|
220
|
+
* password: alchemy.secret(process.env.DB_PASSWORD),
|
|
221
|
+
* port: 5432,
|
|
222
|
+
* user: "postgres"
|
|
223
|
+
* },
|
|
224
|
+
* mtls: {
|
|
225
|
+
* ca_certificate_id: "00000000-0000-0000-0000-0000000000",
|
|
226
|
+
* mtls_certificate_id: "00000000-0000-0000-0000-0000000000",
|
|
227
|
+
* sslmode: "verify-full"
|
|
228
|
+
* }
|
|
229
|
+
* });
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* // Create a Hyperdrive with access client credentials
|
|
233
|
+
* const accessHyperdrive = await Hyperdrive("access-db", {
|
|
234
|
+
* name: "access-db",
|
|
235
|
+
* origin: {
|
|
236
|
+
* database: "postgres",
|
|
237
|
+
* host: "database.example.com",
|
|
238
|
+
* access_client_id: "client-id",
|
|
239
|
+
* access_client_secret: alchemy.secret(process.env.ACCESS_CLIENT_SECRET),
|
|
240
|
+
* port: 5432,
|
|
241
|
+
* user: "postgres"
|
|
242
|
+
* }
|
|
243
|
+
* });
|
|
244
|
+
*/
|
|
245
|
+
export const Hyperdrive = Resource(
|
|
246
|
+
"cloudflare::Hyperdrive",
|
|
247
|
+
async function (
|
|
248
|
+
this: Context<Hyperdrive>,
|
|
249
|
+
id: string,
|
|
250
|
+
props: HyperdriveProps
|
|
251
|
+
): Promise<Hyperdrive> {
|
|
252
|
+
const api = await createCloudflareApi(props);
|
|
253
|
+
const configsPath = `/accounts/${api.accountId}/hyperdrive/configs`;
|
|
254
|
+
|
|
255
|
+
// For create operations, we don't have a hyperdriveId yet
|
|
256
|
+
// For update/delete operations, we need to use the hyperdriveId from props or output
|
|
257
|
+
const hyperdriveId = props.hyperdriveId || this.output?.hyperdriveId;
|
|
258
|
+
const configPath = hyperdriveId
|
|
259
|
+
? `${configsPath}/${hyperdriveId}`
|
|
260
|
+
: `${configsPath}`;
|
|
261
|
+
|
|
262
|
+
if (this.phase === "delete") {
|
|
263
|
+
if (!hyperdriveId) {
|
|
264
|
+
console.warn(`No hyperdriveId found for ${id}, skipping delete`);
|
|
265
|
+
return this.destroy();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const deleteResponse = await api.delete(configPath);
|
|
270
|
+
// Only swallow 404 Not Found errors, all other errors should be handled
|
|
271
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
272
|
+
await handleApiError(deleteResponse, "delete", "hyperdrive", id);
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(`Error deleting Hyperdrive ${id}:`, error);
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
return this.destroy();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let response: Response | undefined;
|
|
282
|
+
let apiResource: any;
|
|
283
|
+
|
|
284
|
+
// Prepare request body with unwrapped secrets
|
|
285
|
+
const requestBody = prepareRequestBody(props);
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
if (this.phase === "update" && hyperdriveId) {
|
|
289
|
+
// Update existing hyperdrive
|
|
290
|
+
response = await api.put(configPath, requestBody);
|
|
291
|
+
} else {
|
|
292
|
+
// Create new hyperdrive
|
|
293
|
+
if (hyperdriveId) {
|
|
294
|
+
// If we have a hyperdriveId but we're in create phase, it could be because
|
|
295
|
+
// the resource exists but wasn't in state. Do a GET to check.
|
|
296
|
+
const getResponse = await api.get(configPath);
|
|
297
|
+
if (getResponse.status === 200) {
|
|
298
|
+
// Hyperdrive exists, update it
|
|
299
|
+
console.log(
|
|
300
|
+
`Hyperdrive '${id}' already exists. Updating existing resource.`
|
|
301
|
+
);
|
|
302
|
+
response = await api.put(configPath, requestBody);
|
|
303
|
+
} else if (getResponse.status === 404) {
|
|
304
|
+
// Hyperdrive doesn't exist, create new
|
|
305
|
+
response = await api.post(configsPath, {
|
|
306
|
+
...requestBody,
|
|
307
|
+
// Ensure name is set correctly if not already set
|
|
308
|
+
name: props.name || id,
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
// Unexpected error during GET check
|
|
312
|
+
await handleApiError(getResponse, "get", "hyperdrive", id);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// No hyperdriveId, create new
|
|
316
|
+
response = await api.post(configsPath, {
|
|
317
|
+
...requestBody,
|
|
318
|
+
// Ensure name is set correctly if not already set
|
|
319
|
+
name: props.name || id,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!response?.ok) {
|
|
325
|
+
const action = this.phase === "update" ? "update" : "create";
|
|
326
|
+
await handleApiError(response!, action, "hyperdrive", id);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const data: { result: Record<string, any> } = await response!.json();
|
|
330
|
+
apiResource = data.result;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error(`Error ${this.phase} Hyperdrive '${id}':`, error);
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Construct the output object from API response and props
|
|
337
|
+
return this({
|
|
338
|
+
id,
|
|
339
|
+
hyperdriveId: apiResource.id, // Store the Cloudflare-assigned UUID
|
|
340
|
+
name: apiResource.name,
|
|
341
|
+
origin: props.origin, // Keep the original origin with secrets
|
|
342
|
+
caching: apiResource.caching,
|
|
343
|
+
mtls: apiResource.mtls,
|
|
344
|
+
type: "hyperdrive",
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Prepare the request body by unwrapping secret values
|
|
351
|
+
*/
|
|
352
|
+
function prepareRequestBody(props: HyperdriveProps): any {
|
|
353
|
+
const requestBody: any = { ...props };
|
|
354
|
+
|
|
355
|
+
// Remove internal props
|
|
356
|
+
delete requestBody.hyperdriveId;
|
|
357
|
+
|
|
358
|
+
// Deep clone and unwrap secrets in the origin object
|
|
359
|
+
if ("password" in props.origin) {
|
|
360
|
+
// Regular origin with password
|
|
361
|
+
requestBody.origin = {
|
|
362
|
+
...props.origin,
|
|
363
|
+
password: props.origin.password.unencrypted,
|
|
364
|
+
scheme: props.origin.scheme ?? "postgres",
|
|
365
|
+
};
|
|
366
|
+
} else if ("access_client_secret" in props.origin) {
|
|
367
|
+
// Origin with access client secret
|
|
368
|
+
requestBody.origin = {
|
|
369
|
+
...props.origin,
|
|
370
|
+
access_client_secret: props.origin.access_client_secret.unencrypted,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return requestBody;
|
|
375
|
+
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./custom-domain.js";
|
|
|
9
9
|
export * from "./d1-database.js";
|
|
10
10
|
export * from "./dns-records.js";
|
|
11
11
|
export * from "./durable-object-namespace.js";
|
|
12
|
+
export * from "./hyperdrive.js";
|
|
12
13
|
export * from "./kv-namespace.js";
|
|
13
14
|
export * from "./local-dev-cloudflare-shim.js";
|
|
14
15
|
export * from "./nuxt.js";
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -784,6 +784,13 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
784
784
|
type: "ai",
|
|
785
785
|
name: bindingName,
|
|
786
786
|
});
|
|
787
|
+
} else if (binding.type === "hyperdrive") {
|
|
788
|
+
// Hyperdrive binding
|
|
789
|
+
meta.bindings.push({
|
|
790
|
+
type: "hyperdrive",
|
|
791
|
+
name: bindingName,
|
|
792
|
+
id: binding.hyperdriveId,
|
|
793
|
+
});
|
|
787
794
|
} else {
|
|
788
795
|
// @ts-expect-error - we should never reach here
|
|
789
796
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error class for Neon API errors
|
|
3
|
+
*/
|
|
4
|
+
export class NeonApiError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* HTTP status code
|
|
7
|
+
*/
|
|
8
|
+
public readonly status: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HTTP status text
|
|
12
|
+
*/
|
|
13
|
+
public readonly statusText: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Error data from the API response
|
|
17
|
+
*/
|
|
18
|
+
public readonly errorData?: any;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a new NeonApiError
|
|
22
|
+
*/
|
|
23
|
+
constructor(message: string, response: Response, errorData?: any) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "NeonApiError";
|
|
26
|
+
this.status = response.status;
|
|
27
|
+
this.statusText = response.statusText;
|
|
28
|
+
this.errorData = errorData;
|
|
29
|
+
|
|
30
|
+
// Capture stack trace
|
|
31
|
+
if (Error.captureStackTrace) {
|
|
32
|
+
Error.captureStackTrace(this, NeonApiError);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Handle API errors and throw NeonApiError with context
|
|
39
|
+
*
|
|
40
|
+
* @param response Response object from fetch
|
|
41
|
+
* @param action Action that was being performed
|
|
42
|
+
* @param resourceType Type of resource
|
|
43
|
+
* @param resourceId ID of the resource
|
|
44
|
+
*/
|
|
45
|
+
export async function handleApiError(
|
|
46
|
+
response: Response,
|
|
47
|
+
action: "get" | "create" | "update" | "delete" | "list",
|
|
48
|
+
resourceType: string,
|
|
49
|
+
resourceId?: string
|
|
50
|
+
): Promise<never> {
|
|
51
|
+
const resourceDisplay = resourceId ? `'${resourceId}'` : "";
|
|
52
|
+
let errorData: any;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
errorData = await response.json();
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// If we can't parse JSON, just use the response text
|
|
58
|
+
try {
|
|
59
|
+
errorData = { message: await response.text() };
|
|
60
|
+
} catch (textError) {
|
|
61
|
+
errorData = { message: response.statusText };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let message = `Error ${action} ${resourceType} ${resourceDisplay}: `;
|
|
66
|
+
|
|
67
|
+
if (errorData?.error?.message) {
|
|
68
|
+
message += errorData.error.message;
|
|
69
|
+
} else if (errorData?.message) {
|
|
70
|
+
message += errorData.message;
|
|
71
|
+
} else if (errorData?.error) {
|
|
72
|
+
message +=
|
|
73
|
+
typeof errorData.error === "string"
|
|
74
|
+
? errorData.error
|
|
75
|
+
: JSON.stringify(errorData.error);
|
|
76
|
+
} else {
|
|
77
|
+
message += response.statusText;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new NeonApiError(message, response, errorData);
|
|
81
|
+
}
|