alchemy 0.49.1 → 0.51.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 (92) hide show
  1. package/lib/apply.js +10 -9
  2. package/lib/apply.js.map +1 -1
  3. package/lib/aws/control/types.d.ts +4 -4
  4. package/lib/cloudflare/api-gateway-operation.d.ts +141 -0
  5. package/lib/cloudflare/api-gateway-operation.d.ts.map +1 -0
  6. package/lib/cloudflare/api-gateway-operation.js +153 -0
  7. package/lib/cloudflare/api-gateway-operation.js.map +1 -0
  8. package/lib/cloudflare/api-mitigation.d.ts +19 -0
  9. package/lib/cloudflare/api-mitigation.d.ts.map +1 -0
  10. package/lib/cloudflare/api-mitigation.js +1 -0
  11. package/lib/cloudflare/api-mitigation.js.map +1 -0
  12. package/lib/cloudflare/api-response.d.ts +2 -2
  13. package/lib/cloudflare/api-response.d.ts.map +1 -1
  14. package/lib/cloudflare/api-response.js +2 -1
  15. package/lib/cloudflare/api-response.js.map +1 -1
  16. package/lib/cloudflare/api-schema.d.ts +137 -0
  17. package/lib/cloudflare/api-schema.d.ts.map +1 -0
  18. package/lib/cloudflare/api-schema.js +197 -0
  19. package/lib/cloudflare/api-schema.js.map +1 -0
  20. package/lib/cloudflare/api-shield.d.ts +366 -0
  21. package/lib/cloudflare/api-shield.d.ts.map +1 -0
  22. package/lib/cloudflare/api-shield.js +427 -0
  23. package/lib/cloudflare/api-shield.js.map +1 -0
  24. package/lib/cloudflare/bucket.d.ts +70 -105
  25. package/lib/cloudflare/bucket.d.ts.map +1 -1
  26. package/lib/cloudflare/bucket.js +127 -244
  27. package/lib/cloudflare/bucket.js.map +1 -1
  28. package/lib/cloudflare/certificate-pack.d.ts +1 -1
  29. package/lib/cloudflare/certificate-pack.d.ts.map +1 -1
  30. package/lib/cloudflare/certificate-pack.js +18 -88
  31. package/lib/cloudflare/certificate-pack.js.map +1 -1
  32. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  33. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  34. package/lib/cloudflare/index.d.ts +3 -0
  35. package/lib/cloudflare/index.d.ts.map +1 -1
  36. package/lib/cloudflare/index.js +3 -0
  37. package/lib/cloudflare/index.js.map +1 -1
  38. package/lib/cloudflare/nuxt.js +1 -1
  39. package/lib/cloudflare/nuxt.js.map +1 -1
  40. package/lib/cloudflare/orange.d.ts.map +1 -1
  41. package/lib/cloudflare/orange.js +4 -1
  42. package/lib/cloudflare/orange.js.map +1 -1
  43. package/lib/cloudflare/redwood.d.ts.map +1 -1
  44. package/lib/cloudflare/redwood.js +5 -1
  45. package/lib/cloudflare/redwood.js.map +1 -1
  46. package/lib/cloudflare/vite.d.ts.map +1 -1
  47. package/lib/cloudflare/vite.js +2 -10
  48. package/lib/cloudflare/vite.js.map +1 -1
  49. package/lib/cloudflare/website.d.ts.map +1 -1
  50. package/lib/cloudflare/website.js +1 -0
  51. package/lib/cloudflare/website.js.map +1 -1
  52. package/lib/cloudflare/worker.d.ts.map +1 -1
  53. package/lib/cloudflare/worker.js +1 -0
  54. package/lib/cloudflare/worker.js.map +1 -1
  55. package/lib/cloudflare/zone.d.ts +12 -0
  56. package/lib/cloudflare/zone.d.ts.map +1 -1
  57. package/lib/cloudflare/zone.js +35 -0
  58. package/lib/cloudflare/zone.js.map +1 -1
  59. package/lib/destroy.d.ts.map +1 -1
  60. package/lib/destroy.js +26 -18
  61. package/lib/destroy.js.map +1 -1
  62. package/lib/os/exec.d.ts +1 -1
  63. package/lib/os/exec.d.ts.map +1 -1
  64. package/lib/os/exec.js +1 -1
  65. package/lib/os/exec.js.map +1 -1
  66. package/lib/scope.d.ts.map +1 -1
  67. package/lib/scope.js +2 -2
  68. package/lib/scope.js.map +1 -1
  69. package/package.json +2 -1
  70. package/src/apply.ts +11 -12
  71. package/src/aws/control/types.ts +4 -4
  72. package/src/cloudflare/api-gateway-operation.ts +340 -0
  73. package/src/cloudflare/api-mitigation.ts +22 -0
  74. package/src/cloudflare/api-response.ts +7 -3
  75. package/src/cloudflare/api-schema.ts +364 -0
  76. package/src/cloudflare/api-shield.ts +676 -0
  77. package/src/cloudflare/bucket.ts +276 -468
  78. package/src/cloudflare/certificate-pack.ts +28 -152
  79. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  80. package/src/cloudflare/index.ts +3 -0
  81. package/src/cloudflare/nuxt.ts +1 -1
  82. package/src/cloudflare/orange.ts +4 -1
  83. package/src/cloudflare/redwood.ts +6 -1
  84. package/src/cloudflare/vite.ts +2 -10
  85. package/src/cloudflare/website.ts +1 -0
  86. package/src/cloudflare/worker.ts +2 -1
  87. package/src/cloudflare/zone.ts +52 -0
  88. package/src/destroy.ts +26 -20
  89. package/src/os/exec.ts +2 -2
  90. package/src/scope.ts +3 -3
  91. package/templates/tanstack-start/package.json +3 -3
  92. package/templates/tanstack-start/vite.config.ts +2 -6
package/src/apply.ts CHANGED
@@ -223,6 +223,14 @@ async function _apply<Out extends Resource>(
223
223
  output: resource,
224
224
  },
225
225
  });
226
+ } else {
227
+ const pendingDeletions =
228
+ (await scope.get<PendingDeletions>("pendingDeletions")) ?? [];
229
+ pendingDeletions.push({
230
+ resource: oldOutput,
231
+ oldProps: state.oldProps,
232
+ });
233
+ await scope.set("pendingDeletions", pendingDeletions);
226
234
  }
227
235
 
228
236
  output = await alchemy.run(
@@ -237,8 +245,8 @@ async function _apply<Out extends Resource>(
237
245
  id: resource[ResourceID],
238
246
  fqn: resource[ResourceFQN],
239
247
  seq: resource[ResourceSeq],
240
- props: state.props,
241
- state,
248
+ props: state!.props,
249
+ state: state!,
242
250
  isReplacement: true,
243
251
  replace: () => {
244
252
  throw new Error(
@@ -248,16 +256,6 @@ async function _apply<Out extends Resource>(
248
256
  }),
249
257
  )(resource[ResourceID], props),
250
258
  );
251
-
252
- if (!error.force) {
253
- const pendingDeletions =
254
- (await scope.get<PendingDeletions>("pendingDeletions")) ?? [];
255
- pendingDeletions.push({
256
- resource: oldOutput,
257
- oldProps: state.oldProps,
258
- });
259
- await scope.set("pendingDeletions", pendingDeletions);
260
- }
261
259
  } else {
262
260
  throw error;
263
261
  }
@@ -282,6 +280,7 @@ async function _apply<Out extends Resource>(
282
280
  replaced: isReplaced,
283
281
  });
284
282
 
283
+ state = (await scope.state.get(resource[ResourceID]))!;
285
284
  await scope.state.set(resource[ResourceID], {
286
285
  kind: resource[ResourceKind],
287
286
  id: resource[ResourceID],
@@ -9717,7 +9717,7 @@ declare namespace AWS {
9717
9717
  ): Promise<SchedulingPolicy>;
9718
9718
  }
9719
9719
  namespace Bedrock {
9720
- interface APISchema {
9720
+ interface ApiSchema {
9721
9721
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-apischema.html#cfn-bedrock-agent-apischema-s3 */
9722
9722
  S3?: S3Identifier;
9723
9723
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-apischema.html#cfn-bedrock-agent-apischema-payload */
@@ -9733,7 +9733,7 @@ declare namespace AWS {
9733
9733
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-agentactiongroup.html#cfn-bedrock-agent-agentactiongroup-description */
9734
9734
  Description?: string;
9735
9735
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-agentactiongroup.html#cfn-bedrock-agent-agentactiongroup-apischema */
9736
- ApiSchema?: APISchema;
9736
+ ApiSchema?: ApiSchema;
9737
9737
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-agentactiongroup.html#cfn-bedrock-agent-agentactiongroup-functionschema */
9738
9738
  FunctionSchema?: FunctionSchema;
9739
9739
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-bedrock-agent-agentactiongroup.html#cfn-bedrock-agent-agentactiongroup-skipresourceinusecheckondelete */
@@ -61212,7 +61212,7 @@ declare namespace AWS {
61212
61212
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-index-textdocumentstatistics.html#cfn-qbusiness-index-textdocumentstatistics-indexedtextdocumentcount */
61213
61213
  IndexedTextDocumentCount?: number;
61214
61214
  }
61215
- interface APISchema {
61215
+ interface ApiSchema {
61216
61216
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-plugin-apischema.html#cfn-qbusiness-plugin-apischema-s3 */
61217
61217
  S3?: S3;
61218
61218
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-plugin-apischema.html#cfn-qbusiness-plugin-apischema-payload */
@@ -61228,7 +61228,7 @@ declare namespace AWS {
61228
61228
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-plugin-custompluginconfiguration.html#cfn-qbusiness-plugin-custompluginconfiguration-description */
61229
61229
  Description: string;
61230
61230
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-plugin-custompluginconfiguration.html#cfn-qbusiness-plugin-custompluginconfiguration-apischema */
61231
- ApiSchema: APISchema;
61231
+ ApiSchema: ApiSchema;
61232
61232
  /** http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-qbusiness-plugin-custompluginconfiguration.html#cfn-qbusiness-plugin-custompluginconfiguration-apischematype */
61233
61233
  ApiSchemaType: string;
61234
61234
  }
@@ -0,0 +1,340 @@
1
+ import type { Context } from "../context.ts";
2
+ import { Resource } from "../resource.ts";
3
+ import { handleApiError } from "./api-error.ts";
4
+ import type { APIMitigation } from "./api-mitigation.ts";
5
+ import {
6
+ createCloudflareApi,
7
+ type CloudflareApi,
8
+ type CloudflareApiOptions,
9
+ } from "./api.ts";
10
+ import type { Zone } from "./zone.ts";
11
+
12
+ /**
13
+ * HTTP methods supported by API Gateway
14
+ */
15
+ export type HTTPMethod =
16
+ | "get"
17
+ | "post"
18
+ | "put"
19
+ | "patch"
20
+ | "delete"
21
+ | "head"
22
+ | "options"
23
+ | "trace";
24
+
25
+ /**
26
+ * Properties for creating or updating an API Operation
27
+ */
28
+ export interface APIGatewayOperationProps extends CloudflareApiOptions {
29
+ /**
30
+ * The zone this operation belongs to
31
+ */
32
+ zone: string | Zone;
33
+
34
+ /**
35
+ * The API endpoint path (can contain path variables like /users/{id})
36
+ */
37
+ endpoint: string;
38
+
39
+ /**
40
+ * The host for this operation
41
+ */
42
+ host: string;
43
+
44
+ /**
45
+ * The HTTP method (GET, POST, PUT, DELETE, etc.)
46
+ */
47
+ method: string;
48
+
49
+ /**
50
+ * Mitigation action for this operation
51
+ * @default "none"
52
+ */
53
+ mitigation?: APIMitigation;
54
+ }
55
+
56
+ /**
57
+ * API Operation output
58
+ */
59
+ export interface APIGatewayOperation
60
+ extends Resource<"cloudflare::APIGatewayOperation"> {
61
+ /**
62
+ * Zone ID
63
+ */
64
+ zoneId: string;
65
+
66
+ /**
67
+ * Zone name
68
+ */
69
+ zoneName: string;
70
+
71
+ /**
72
+ * Operation ID assigned by Cloudflare
73
+ */
74
+ operationId: string;
75
+
76
+ /**
77
+ * The API endpoint path
78
+ */
79
+ endpoint: string;
80
+
81
+ /**
82
+ * The host for this operation
83
+ */
84
+ host: string;
85
+
86
+ /**
87
+ * The HTTP method
88
+ */
89
+ method: string;
90
+
91
+ /**
92
+ * The mitigation action for this operation
93
+ */
94
+ action: APIMitigation;
95
+ }
96
+
97
+ /**
98
+ * Cloudflare API Gateway Operation manages individual API endpoints that can be
99
+ * monitored, secured, and configured through Cloudflare's API Shield.
100
+ *
101
+ * Operations are the building blocks for API management, representing specific
102
+ * HTTP method + endpoint + host combinations that your API exposes.
103
+ *
104
+ * @example
105
+ * ## Basic API operation
106
+ *
107
+ * Create a simple GET endpoint for user retrieval
108
+ *
109
+ * const getUserOp = await APIGatewayOperation("get-users", {
110
+ * zone: myZone,
111
+ * endpoint: "/users",
112
+ * host: "api.example.com",
113
+ * method: "GET"
114
+ * });
115
+ *
116
+ * @example
117
+ * ## API operation with path parameters
118
+ *
119
+ * Create an operation that includes path variables
120
+ *
121
+ * const getUserByIdOp = await APIGatewayOperation("get-user-by-id", {
122
+ * zone: "api.example.com",
123
+ * endpoint: "/users/{id}",
124
+ * host: "api.example.com",
125
+ * method: "GET"
126
+ * });
127
+ *
128
+ * @example
129
+ * ## RESTful CRUD operations
130
+ *
131
+ * Create a complete set of CRUD operations for a resource
132
+ *
133
+ * const createUserOp = await APIGatewayOperation("create-user", {
134
+ * zone: myZone,
135
+ * endpoint: "/users",
136
+ * host: "api.example.com",
137
+ * method: "POST"
138
+ * });
139
+ *
140
+ * const updateUserOp = await APIGatewayOperation("update-user", {
141
+ * zone: myZone,
142
+ * endpoint: "/users/{id}",
143
+ * host: "api.example.com",
144
+ * method: "PUT"
145
+ * });
146
+ *
147
+ * const deleteUserOp = await APIGatewayOperation("delete-user", {
148
+ * zone: myZone,
149
+ * endpoint: "/users/{id}",
150
+ * host: "api.example.com",
151
+ * method: "DELETE"
152
+ * });
153
+ *
154
+ * @see https://developers.cloudflare.com/api/resources/api_gateway/subresources/operations/
155
+ * @see https://developers.cloudflare.com/api-shield/management-and-monitoring/endpoint-management/
156
+ */
157
+ export const APIGatewayOperation = Resource(
158
+ "cloudflare::APIGatewayOperation",
159
+ async function (
160
+ this: Context<APIGatewayOperation>,
161
+ _id: string,
162
+ props: APIGatewayOperationProps,
163
+ ): Promise<APIGatewayOperation> {
164
+ const api = await createCloudflareApi(props);
165
+
166
+ // Resolve zone ID and name
167
+ const zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
168
+ const zoneName =
169
+ typeof props.zone === "string" ? props.zone : props.zone.name;
170
+
171
+ if (this.phase === "delete") {
172
+ if (this.output?.operationId) {
173
+ await deleteOperation(api, zoneId, this.output.operationId);
174
+ }
175
+ return this.destroy();
176
+ }
177
+ let operationId: string;
178
+ if (this.phase === "update") {
179
+ if (
180
+ props.endpoint !== this.output?.endpoint ||
181
+ props.host !== this.output?.host ||
182
+ props.method !== this.output?.method
183
+ ) {
184
+ // delete and then re-create (cloudflare doesn't allow updating these properties)
185
+ this.replace(true);
186
+ }
187
+ if (props.mitigation !== this.output?.action) {
188
+ // we can update the mitigation action
189
+ await updateOperationSettings(api, zoneId, this.output.operationId, {
190
+ mitigation_action: props.mitigation ?? null,
191
+ });
192
+ }
193
+ operationId = this.output.operationId;
194
+ } else {
195
+ const operation = await createOperation(api, zoneId, props);
196
+ operationId = operation.operation_id;
197
+
198
+ if (props.mitigation) {
199
+ await updateOperationSettings(api, zoneId, operationId, {
200
+ mitigation_action: props.mitigation ?? "none",
201
+ });
202
+ }
203
+ }
204
+
205
+ return this({
206
+ zoneId,
207
+ zoneName,
208
+ operationId,
209
+ endpoint: props.endpoint,
210
+ host: props.host,
211
+ method: props.method.toUpperCase(),
212
+ action: props.mitigation ?? null,
213
+ });
214
+ },
215
+ );
216
+
217
+ async function createOperation(
218
+ api: CloudflareApi,
219
+ zoneId: string,
220
+ props: APIGatewayOperationProps,
221
+ ) {
222
+ // Create the operation
223
+ const response = await api.post(
224
+ `/zones/${zoneId}/api_gateway/operations/item`,
225
+ {
226
+ endpoint: props.endpoint,
227
+ host: props.host,
228
+ method: props.method.toUpperCase(),
229
+ },
230
+ );
231
+
232
+ if (!response.ok) {
233
+ await handleApiError(
234
+ response,
235
+ "creating",
236
+ "operation",
237
+ `${props.method} ${props.endpoint}`,
238
+ );
239
+ }
240
+
241
+ const data = (await response.json()) as {
242
+ result: {
243
+ operation_id: string;
244
+ endpoint: string;
245
+ host: string;
246
+ method: string;
247
+ last_updated: string;
248
+ };
249
+ };
250
+
251
+ return data.result;
252
+ }
253
+
254
+ async function deleteOperation(
255
+ api: CloudflareApi,
256
+ zoneId: string,
257
+ operationId: string,
258
+ ) {
259
+ const deleteResponse = await api.delete(
260
+ `/zones/${zoneId}/api_gateway/operations/${operationId}`,
261
+ );
262
+
263
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
264
+ await handleApiError(deleteResponse, "deleting", "operation", operationId);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Update validation settings for a specific operation
270
+ */
271
+ async function updateOperationSettings(
272
+ api: CloudflareApi,
273
+ zoneId: string,
274
+ operationId: string,
275
+ params: {
276
+ mitigation_action: APIMitigation;
277
+ },
278
+ ): Promise<void> {
279
+ const response = await api.put(
280
+ `/zones/${zoneId}/schema_validation/settings/operations/${operationId}`,
281
+ params,
282
+ );
283
+
284
+ if (!response.ok) {
285
+ await handleApiError(
286
+ response,
287
+ "updating",
288
+ "operation settings",
289
+ operationId,
290
+ );
291
+ }
292
+ }
293
+
294
+ export interface CloudflareOperation {
295
+ operation_id: string;
296
+ method: string;
297
+ host: string;
298
+ endpoint: string;
299
+ }
300
+
301
+ export async function getOperations(
302
+ api: CloudflareApi,
303
+ zoneId: string,
304
+ ): Promise<CloudflareOperation[]> {
305
+ const response = await api.get(`/zones/${zoneId}/api_gateway/operations`);
306
+
307
+ if (!response.ok) {
308
+ await handleApiError(response, "getting", "operations");
309
+ }
310
+
311
+ return ((await response.json()) as { result: CloudflareOperation[] }).result;
312
+ }
313
+
314
+ export async function getOperationSchemaValidationSetting(
315
+ api: CloudflareApi,
316
+ zoneId: string,
317
+ operationId: string,
318
+ ) {
319
+ const response = await api.get(
320
+ `/zones/${zoneId}/schema_validation/settings/operations/${operationId}`,
321
+ );
322
+
323
+ if (!response.ok) {
324
+ await handleApiError(
325
+ response,
326
+ "getting",
327
+ "operation mitigation status",
328
+ operationId,
329
+ );
330
+ }
331
+
332
+ const data = (await response.json()) as {
333
+ result: {
334
+ mitigation_action: APIMitigation;
335
+ operation_id: string;
336
+ };
337
+ };
338
+
339
+ return data.result;
340
+ }
@@ -0,0 +1,22 @@
1
+ import type { OpenAPIV3 } from "openapi-types";
2
+
3
+ /**
4
+ * Specifies the mitigation action to apply when a request does not conform to
5
+ * the schema for this operation:
6
+ * - `"log"`: Log the request.
7
+ * - `"block"`: Deny access to the site.
8
+ * - `"none"`: Skip mitigation for this operation.
9
+ * - `null`: Clear any mitigation action.
10
+ */
11
+ export type APIMitigation = "none" | "log" | "block" | null;
12
+
13
+ /**
14
+ * Mitigation actions for a given API schema.
15
+ */
16
+ export type APIMitigations<S extends OpenAPIV3.Document> = {
17
+ [path in keyof S["paths"]]?:
18
+ | APIMitigation
19
+ | {
20
+ [method in keyof S["paths"][path]]?: APIMitigation;
21
+ };
22
+ };
@@ -1,3 +1,5 @@
1
+ import { CloudflareApiError } from "./api-error.ts";
2
+
1
3
  /**
2
4
  * Cloudflare API response format
3
5
  */
@@ -15,7 +17,7 @@ export interface CloudflareApiResponse<T> {
15
17
  /**
16
18
  * Error details if success is false
17
19
  */
18
- errors: CloudflareApiError[];
20
+ errors: CloudflareApiErrorPayload[];
19
21
 
20
22
  /**
21
23
  * Response messages
@@ -37,7 +39,7 @@ export interface CloudflareApiResponse<T> {
37
39
  /**
38
40
  * Cloudflare API error format
39
41
  */
40
- export interface CloudflareApiError {
42
+ export interface CloudflareApiErrorPayload {
41
43
  /**
42
44
  * Error code
43
45
  */
@@ -89,8 +91,10 @@ export async function extractCloudflareResult<T>(
89
91
  if (json.success) {
90
92
  return json.result;
91
93
  } else {
92
- const error = new Error(
94
+ const error = new CloudflareApiError(
93
95
  `Failed to ${label} (${response.status} ${response.statusText}):\n${json.errors.map((e) => `- [${e.code}] ${e.message}${e.documentation_url ? ` (${e.documentation_url})` : ""}`).join("\n")}`,
96
+ response,
97
+ json.errors,
94
98
  );
95
99
  Error.captureStackTrace(error, extractCloudflareResult);
96
100
  Object.assign(error, {