alchemy 0.71.0 → 0.72.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 (97) hide show
  1. package/README.md +1 -1
  2. package/bin/alchemy.js +119 -44
  3. package/bin/services/execute-alchemy.ts +12 -3
  4. package/bin/trpc.ts +12 -12
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +21 -6
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/apply.js +1 -1
  9. package/lib/apply.js.map +1 -1
  10. package/lib/cloudflare/d1-database.d.ts +1 -1
  11. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  12. package/lib/cloudflare/d1-database.js +1 -1
  13. package/lib/cloudflare/d1-database.js.map +1 -1
  14. package/lib/cloudflare/health-check.d.ts +261 -0
  15. package/lib/cloudflare/health-check.d.ts.map +1 -0
  16. package/lib/cloudflare/health-check.js +277 -0
  17. package/lib/cloudflare/health-check.js.map +1 -0
  18. package/lib/cloudflare/index.d.ts +1 -0
  19. package/lib/cloudflare/index.d.ts.map +1 -1
  20. package/lib/cloudflare/index.js +1 -0
  21. package/lib/cloudflare/index.js.map +1 -1
  22. package/lib/cloudflare/wrangler.json.d.ts +8 -8
  23. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  24. package/lib/cloudflare/wrangler.json.js +9 -11
  25. package/lib/cloudflare/wrangler.json.js.map +1 -1
  26. package/lib/cloudflare/zone.js +1 -1
  27. package/lib/destroy.d.ts.map +1 -1
  28. package/lib/destroy.js +27 -0
  29. package/lib/destroy.js.map +1 -1
  30. package/lib/docker/image.js +1 -1
  31. package/lib/docker/image.js.map +1 -1
  32. package/lib/planetscale/api.d.ts +0 -4
  33. package/lib/planetscale/api.d.ts.map +1 -1
  34. package/lib/planetscale/api.js.map +1 -1
  35. package/lib/planetscale/branch.d.ts +12 -11
  36. package/lib/planetscale/branch.d.ts.map +1 -1
  37. package/lib/planetscale/branch.js +40 -23
  38. package/lib/planetscale/branch.js.map +1 -1
  39. package/lib/planetscale/database.d.ts +10 -5
  40. package/lib/planetscale/database.d.ts.map +1 -1
  41. package/lib/planetscale/database.js +30 -18
  42. package/lib/planetscale/database.js.map +1 -1
  43. package/lib/planetscale/index.d.ts +1 -0
  44. package/lib/planetscale/index.d.ts.map +1 -1
  45. package/lib/planetscale/index.js +1 -0
  46. package/lib/planetscale/index.js.map +1 -1
  47. package/lib/planetscale/organization.d.ts +5 -0
  48. package/lib/planetscale/organization.d.ts.map +1 -0
  49. package/lib/planetscale/organization.js +11 -0
  50. package/lib/planetscale/organization.js.map +1 -0
  51. package/lib/planetscale/password.d.ts +10 -9
  52. package/lib/planetscale/password.d.ts.map +1 -1
  53. package/lib/planetscale/password.js +28 -13
  54. package/lib/planetscale/password.js.map +1 -1
  55. package/lib/planetscale/role.d.ts +2 -1
  56. package/lib/planetscale/role.d.ts.map +1 -1
  57. package/lib/planetscale/role.js +11 -8
  58. package/lib/planetscale/role.js.map +1 -1
  59. package/lib/resource.js +1 -1
  60. package/lib/resource.js.map +1 -1
  61. package/lib/scope.d.ts +6 -1
  62. package/lib/scope.d.ts.map +1 -1
  63. package/lib/scope.js +16 -15
  64. package/lib/scope.js.map +1 -1
  65. package/lib/state/instrumented-state-store.js +1 -1
  66. package/lib/state/instrumented-state-store.js.map +1 -1
  67. package/lib/util/cli-args.d.ts +3 -0
  68. package/lib/util/cli-args.d.ts.map +1 -0
  69. package/lib/util/cli-args.js +6 -0
  70. package/lib/util/cli-args.js.map +1 -0
  71. package/lib/util/telemetry.d.ts +1 -0
  72. package/lib/util/telemetry.d.ts.map +1 -1
  73. package/lib/util/telemetry.js +47 -2
  74. package/lib/util/telemetry.js.map +1 -1
  75. package/package.json +10 -8
  76. package/src/alchemy.ts +25 -11
  77. package/src/apply.ts +1 -1
  78. package/src/cloudflare/d1-database.ts +2 -2
  79. package/src/cloudflare/health-check.ts +617 -0
  80. package/src/cloudflare/index.ts +1 -0
  81. package/src/cloudflare/wrangler.json.ts +21 -23
  82. package/src/cloudflare/zone.ts +1 -1
  83. package/src/destroy.ts +32 -0
  84. package/src/docker/image.ts +1 -1
  85. package/src/planetscale/api.ts +0 -4
  86. package/src/planetscale/branch.ts +51 -35
  87. package/src/planetscale/database.ts +40 -20
  88. package/src/planetscale/index.ts +1 -0
  89. package/src/planetscale/organization.ts +17 -0
  90. package/src/planetscale/password.ts +34 -16
  91. package/src/planetscale/role.ts +16 -9
  92. package/src/resource.ts +1 -1
  93. package/src/scope.ts +25 -15
  94. package/src/state/instrumented-state-store.ts +1 -1
  95. package/src/util/cli-args.ts +11 -0
  96. package/src/util/telemetry.ts +68 -2
  97. package/workers/tunnel-proxy.js +1 -1
@@ -0,0 +1,617 @@
1
+ import type { Context } from "../context.ts";
2
+ import { Resource } from "../resource.ts";
3
+ import type { Secret } from "../secret.ts";
4
+ import { Secret as SecretClass } from "../secret.ts";
5
+ import { logger } from "../util/logger.ts";
6
+ import { CloudflareApiError, handleApiError } from "./api-error.ts";
7
+ import {
8
+ extractCloudflareResult,
9
+ type CloudflareApiErrorPayload,
10
+ } from "./api-response.ts";
11
+ import {
12
+ createCloudflareApi,
13
+ type CloudflareApi,
14
+ type CloudflareApiOptions,
15
+ } from "./api.ts";
16
+ import type { Zone } from "./zone.ts";
17
+
18
+ export type CheckRegion =
19
+ | "WNAM"
20
+ | "ENAM"
21
+ | "WEU"
22
+ | "EEU"
23
+ | "NSAM"
24
+ | "SSAM"
25
+ | "OC"
26
+ | "ME"
27
+ | "NAF"
28
+ | "SAF"
29
+ | "IN"
30
+ | "SEAS"
31
+ | "NEAS"
32
+ | "ALL_REGIONS"
33
+ | (string & {});
34
+
35
+ export interface HTTPConfiguration {
36
+ /**
37
+ * Do not validate the certificate when the health check uses HTTPS
38
+ * @default false
39
+ */
40
+ allowInsecure?: boolean;
41
+
42
+ /**
43
+ * A case-insensitive sub-string to look for in the response body
44
+ * If this string is not found, the origin will be marked as unhealthy
45
+ * @default undefined
46
+ */
47
+ expectedBody?: string;
48
+
49
+ /**
50
+ * The expected HTTP response codes (e.g. "200") or code ranges (e.g. "2xx" for all codes starting with 2)
51
+ * @default undefined
52
+ */
53
+ expectedCodes?: string[];
54
+
55
+ /**
56
+ * Follow redirects if the origin returns a 3xx status code
57
+ * @default false
58
+ */
59
+ followRedirects?: boolean;
60
+
61
+ /**
62
+ * The HTTP request headers to send in the health check
63
+ * It is recommended you set a Host header by default
64
+ * The User-Agent header cannot be overridden
65
+ * Supports secret values for sensitive headers
66
+ */
67
+ header?: Record<string, (string | Secret<string>)[]>;
68
+
69
+ /**
70
+ * The HTTP method to use for the health check
71
+ * @default "GET"
72
+ */
73
+ method?: "GET" | "HEAD";
74
+
75
+ /**
76
+ * The endpoint path to health check against
77
+ * @default "/"
78
+ */
79
+ path?: string;
80
+
81
+ /**
82
+ * Port number to connect to for the health check
83
+ * Defaults based on the health check type (specified via the `type` property):
84
+ * @default 80 for HTTP
85
+ * @default 443 for HTTPS
86
+ */
87
+ port?: number;
88
+ }
89
+
90
+ export interface TCPConfiguration {
91
+ /**
92
+ * The TCP connection method to use for the health check
93
+ * @default "connection_established"
94
+ */
95
+ method?: "connection_established";
96
+
97
+ /**
98
+ * Port number to connect to for the health check
99
+ * @default 80
100
+ */
101
+ port?: number;
102
+ }
103
+
104
+ export interface HealthCheckProps extends CloudflareApiOptions {
105
+ /**
106
+ * Zone this health check belongs to
107
+ * Can be either a Zone resource or a zone ID string
108
+ */
109
+ zone: string | Zone;
110
+
111
+ /**
112
+ * The hostname or IP address of the origin server to run health checks on
113
+ */
114
+ address: string;
115
+
116
+ /**
117
+ * A short name to identify the health check
118
+ * Only alphanumeric characters, hyphens and underscores are allowed
119
+ *
120
+ * @default ${app}-${stage}-${id}
121
+ */
122
+ name?: string;
123
+
124
+ /**
125
+ * A list of regions from which to run health checks
126
+ * If not specified, Cloudflare will pick a default region
127
+ */
128
+ checkRegions?: CheckRegion[];
129
+
130
+ /**
131
+ * The number of consecutive fails required from a health check before changing the health to unhealthy
132
+ * @default 1
133
+ */
134
+ consecutiveFails?: number;
135
+
136
+ /**
137
+ * The number of consecutive successes required from a health check before changing the health to healthy
138
+ * @default 1
139
+ */
140
+ consecutiveSuccesses?: number;
141
+
142
+ /**
143
+ * A human-readable description of the health check
144
+ */
145
+ description?: string;
146
+
147
+ /**
148
+ * Parameters specific to an HTTP or HTTPS health check
149
+ */
150
+ httpConfig?: HTTPConfiguration;
151
+
152
+ /**
153
+ * The interval between each health check in seconds
154
+ * Shorter intervals may give quicker notifications if the origin status changes,
155
+ * but will increase load on the origin as we check from multiple locations
156
+ * @default 60
157
+ */
158
+ interval?: number;
159
+
160
+ /**
161
+ * The number of retries to attempt in case of a timeout before marking the origin as unhealthy
162
+ * Retries are attempted immediately
163
+ * @default 2
164
+ */
165
+ retries?: number;
166
+
167
+ /**
168
+ * If suspended, no health checks are sent to the origin
169
+ * @default false
170
+ */
171
+ suspended?: boolean;
172
+
173
+ /**
174
+ * Parameters specific to TCP health check
175
+ */
176
+ tcpConfig?: TCPConfiguration;
177
+
178
+ /**
179
+ * The timeout (in seconds) before marking the health check as failed
180
+ * @default 5
181
+ */
182
+ timeout?: number;
183
+
184
+ /**
185
+ * The protocol to use for the health check
186
+ * Currently supported protocols are 'HTTP', 'HTTPS' and 'TCP'
187
+ * @default "HTTP"
188
+ */
189
+ type?: string;
190
+
191
+ /**
192
+ * Whether to adopt an existing health check
193
+ * @default false
194
+ */
195
+ adopt?: boolean;
196
+ }
197
+
198
+ /**
199
+ * Output returned after Cloudflare Health Check creation/update
200
+ * IMPORTANT: The type name MUST match the exported resource name
201
+ */
202
+ export type HealthCheck = Omit<HealthCheckProps, "zone" | "adopt"> & {
203
+ /**
204
+ * The resource ID
205
+ */
206
+ id: string;
207
+
208
+ /**
209
+ * The Cloudflare-generated health check ID
210
+ */
211
+ healthCheckId: string;
212
+
213
+ /**
214
+ * The name of the health check
215
+ */
216
+ name: string;
217
+
218
+ /**
219
+ * The zone ID (extracted from zone prop)
220
+ */
221
+ zoneId: string;
222
+
223
+ /**
224
+ * Time at which the health check was created
225
+ */
226
+ createdOn?: string;
227
+
228
+ /**
229
+ * Time at which the health check was last modified
230
+ */
231
+ modifiedOn?: string;
232
+
233
+ /**
234
+ * The current status of the origin server according to the health check
235
+ */
236
+ status?: "unknown" | "healthy" | "unhealthy" | "suspended";
237
+
238
+ /**
239
+ * The current failure reason if status is unhealthy
240
+ */
241
+ failureReason?: string;
242
+ };
243
+
244
+ /**
245
+ * Delete a health check from Cloudflare
246
+ * @internal
247
+ */
248
+ export async function deleteHealthCheck(
249
+ api: CloudflareApi,
250
+ zoneId: string,
251
+ healthCheckId: string,
252
+ id: string,
253
+ ): Promise<void> {
254
+ try {
255
+ const deleteResponse = await api.delete(
256
+ `/zones/${zoneId}/healthchecks/${healthCheckId}`,
257
+ );
258
+
259
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
260
+ await handleApiError(deleteResponse, "delete", "health check", id);
261
+ }
262
+ } catch (error) {
263
+ logger.error(`Error deleting Health Check ${id}:`, error);
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Represents a Cloudflare Health Check for monitoring origin server availability.
270
+ *
271
+ * Health Checks monitor the availability of your origin servers and can be used
272
+ * with Load Balancers to automatically route traffic away from unhealthy origins.
273
+ *
274
+ * @example
275
+ * // Create a basic HTTP health check
276
+ * const basicHealthCheck = await HealthCheck("api-healthcheck", {
277
+ * zone: "023e105f4ecef8ad9ca31a8372d0c353",
278
+ * address: "api.example.com",
279
+ * name: "api-server-check"
280
+ * });
281
+ *
282
+ * @example
283
+ * // Create an HTTPS health check with custom path and expected response
284
+ * const httpsHealthCheck = await HealthCheck("secure-api-check", {
285
+ * zone: "023e105f4ecef8ad9ca31a8372d0c353",
286
+ * address: "secure-api.example.com",
287
+ * name: "secure-api-check",
288
+ * type: "HTTPS",
289
+ * httpConfig: {
290
+ * path: "/health",
291
+ * expectedCodes: ["200", "201"],
292
+ * expectedBody: "OK",
293
+ * method: "GET"
294
+ * }
295
+ * });
296
+ *
297
+ * @example
298
+ * // Create a health check with custom intervals and retry logic
299
+ * const customHealthCheck = await HealthCheck("custom-check", {
300
+ * zone: "023e105f4ecef8ad9ca31a8372d0c353",
301
+ * address: "backend.example.com",
302
+ * name: "backend-check",
303
+ * interval: 30,
304
+ * timeout: 10,
305
+ * retries: 3,
306
+ * consecutiveFails: 2,
307
+ * consecutiveSuccesses: 2,
308
+ * description: "Backend server health monitoring"
309
+ * });
310
+ *
311
+ * @example
312
+ * // Create a TCP health check
313
+ * const tcpHealthCheck = await HealthCheck("tcp-check", {
314
+ * zone: "023e105f4ecef8ad9ca31a8372d0c353",
315
+ * address: "database.example.com",
316
+ * name: "database-check",
317
+ * type: "TCP",
318
+ * tcpConfig: {
319
+ * port: 5432,
320
+ * method: "connection_established"
321
+ * }
322
+ * });
323
+ *
324
+ * @example
325
+ * // Create a health check with specific regions and custom headers
326
+ * const regionalHealthCheck = await HealthCheck("regional-check", {
327
+ * zone: "023e105f4ecef8ad9ca31a8372d0c353",
328
+ * address: "api.example.com",
329
+ * name: "regional-api-check",
330
+ * checkRegions: ["WNAM", "ENAM", "WEU"],
331
+ * httpConfig: {
332
+ * path: "/api/health",
333
+ * header: {
334
+ * "Host": ["api.example.com"],
335
+ * "X-Health-Check": ["true"]
336
+ * },
337
+ * followRedirects: true
338
+ * }
339
+ * });
340
+ */
341
+ export const HealthCheck = Resource(
342
+ "cloudflare::HealthCheck",
343
+ async function (
344
+ this: Context<HealthCheck>,
345
+ id: string,
346
+ props: HealthCheckProps,
347
+ ): Promise<HealthCheck> {
348
+ const zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
349
+ const adopt = props.adopt ?? this.scope.adopt;
350
+ const name =
351
+ props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
352
+
353
+ const api = await createCloudflareApi(props);
354
+
355
+ if (this.phase === "delete") {
356
+ const healthCheckId = this.output?.healthCheckId;
357
+ if (!healthCheckId) {
358
+ logger.warn(`No healthCheckId found for ${id}, skipping delete`);
359
+ return this.destroy();
360
+ }
361
+
362
+ await deleteHealthCheck(api, zoneId, healthCheckId, id);
363
+ return this.destroy();
364
+ }
365
+
366
+ const requestBody = prepareRequestBody(props, name);
367
+ let result: HealthCheckResponse;
368
+
369
+ if (this.output?.healthCheckId) {
370
+ // Update existing health check
371
+ result = await extractCloudflareResult<HealthCheckResponse>(
372
+ `update health check "${name}"`,
373
+ api.patch(
374
+ `/zones/${zoneId}/healthchecks/${this.output.healthCheckId}`,
375
+ requestBody,
376
+ ),
377
+ );
378
+ } else {
379
+ // Create new health check
380
+ try {
381
+ result = await extractCloudflareResult<HealthCheckResponse>(
382
+ `create health check "${name}"`,
383
+ api.post(`/zones/${zoneId}/healthchecks`, requestBody),
384
+ );
385
+ } catch (error) {
386
+ if (
387
+ error instanceof CloudflareApiError &&
388
+ (error.errorData as CloudflareApiErrorPayload[]).some(
389
+ (e) =>
390
+ e.code === 1003 ||
391
+ e.code === 1004 ||
392
+ e.message?.includes("already exists") ||
393
+ e.message?.includes("value not unique"),
394
+ )
395
+ ) {
396
+ if (!adopt) {
397
+ throw new Error(
398
+ `Health check "${name}" already exists. Use adopt: true to adopt it.`,
399
+ { cause: error },
400
+ );
401
+ }
402
+ const existing = await findHealthCheckByName(api, zoneId, name);
403
+ if (!existing) {
404
+ throw new Error(
405
+ `Health check "${name}" failed to create due to name conflict and could not be found for adoption.`,
406
+ { cause: error },
407
+ );
408
+ }
409
+ result = await extractCloudflareResult<HealthCheckResponse>(
410
+ `adopt health check "${name}"`,
411
+ api.patch(
412
+ `/zones/${zoneId}/healthchecks/${existing.id}`,
413
+ requestBody,
414
+ ),
415
+ );
416
+ } else {
417
+ throw error;
418
+ }
419
+ }
420
+ }
421
+
422
+ return {
423
+ id,
424
+ healthCheckId: result.id!,
425
+ zoneId,
426
+ address: result.address!,
427
+ name: result.name!,
428
+ checkRegions: result.check_regions,
429
+ consecutiveFails: result.consecutive_fails,
430
+ consecutiveSuccesses: result.consecutive_successes,
431
+ description: result.description,
432
+ httpConfig: result.http_config
433
+ ? mapHttpConfigFromApi(result.http_config)
434
+ : undefined,
435
+ interval: result.interval,
436
+ retries: result.retries,
437
+ suspended: result.suspended,
438
+ tcpConfig: result.tcp_config
439
+ ? mapTcpConfigFromApi(result.tcp_config)
440
+ : undefined,
441
+ timeout: result.timeout,
442
+ type: result.type,
443
+ createdOn: result.created_on,
444
+ modifiedOn: result.modified_on,
445
+ status: result.status,
446
+ failureReason: result.failure_reason,
447
+ accountId: props.accountId,
448
+ apiToken: props.apiToken,
449
+ };
450
+ },
451
+ );
452
+
453
+ /**
454
+ * API response format (snake_case from Cloudflare API)
455
+ * @internal
456
+ */
457
+ interface HealthCheckResponse {
458
+ id?: string;
459
+ address?: string;
460
+ name?: string;
461
+ check_regions?: CheckRegion[];
462
+ consecutive_fails?: number;
463
+ consecutive_successes?: number;
464
+ created_on?: string;
465
+ description?: string;
466
+ failure_reason?: string;
467
+ http_config?: {
468
+ allow_insecure?: boolean;
469
+ expected_body?: string;
470
+ expected_codes?: string[];
471
+ follow_redirects?: boolean;
472
+ header?: Record<string, string[]>;
473
+ method?: "GET" | "HEAD";
474
+ path?: string;
475
+ port?: number;
476
+ };
477
+ interval?: number;
478
+ modified_on?: string;
479
+ retries?: number;
480
+ status?: "unknown" | "healthy" | "unhealthy" | "suspended";
481
+ suspended?: boolean;
482
+ tcp_config?: {
483
+ method?: "connection_established";
484
+ port?: number;
485
+ };
486
+ timeout?: number;
487
+ type?: string;
488
+ }
489
+
490
+ /**
491
+ * Find a health check by name
492
+ * @internal
493
+ */
494
+ async function findHealthCheckByName(
495
+ api: CloudflareApi,
496
+ zoneId: string,
497
+ name: string,
498
+ page = 1,
499
+ ): Promise<HealthCheckResponse | undefined> {
500
+ const response = await api.get(
501
+ `/zones/${zoneId}/healthchecks?page=${page}&per_page=50`,
502
+ );
503
+ const data: {
504
+ result: HealthCheckResponse[];
505
+ result_info?: { total_pages?: number };
506
+ } = await response.json();
507
+ const found = data.result.find((check) => check.name === name);
508
+ if (found) {
509
+ return found;
510
+ }
511
+ if (data.result_info?.total_pages && page < data.result_info.total_pages) {
512
+ return await findHealthCheckByName(api, zoneId, name, page + 1);
513
+ }
514
+ return undefined;
515
+ }
516
+
517
+ /**
518
+ * Prepare the request body by converting to snake_case
519
+ * @internal
520
+ */
521
+ function prepareRequestBody(props: HealthCheckProps, name: string): any {
522
+ const body: any = {
523
+ address: props.address,
524
+ name: name,
525
+ };
526
+
527
+ if (props.checkRegions !== undefined) {
528
+ body.check_regions = props.checkRegions;
529
+ }
530
+ if (props.consecutiveFails !== undefined) {
531
+ body.consecutive_fails = props.consecutiveFails;
532
+ }
533
+ if (props.consecutiveSuccesses !== undefined) {
534
+ body.consecutive_successes = props.consecutiveSuccesses;
535
+ }
536
+ if (props.description !== undefined) {
537
+ body.description = props.description;
538
+ }
539
+ if (props.httpConfig !== undefined) {
540
+ body.http_config = props.httpConfig
541
+ ? {
542
+ allow_insecure: props.httpConfig.allowInsecure,
543
+ expected_body: props.httpConfig.expectedBody,
544
+ expected_codes: props.httpConfig.expectedCodes,
545
+ follow_redirects: props.httpConfig.followRedirects,
546
+ header: props.httpConfig.header
547
+ ? Object.fromEntries(
548
+ Object.entries(props.httpConfig.header).map(([key, values]) => [
549
+ key,
550
+ values.map((v) => SecretClass.unwrap(v)),
551
+ ]),
552
+ )
553
+ : undefined,
554
+ method: props.httpConfig.method,
555
+ path: props.httpConfig.path,
556
+ port: props.httpConfig.port,
557
+ }
558
+ : undefined;
559
+ }
560
+ if (props.interval !== undefined) {
561
+ body.interval = props.interval;
562
+ }
563
+ if (props.retries !== undefined) {
564
+ body.retries = props.retries;
565
+ }
566
+ if (props.suspended !== undefined) {
567
+ body.suspended = props.suspended;
568
+ }
569
+ if (props.tcpConfig !== undefined) {
570
+ body.tcp_config = props.tcpConfig
571
+ ? {
572
+ method: props.tcpConfig.method,
573
+ port: props.tcpConfig.port,
574
+ }
575
+ : undefined;
576
+ }
577
+ if (props.timeout !== undefined) {
578
+ body.timeout = props.timeout;
579
+ }
580
+ if (props.type !== undefined) {
581
+ body.type = props.type;
582
+ }
583
+
584
+ return body;
585
+ }
586
+
587
+ /**
588
+ * Map HTTP config from API response (snake_case) to camelCase
589
+ * @internal
590
+ */
591
+ function mapHttpConfigFromApi(
592
+ apiConfig: NonNullable<HealthCheckResponse["http_config"]>,
593
+ ): HTTPConfiguration {
594
+ return {
595
+ allowInsecure: apiConfig.allow_insecure,
596
+ expectedBody: apiConfig.expected_body,
597
+ expectedCodes: apiConfig.expected_codes,
598
+ followRedirects: apiConfig.follow_redirects,
599
+ header: apiConfig.header,
600
+ method: apiConfig.method,
601
+ path: apiConfig.path,
602
+ port: apiConfig.port,
603
+ };
604
+ }
605
+
606
+ /**
607
+ * Map TCP config from API response (snake_case) to camelCase
608
+ * @internal
609
+ */
610
+ function mapTcpConfigFromApi(
611
+ apiConfig: NonNullable<HealthCheckResponse["tcp_config"]>,
612
+ ): TCPConfiguration {
613
+ return {
614
+ method: apiConfig.method,
615
+ port: apiConfig.port,
616
+ };
617
+ }
@@ -32,6 +32,7 @@ export * from "./email-address.ts";
32
32
  export * from "./email-catch-all.ts";
33
33
  export * from "./email-routing.ts";
34
34
  export * from "./email-rule.ts";
35
+ export * from "./health-check.ts";
35
36
  export * from "./hyperdrive-ref.ts";
36
37
  export * from "./hyperdrive.ts";
37
38
  export * from "./images.ts";