alchemy 0.70.2 → 0.71.1
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/README.md +1 -1
- package/bin/alchemy.js +131 -58
- package/bin/commands/init.ts +0 -10
- package/bin/services/execute-alchemy.ts +12 -3
- package/bin/trpc.ts +12 -12
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +21 -6
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +3 -1
- package/lib/apply.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +12 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -2
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts +6 -0
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +5 -3
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.js +29 -9
- package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-database.d.ts +1 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +1 -1
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/health-check.d.ts +261 -0
- package/lib/cloudflare/health-check.d.ts.map +1 -0
- package/lib/cloudflare/health-check.js +277 -0
- package/lib/cloudflare/health-check.js.map +1 -0
- package/lib/cloudflare/index.d.ts +3 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +3 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/logpush-job.d.ts +349 -0
- package/lib/cloudflare/logpush-job.d.ts.map +1 -0
- package/lib/cloudflare/logpush-job.js +277 -0
- package/lib/cloudflare/logpush-job.js.map +1 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +1 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +16 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +2 -0
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/queue-consumer.js +2 -2
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/worker-loader.d.ts +9 -0
- package/lib/cloudflare/worker-loader.d.ts.map +1 -0
- package/lib/cloudflare/worker-loader.js +6 -0
- package/lib/cloudflare/worker-loader.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +6 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +7 -0
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +11 -8
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +18 -11
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/cloudflare/zone.js +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +27 -0
- package/lib/destroy.js.map +1 -1
- package/lib/docker/image.js +1 -1
- package/lib/docker/image.js.map +1 -1
- package/lib/github/comment.d.ts.map +1 -1
- package/lib/github/comment.js +14 -7
- package/lib/github/comment.js.map +1 -1
- package/lib/resource.js +1 -1
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +6 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +22 -22
- package/lib/scope.js.map +1 -1
- package/lib/state/instrumented-state-store.js +1 -1
- package/lib/state/instrumented-state-store.js.map +1 -1
- package/lib/util/cli-args.d.ts +3 -0
- package/lib/util/cli-args.d.ts.map +1 -0
- package/lib/util/cli-args.js +6 -0
- package/lib/util/cli-args.js.map +1 -0
- package/lib/util/retry.d.ts +1 -1
- package/lib/util/retry.d.ts.map +1 -1
- package/lib/util/retry.js +2 -2
- package/lib/util/retry.js.map +1 -1
- package/lib/util/telemetry.d.ts +1 -0
- package/lib/util/telemetry.d.ts.map +1 -1
- package/lib/util/telemetry.js +47 -2
- package/lib/util/telemetry.js.map +1 -1
- package/lib/util/validate-resource-id.d.ts +2 -0
- package/lib/util/validate-resource-id.d.ts.map +1 -0
- package/lib/util/validate-resource-id.js +11 -0
- package/lib/util/validate-resource-id.js.map +1 -0
- package/package.json +11 -9
- package/src/alchemy.ts +25 -11
- package/src/apply.ts +3 -1
- package/src/cloudflare/bindings.ts +13 -0
- package/src/cloudflare/bound.ts +36 -32
- package/src/cloudflare/bucket.ts +13 -4
- package/src/cloudflare/bun-spa/bun-spa.ts +42 -10
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-database.ts +2 -2
- package/src/cloudflare/health-check.ts +617 -0
- package/src/cloudflare/index.ts +3 -0
- package/src/cloudflare/logpush-job.ts +768 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +18 -6
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +2 -0
- package/src/cloudflare/queue-consumer.ts +2 -2
- package/src/cloudflare/worker-loader.ts +12 -0
- package/src/cloudflare/worker-metadata.ts +5 -0
- package/src/cloudflare/worker-subdomain.ts +1 -1
- package/src/cloudflare/worker.ts +8 -0
- package/src/cloudflare/wrangler.json.ts +36 -23
- package/src/cloudflare/zone.ts +1 -1
- package/src/destroy.ts +32 -0
- package/src/docker/image.ts +1 -1
- package/src/github/comment.ts +17 -8
- package/src/resource.ts +1 -1
- package/src/scope.ts +32 -25
- package/src/state/instrumented-state-store.ts +1 -1
- package/src/util/cli-args.ts +11 -0
- package/src/util/retry.ts +2 -1
- package/src/util/telemetry.ts +68 -2
- package/src/util/validate-resource-id.ts +10 -0
- 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
|
+
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -32,10 +32,12 @@ 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";
|
|
38
39
|
export * from "./kv-namespace.ts";
|
|
40
|
+
export * from "./logpush-job.ts";
|
|
39
41
|
export * from "./next.ts";
|
|
40
42
|
export * from "./nuxt/nuxt.ts";
|
|
41
43
|
export * from "./orange.ts";
|
|
@@ -69,5 +71,6 @@ export * from "./worker-stub.ts";
|
|
|
69
71
|
export * from "./worker-subdomain.ts";
|
|
70
72
|
export * from "./worker.ts";
|
|
71
73
|
export { Workflow } from "./workflow.ts";
|
|
74
|
+
export { WorkerLoader } from "./worker-loader.ts";
|
|
72
75
|
export * from "./wrangler.json.ts";
|
|
73
76
|
export * from "./zone.ts";
|