alchemy 0.53.0 → 0.55.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 (214) hide show
  1. package/bin/alchemy.js +196353 -0
  2. package/bin/alchemy.ts +2 -2
  3. package/bin/commands/init.ts +1190 -0
  4. package/bin/constants.ts +18 -0
  5. package/bin/services/execute-alchemy.ts +7 -2
  6. package/bin/services/package-manager.ts +1 -1
  7. package/bin/types.ts +14 -5
  8. package/lib/aws/ec2/index.d.ts +10 -0
  9. package/lib/aws/ec2/index.d.ts.map +1 -0
  10. package/lib/aws/ec2/index.js +10 -0
  11. package/lib/aws/ec2/index.js.map +1 -0
  12. package/lib/aws/ec2/internet-gateway-attachment.d.ts +150 -0
  13. package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -0
  14. package/lib/aws/ec2/internet-gateway-attachment.js +248 -0
  15. package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -0
  16. package/lib/aws/ec2/internet-gateway.d.ts +132 -0
  17. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
  18. package/lib/aws/ec2/internet-gateway.js +311 -0
  19. package/lib/aws/ec2/internet-gateway.js.map +1 -0
  20. package/lib/aws/ec2/nat-gateway.d.ts +145 -0
  21. package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
  22. package/lib/aws/ec2/nat-gateway.js +618 -0
  23. package/lib/aws/ec2/nat-gateway.js.map +1 -0
  24. package/lib/aws/ec2/route-table-association.d.ts +134 -0
  25. package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
  26. package/lib/aws/ec2/route-table-association.js +364 -0
  27. package/lib/aws/ec2/route-table-association.js.map +1 -0
  28. package/lib/aws/ec2/route-table.d.ts +94 -0
  29. package/lib/aws/ec2/route-table.d.ts.map +1 -0
  30. package/lib/aws/ec2/route-table.js +253 -0
  31. package/lib/aws/ec2/route-table.js.map +1 -0
  32. package/lib/aws/ec2/route.d.ts +130 -0
  33. package/lib/aws/ec2/route.d.ts.map +1 -0
  34. package/lib/aws/ec2/route.js +253 -0
  35. package/lib/aws/ec2/route.js.map +1 -0
  36. package/lib/aws/ec2/security-group-rule.d.ts +84 -0
  37. package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
  38. package/lib/aws/ec2/security-group-rule.js +123 -0
  39. package/lib/aws/ec2/security-group-rule.js.map +1 -0
  40. package/lib/aws/ec2/security-group.d.ts +84 -0
  41. package/lib/aws/ec2/security-group.d.ts.map +1 -0
  42. package/lib/aws/ec2/security-group.js +207 -0
  43. package/lib/aws/ec2/security-group.js.map +1 -0
  44. package/lib/aws/ec2/subnet.d.ts +110 -0
  45. package/lib/aws/ec2/subnet.d.ts.map +1 -0
  46. package/lib/aws/ec2/subnet.js +336 -0
  47. package/lib/aws/ec2/subnet.js.map +1 -0
  48. package/lib/aws/ec2/utils.d.ts +15 -0
  49. package/lib/aws/ec2/utils.d.ts.map +1 -0
  50. package/lib/aws/ec2/utils.js +79 -0
  51. package/lib/aws/ec2/utils.js.map +1 -0
  52. package/lib/aws/ec2/vpc.d.ts +160 -0
  53. package/lib/aws/ec2/vpc.d.ts.map +1 -0
  54. package/lib/aws/ec2/vpc.js +453 -0
  55. package/lib/aws/ec2/vpc.js.map +1 -0
  56. package/lib/aws/index.d.ts +7 -0
  57. package/lib/aws/index.d.ts.map +1 -1
  58. package/lib/aws/index.js +8 -0
  59. package/lib/aws/index.js.map +1 -1
  60. package/lib/aws/utils.d.ts +25 -0
  61. package/lib/aws/utils.d.ts.map +1 -0
  62. package/lib/aws/utils.js +89 -0
  63. package/lib/aws/utils.js.map +1 -0
  64. package/lib/build-date.d.ts +6 -0
  65. package/lib/build-date.d.ts.map +1 -0
  66. package/lib/build-date.js +8 -0
  67. package/lib/build-date.js.map +1 -0
  68. package/lib/cloudflare/astro.d.ts.map +1 -1
  69. package/lib/cloudflare/astro.js +4 -3
  70. package/lib/cloudflare/astro.js.map +1 -1
  71. package/lib/cloudflare/bundle/externals.d.ts +3 -0
  72. package/lib/cloudflare/bundle/externals.d.ts.map +1 -0
  73. package/lib/cloudflare/bundle/externals.js +35 -0
  74. package/lib/cloudflare/bundle/externals.js.map +1 -0
  75. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +8 -1
  76. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
  77. package/lib/cloudflare/bundle/internal-worker-bundle.js +11 -4
  78. package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
  79. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +4 -1
  80. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
  81. package/lib/cloudflare/bundle/plugin-hot-reload.js +27 -11
  82. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
  83. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +5 -1
  84. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
  85. package/lib/cloudflare/bundle/plugin-wasm.d.ts +6 -2
  86. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  87. package/lib/cloudflare/bundle/plugin-wasm.js +21 -47
  88. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  89. package/lib/cloudflare/bundle/validate-node-compat.d.ts +2 -1
  90. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -1
  91. package/lib/cloudflare/bundle/validate-node-compat.js +5 -4
  92. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -1
  93. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  94. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  95. package/lib/cloudflare/d1-database.js +1 -1
  96. package/lib/cloudflare/d1-database.js.map +1 -1
  97. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  98. package/lib/cloudflare/d1-migrations.js +8 -10
  99. package/lib/cloudflare/d1-migrations.js.map +1 -1
  100. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +3 -3
  101. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
  102. package/lib/cloudflare/miniflare/miniflare-worker-options.js +23 -6
  103. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
  104. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
  105. package/lib/cloudflare/miniflare/miniflare.js +2 -4
  106. package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
  107. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  108. package/lib/cloudflare/miniflare/remote-binding-proxy.js +4 -6
  109. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  110. package/lib/cloudflare/react-router.d.ts +4 -0
  111. package/lib/cloudflare/react-router.d.ts.map +1 -1
  112. package/lib/cloudflare/react-router.js +8 -5
  113. package/lib/cloudflare/react-router.js.map +1 -1
  114. package/lib/cloudflare/worker-bundle.d.ts +80 -0
  115. package/lib/cloudflare/worker-bundle.d.ts.map +1 -0
  116. package/lib/cloudflare/worker-bundle.js +266 -0
  117. package/lib/cloudflare/worker-bundle.js.map +1 -0
  118. package/lib/cloudflare/worker.d.ts +1 -1
  119. package/lib/cloudflare/worker.d.ts.map +1 -1
  120. package/lib/cloudflare/worker.js +81 -195
  121. package/lib/cloudflare/worker.js.map +1 -1
  122. package/lib/esbuild/bundle.d.ts +1 -0
  123. package/lib/esbuild/bundle.d.ts.map +1 -1
  124. package/lib/state/cloudflare-state-store.js +1 -5
  125. package/lib/state/cloudflare-state-store.js.map +1 -1
  126. package/lib/state/dofs-state-store/store.d.ts.map +1 -1
  127. package/lib/state/dofs-state-store/store.js +4 -4
  128. package/lib/state/dofs-state-store/store.js.map +1 -1
  129. package/lib/util/params.d.ts +5 -0
  130. package/lib/util/params.d.ts.map +1 -0
  131. package/lib/util/params.js +30 -0
  132. package/lib/util/params.js.map +1 -0
  133. package/lib/util/timeout.d.ts +31 -0
  134. package/lib/util/timeout.d.ts.map +1 -0
  135. package/lib/util/timeout.js +109 -0
  136. package/lib/util/timeout.js.map +1 -0
  137. package/lib/util/xml.d.ts +5 -0
  138. package/lib/util/xml.d.ts.map +1 -0
  139. package/lib/util/xml.js +8 -0
  140. package/lib/util/xml.js.map +1 -0
  141. package/package.json +12 -5
  142. package/src/aws/ec2/index.ts +9 -0
  143. package/src/aws/ec2/internet-gateway-attachment.ts +468 -0
  144. package/src/aws/ec2/internet-gateway.ts +557 -0
  145. package/src/aws/ec2/nat-gateway.ts +1123 -0
  146. package/src/aws/ec2/route-table-association.ts +747 -0
  147. package/src/aws/ec2/route-table.ts +452 -0
  148. package/src/aws/ec2/route.ts +447 -0
  149. package/src/aws/ec2/security-group-rule.ts +229 -0
  150. package/src/aws/ec2/security-group.ts +418 -0
  151. package/src/aws/ec2/subnet.ts +625 -0
  152. package/src/aws/ec2/utils.ts +102 -0
  153. package/src/aws/ec2/vpc.ts +969 -0
  154. package/src/aws/index.ts +9 -0
  155. package/src/aws/utils.ts +137 -0
  156. package/src/build-date.ts +8 -0
  157. package/src/cloudflare/astro.ts +4 -3
  158. package/src/cloudflare/bundle/externals.ts +36 -0
  159. package/src/cloudflare/bundle/internal-worker-bundle.ts +12 -3
  160. package/src/cloudflare/bundle/plugin-hot-reload.ts +32 -12
  161. package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +6 -1
  162. package/src/cloudflare/bundle/plugin-wasm.ts +26 -54
  163. package/src/cloudflare/bundle/validate-node-compat.ts +6 -5
  164. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  165. package/src/cloudflare/d1-database.ts +1 -1
  166. package/src/cloudflare/d1-migrations.ts +19 -18
  167. package/src/cloudflare/miniflare/miniflare-worker-options.ts +27 -13
  168. package/src/cloudflare/miniflare/miniflare.ts +2 -4
  169. package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -7
  170. package/src/cloudflare/react-router.ts +14 -6
  171. package/src/cloudflare/worker-bundle.ts +364 -0
  172. package/src/cloudflare/worker.ts +104 -241
  173. package/src/state/cloudflare-state-store.ts +1 -5
  174. package/src/state/dofs-state-store/store.ts +4 -4
  175. package/src/util/params.ts +33 -0
  176. package/src/util/timeout.ts +178 -0
  177. package/src/util/xml.ts +7 -0
  178. package/templates/nuxt/package.json +1 -1
  179. package/templates/nuxt/types/env.d.ts +11 -0
  180. package/templates/react-router/workers/app.ts +2 -2
  181. package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +385 -0
  182. package/templates/rwsdk/wrangler.jsonc +10 -7
  183. package/templates/typescript/tsconfig.json +1 -2
  184. package/bin/alchemy.mjs +0 -51766
  185. package/lib/cloudflare/bundle/esbuild.d.ts +0 -16
  186. package/lib/cloudflare/bundle/esbuild.d.ts.map +0 -1
  187. package/lib/cloudflare/bundle/esbuild.js +0 -159
  188. package/lib/cloudflare/bundle/esbuild.js.map +0 -1
  189. package/lib/cloudflare/bundle/fs.d.ts +0 -18
  190. package/lib/cloudflare/bundle/fs.d.ts.map +0 -1
  191. package/lib/cloudflare/bundle/fs.js +0 -78
  192. package/lib/cloudflare/bundle/fs.js.map +0 -1
  193. package/lib/cloudflare/bundle/index.d.ts +0 -3
  194. package/lib/cloudflare/bundle/index.d.ts.map +0 -1
  195. package/lib/cloudflare/bundle/index.js +0 -2
  196. package/lib/cloudflare/bundle/index.js.map +0 -1
  197. package/lib/cloudflare/bundle/inline.d.ts +0 -12
  198. package/lib/cloudflare/bundle/inline.d.ts.map +0 -1
  199. package/lib/cloudflare/bundle/inline.js +0 -32
  200. package/lib/cloudflare/bundle/inline.js.map +0 -1
  201. package/lib/cloudflare/bundle/normalize.d.ts +0 -21
  202. package/lib/cloudflare/bundle/normalize.d.ts.map +0 -1
  203. package/lib/cloudflare/bundle/normalize.js +0 -49
  204. package/lib/cloudflare/bundle/normalize.js.map +0 -1
  205. package/lib/cloudflare/bundle/shared.d.ts +0 -30
  206. package/lib/cloudflare/bundle/shared.d.ts.map +0 -1
  207. package/lib/cloudflare/bundle/shared.js +0 -34
  208. package/lib/cloudflare/bundle/shared.js.map +0 -1
  209. package/src/cloudflare/bundle/esbuild.ts +0 -194
  210. package/src/cloudflare/bundle/fs.ts +0 -103
  211. package/src/cloudflare/bundle/index.ts +0 -6
  212. package/src/cloudflare/bundle/inline.ts +0 -42
  213. package/src/cloudflare/bundle/normalize.ts +0 -76
  214. package/src/cloudflare/bundle/shared.ts +0 -73
@@ -0,0 +1,747 @@
1
+ import type { AwsClient } from "aws4fetch";
2
+ import { XMLParser } from "fast-xml-parser";
3
+ import type { Context } from "../../context.ts";
4
+ import { Resource } from "../../resource.ts";
5
+ import { ignore } from "../../util/ignore.ts";
6
+ import { logger } from "../../util/logger.ts";
7
+ import {
8
+ mergeTimeoutConfig,
9
+ type TimeoutConfig,
10
+ waitForResourceState,
11
+ } from "../../util/timeout.ts";
12
+ import type { RouteTable } from "./route-table.ts";
13
+ import type { Subnet } from "./subnet.ts";
14
+ import { callEC2Api, createEC2Client } from "./utils.ts";
15
+
16
+ /**
17
+ * Properties for creating or updating a Route Table Association
18
+ */
19
+ export interface RouteTableAssociationProps {
20
+ /**
21
+ * The route table to associate
22
+ */
23
+ routeTable: RouteTable | string;
24
+
25
+ /**
26
+ * The subnet to associate with the route table
27
+ * Either subnet or gateway must be specified, but not both
28
+ */
29
+ subnet?: Subnet | string;
30
+
31
+ /**
32
+ * The internet gateway or virtual private gateway to associate with the route table
33
+ * Either subnet or gateway must be specified, but not both
34
+ */
35
+ gateway?: string;
36
+
37
+ /**
38
+ * Timeout configuration for Route Table Association operations
39
+ * @default Route Table Association-specific sensible defaults (30 attempts, 1000ms delay)
40
+ */
41
+ timeout?: Partial<TimeoutConfig>;
42
+ }
43
+
44
+ /**
45
+ * Output returned after Route Table Association creation/update
46
+ */
47
+ export interface RouteTableAssociation
48
+ extends Resource<"aws::RouteTableAssociation">,
49
+ RouteTableAssociationProps {
50
+ /**
51
+ * The ID of the route table association
52
+ */
53
+ associationId: string;
54
+
55
+ /**
56
+ * The ID of the route table
57
+ */
58
+ routeTableId: string;
59
+
60
+ /**
61
+ * The ID of the subnet (if associated with a subnet)
62
+ */
63
+ subnetId?: string;
64
+
65
+ /**
66
+ * The ID of the gateway (if associated with a gateway)
67
+ */
68
+ gatewayId?: string;
69
+
70
+ /**
71
+ * Whether this is the main route table association
72
+ */
73
+ isMain: boolean;
74
+
75
+ /**
76
+ * The state of the association
77
+ */
78
+ state: string;
79
+ }
80
+
81
+ /**
82
+ * Wait for Route Table Association to reach a specific state
83
+ */
84
+ async function waitForRouteTableAssociationState(
85
+ client: AwsClient,
86
+ routeTableId: string,
87
+ associationId: string,
88
+ targetState: string,
89
+ timeoutConfig: TimeoutConfig,
90
+ ): Promise<void> {
91
+ const checkFunction = async (): Promise<
92
+ AwsRouteTableAssociation | undefined
93
+ > => {
94
+ const response = await callEC2Api<DescribeRouteTablesResponse>(
95
+ client,
96
+ "DescribeRouteTables",
97
+ parseRouteTableAssociationXmlResponse,
98
+ convertDescribeRouteTablesParamsToAwsFormat({
99
+ RouteTableIds: [routeTableId],
100
+ }),
101
+ );
102
+
103
+ const routeTable = response.RouteTables?.[0];
104
+ if (!routeTable) {
105
+ throw new Error(`Route table ${routeTableId} not found`);
106
+ }
107
+
108
+ const association = routeTable.Associations?.find(
109
+ (a) => a.RouteTableAssociationId === associationId,
110
+ );
111
+
112
+ return association;
113
+ };
114
+
115
+ const isReady = (
116
+ association: AwsRouteTableAssociation | undefined,
117
+ ): boolean => {
118
+ return association?.AssociationState?.State === targetState;
119
+ };
120
+
121
+ await waitForResourceState(
122
+ checkFunction,
123
+ isReady,
124
+ timeoutConfig,
125
+ associationId,
126
+ "Route Table Association",
127
+ `reach state ${targetState}`,
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Wait for Route Table Association to be deleted
133
+ */
134
+ async function waitForRouteTableAssociationDeleted(
135
+ client: AwsClient,
136
+ routeTableId: string,
137
+ associationId: string,
138
+ timeoutConfig: TimeoutConfig,
139
+ ): Promise<void> {
140
+ const checkFunction = async (): Promise<
141
+ AwsRouteTableAssociation | undefined
142
+ > => {
143
+ try {
144
+ const response = await callEC2Api<DescribeRouteTablesResponse>(
145
+ client,
146
+ "DescribeRouteTables",
147
+ parseRouteTableAssociationXmlResponse,
148
+ convertDescribeRouteTablesParamsToAwsFormat({
149
+ RouteTableIds: [routeTableId],
150
+ }),
151
+ );
152
+
153
+ const routeTable = response.RouteTables?.[0];
154
+ if (!routeTable) {
155
+ // Route table doesn't exist, so association is definitely gone
156
+ return undefined;
157
+ }
158
+
159
+ const association = routeTable.Associations?.find(
160
+ (a) => a.RouteTableAssociationId === associationId,
161
+ );
162
+
163
+ return association;
164
+ } catch (error: any) {
165
+ // If Route Table is not found, association is definitely gone
166
+ if (
167
+ error.code === "InvalidRouteTableID.NotFound" ||
168
+ error.code === "InvalidRouteTable.NotFound"
169
+ ) {
170
+ return undefined;
171
+ }
172
+ throw error;
173
+ }
174
+ };
175
+
176
+ const isReady = (
177
+ association: AwsRouteTableAssociation | undefined,
178
+ ): boolean => {
179
+ // Association is deleted if it doesn't exist
180
+ return !association;
181
+ };
182
+
183
+ await waitForResourceState(
184
+ checkFunction,
185
+ isReady,
186
+ timeoutConfig,
187
+ associationId,
188
+ "Route Table Association",
189
+ "be deleted",
190
+ );
191
+ }
192
+
193
+ /**
194
+ * AWS Route Table Association Resource
195
+ *
196
+ * Creates and manages associations between route tables and subnets or gateways.
197
+ * Each subnet can only be associated with one route table at a time. Route table
198
+ * associations determine which route table controls the traffic for a subnet.
199
+ *
200
+ * @example
201
+ * // Associate a subnet with a route table
202
+ * const association = await RouteTableAssociation("subnet-association", {
203
+ * routeTable: myRouteTable,
204
+ * subnet: mySubnet
205
+ * });
206
+ *
207
+ * @example
208
+ * // Associate a subnet with a route table using IDs
209
+ * const association = await RouteTableAssociation("subnet-association", {
210
+ * routeTable: "rtb-12345678",
211
+ * subnet: "subnet-12345678"
212
+ * });
213
+ *
214
+ * @example
215
+ * // Associate a gateway with a route table
216
+ * const gatewayAssociation = await RouteTableAssociation("gateway-association", {
217
+ * routeTable: myRouteTable,
218
+ * gateway: "igw-12345678"
219
+ * });
220
+ *
221
+ * @example
222
+ * // Create association with custom timeout configuration
223
+ * const customAssociation = await RouteTableAssociation("custom-association", {
224
+ * routeTable: myRouteTable,
225
+ * subnet: mySubnet,
226
+ * timeout: {
227
+ * maxAttempts: 60,
228
+ * delayMs: 2000
229
+ * }
230
+ * });
231
+ *
232
+ * @example
233
+ * // Associate multiple subnets with the same route table
234
+ * const publicSubnets = [subnet1, subnet2, subnet3];
235
+ * const associations = await Promise.all(
236
+ * publicSubnets.map((subnet, index) =>
237
+ * RouteTableAssociation(`public-association-${index}`, {
238
+ * routeTable: publicRouteTable,
239
+ * subnet
240
+ * })
241
+ * )
242
+ * );
243
+ *
244
+ * @example
245
+ * // Associate private subnets with a private route table
246
+ * const privateAssociation1 = await RouteTableAssociation("private-association-1", {
247
+ * routeTable: privateRouteTable,
248
+ * subnet: privateSubnet1
249
+ * });
250
+ *
251
+ * const privateAssociation2 = await RouteTableAssociation("private-association-2", {
252
+ * routeTable: privateRouteTable,
253
+ * subnet: privateSubnet2
254
+ * });
255
+ *
256
+ * @example
257
+ * // Handle association replacement scenarios
258
+ * const newAssociation = await RouteTableAssociation("replaced-association", {
259
+ * routeTable: newRouteTable,
260
+ * subnet: existingSubnet // This will replace any existing association
261
+ * });
262
+ */
263
+ export const RouteTableAssociation = Resource(
264
+ "aws::RouteTableAssociation",
265
+ async function (
266
+ this: Context<RouteTableAssociation>,
267
+ _id: string,
268
+ props: RouteTableAssociationProps,
269
+ ): Promise<RouteTableAssociation> {
270
+ const client = await createEC2Client();
271
+ const timeoutConfig = mergeTimeoutConfig(
272
+ ROUTE_TABLE_ASSOCIATION_TIMEOUT,
273
+ props.timeout,
274
+ );
275
+
276
+ const routeTableId =
277
+ typeof props.routeTable === "string"
278
+ ? props.routeTable
279
+ : props.routeTable.routeTableId;
280
+
281
+ const subnetId = props.subnet
282
+ ? typeof props.subnet === "string"
283
+ ? props.subnet
284
+ : props.subnet.subnetId
285
+ : undefined;
286
+
287
+ const gatewayId = props.gateway;
288
+
289
+ // Validate that exactly one of subnet or gateway is specified
290
+ if ((!subnetId && !gatewayId) || (subnetId && gatewayId)) {
291
+ throw new Error(
292
+ "Exactly one of subnet or gateway must be specified for route table association",
293
+ );
294
+ }
295
+
296
+ if (this.phase === "delete") {
297
+ if (this.output?.associationId) {
298
+ logger.log(
299
+ `🗑️ Deleting Route Table Association: ${this.output.associationId}`,
300
+ );
301
+
302
+ await ignore("InvalidAssociationID.NotFound", async () => {
303
+ const params = convertDisassociateRouteTableParamsToAwsFormat({
304
+ AssociationId: this.output!.associationId,
305
+ });
306
+
307
+ await callEC2Api<DisassociateRouteTableResponse>(
308
+ client,
309
+ "DisassociateRouteTable",
310
+ parseRouteTableAssociationXmlResponse,
311
+ params,
312
+ );
313
+ });
314
+
315
+ // Wait for association to be fully deleted
316
+ await waitForRouteTableAssociationDeleted(
317
+ client,
318
+ routeTableId,
319
+ this.output.associationId,
320
+ timeoutConfig,
321
+ );
322
+
323
+ logger.log(
324
+ ` ✅ Route Table Association ${this.output.associationId} deletion completed`,
325
+ );
326
+ }
327
+ return this.destroy();
328
+ }
329
+
330
+ let associationId: string;
331
+ let isMain = false;
332
+ let state = "associated";
333
+
334
+ if (this.phase === "update" && this.output?.associationId) {
335
+ // For updates, we need to check if the route table or target has changed
336
+ const currentRouteTableId = this.output.routeTableId;
337
+ const currentSubnetId = this.output.subnetId;
338
+ const currentGatewayId = this.output.gatewayId;
339
+
340
+ if (
341
+ currentRouteTableId !== routeTableId ||
342
+ currentSubnetId !== subnetId ||
343
+ currentGatewayId !== gatewayId
344
+ ) {
345
+ // Route table or target has changed, we need to replace the association
346
+ const response = await callEC2Api<ReplaceRouteTableAssociationResponse>(
347
+ client,
348
+ "ReplaceRouteTableAssociation",
349
+ parseRouteTableAssociationXmlResponse,
350
+ {
351
+ AssociationId: this.output.associationId,
352
+ RouteTableId: routeTableId,
353
+ },
354
+ );
355
+
356
+ associationId = response.NewAssociationId;
357
+ state = response.AssociationState?.State || "associated";
358
+
359
+ // Wait for the new association to be active
360
+ await waitForRouteTableAssociationState(
361
+ client,
362
+ routeTableId,
363
+ associationId,
364
+ "associated",
365
+ timeoutConfig,
366
+ );
367
+ } else {
368
+ // No changes needed, keep existing association
369
+ associationId = this.output.associationId;
370
+ state = this.output.state;
371
+ }
372
+ } else {
373
+ // Create new association
374
+ const params = convertAssociateRouteTableParamsToAwsFormat({
375
+ RouteTableId: routeTableId,
376
+ SubnetId: subnetId,
377
+ GatewayId: gatewayId,
378
+ });
379
+
380
+ const response = await callEC2Api<AssociateRouteTableResponse>(
381
+ client,
382
+ "AssociateRouteTable",
383
+ parseRouteTableAssociationXmlResponse,
384
+ params,
385
+ );
386
+
387
+ associationId = response.AssociationId;
388
+ state = response.AssociationState?.State || "associated";
389
+
390
+ // Wait for association to be active
391
+ await waitForRouteTableAssociationState(
392
+ client,
393
+ routeTableId,
394
+ associationId,
395
+ "associated",
396
+ timeoutConfig,
397
+ );
398
+ }
399
+
400
+ // Get the current association details to check if it's main
401
+ const describeResponse = await callEC2Api<DescribeRouteTablesResponse>(
402
+ client,
403
+ "DescribeRouteTables",
404
+ parseRouteTableAssociationXmlResponse,
405
+ convertDescribeRouteTablesParamsToAwsFormat({
406
+ RouteTableIds: [routeTableId],
407
+ }),
408
+ );
409
+
410
+ const routeTable = describeResponse.RouteTables?.[0];
411
+ if (routeTable) {
412
+ const association = routeTable.Associations?.find(
413
+ (a) => a.RouteTableAssociationId === associationId,
414
+ );
415
+ if (association) {
416
+ isMain = association.Main;
417
+ state = association.AssociationState.State;
418
+ }
419
+ }
420
+
421
+ return this({
422
+ associationId,
423
+ routeTableId,
424
+ subnetId,
425
+ gatewayId,
426
+ isMain,
427
+ state,
428
+ ...props,
429
+ routeTable: routeTableId,
430
+ subnet: subnetId,
431
+ gateway: gatewayId,
432
+ });
433
+ },
434
+ );
435
+
436
+ /**
437
+ * Route Table Association timeout constants
438
+ */
439
+ export const ROUTE_TABLE_ASSOCIATION_TIMEOUT: TimeoutConfig = {
440
+ maxAttempts: 30,
441
+ delayMs: 1000, // 1 second - Route Table Associations are fast resources
442
+ };
443
+
444
+ /**
445
+ * Route Table Association API Parameter Types
446
+ */
447
+ interface AssociateRouteTableParams {
448
+ RouteTableId: string;
449
+ SubnetId?: string;
450
+ GatewayId?: string;
451
+ DryRun?: boolean;
452
+ }
453
+
454
+ interface DisassociateRouteTableParams {
455
+ AssociationId: string;
456
+ DryRun?: boolean;
457
+ }
458
+
459
+ interface DescribeRouteTablesParams {
460
+ RouteTableIds?: string[];
461
+ Filters?: Filter[];
462
+ DryRun?: boolean;
463
+ NextToken?: string;
464
+ MaxResults?: number;
465
+ }
466
+
467
+ interface Filter {
468
+ Name: string;
469
+ Values: string[];
470
+ }
471
+
472
+ /**
473
+ * Route Table Association XML Response Types
474
+ */
475
+ interface AssociateRouteTableXmlResponse {
476
+ associationId: string;
477
+ associationState?: {
478
+ state:
479
+ | "associating"
480
+ | "associated"
481
+ | "disassociating"
482
+ | "disassociated"
483
+ | "failed";
484
+ statusMessage?: string;
485
+ };
486
+ requestId: string;
487
+ }
488
+
489
+ interface DisassociateRouteTableXmlResponse {
490
+ return: boolean;
491
+ requestId: string;
492
+ }
493
+
494
+ interface ReplaceRouteTableAssociationXmlResponse {
495
+ newAssociationId: string;
496
+ associationState?: {
497
+ state: string;
498
+ statusMessage?: string;
499
+ };
500
+ requestId: string;
501
+ }
502
+
503
+ interface RouteTableXmlAssociation {
504
+ routeTableAssociationId: string;
505
+ routeTableId: string;
506
+ subnetId?: string;
507
+ gatewayId?: string;
508
+ main: boolean;
509
+ associationState?: {
510
+ state:
511
+ | "associating"
512
+ | "associated"
513
+ | "disassociating"
514
+ | "disassociated"
515
+ | "failed";
516
+ statusMessage?: string;
517
+ };
518
+ }
519
+
520
+ interface RouteTableXmlResult {
521
+ routeTableId: string;
522
+ vpcId: string;
523
+ associationSet?: {
524
+ item: RouteTableXmlAssociation | RouteTableXmlAssociation[];
525
+ };
526
+ }
527
+
528
+ interface DescribeRouteTablesXmlResponse {
529
+ routeTableSet: {
530
+ item: RouteTableXmlResult | RouteTableXmlResult[];
531
+ };
532
+ requestId: string;
533
+ nextToken?: string;
534
+ }
535
+
536
+ /**
537
+ * Helper functions to convert typed parameters to AWS API format
538
+ */
539
+ function convertAssociateRouteTableParamsToAwsFormat(
540
+ params: AssociateRouteTableParams,
541
+ ): Record<string, string> {
542
+ const awsParams: Record<string, string> = {
543
+ RouteTableId: params.RouteTableId,
544
+ };
545
+
546
+ if (params.SubnetId) {
547
+ awsParams.SubnetId = params.SubnetId;
548
+ }
549
+
550
+ if (params.GatewayId) {
551
+ awsParams.GatewayId = params.GatewayId;
552
+ }
553
+
554
+ if (params.DryRun !== undefined) {
555
+ awsParams.DryRun = params.DryRun.toString();
556
+ }
557
+
558
+ return awsParams;
559
+ }
560
+
561
+ function convertDisassociateRouteTableParamsToAwsFormat(
562
+ params: DisassociateRouteTableParams,
563
+ ): Record<string, string> {
564
+ const awsParams: Record<string, string> = {
565
+ AssociationId: params.AssociationId,
566
+ };
567
+
568
+ if (params.DryRun !== undefined) {
569
+ awsParams.DryRun = params.DryRun.toString();
570
+ }
571
+
572
+ return awsParams;
573
+ }
574
+
575
+ function convertDescribeRouteTablesParamsToAwsFormat(
576
+ params: DescribeRouteTablesParams,
577
+ ): Record<string, string> {
578
+ const awsParams: Record<string, string> = {};
579
+
580
+ if (params.RouteTableIds) {
581
+ params.RouteTableIds.forEach((routeTableId, index) => {
582
+ awsParams[`RouteTableId.${index + 1}`] = routeTableId;
583
+ });
584
+ }
585
+
586
+ if (params.Filters) {
587
+ params.Filters.forEach((filter, filterIndex) => {
588
+ const filterNum = filterIndex + 1;
589
+ awsParams[`Filter.${filterNum}.Name`] = filter.Name;
590
+
591
+ filter.Values.forEach((value, valueIndex) => {
592
+ awsParams[`Filter.${filterNum}.Value.${valueIndex + 1}`] = value;
593
+ });
594
+ });
595
+ }
596
+
597
+ if (params.DryRun !== undefined) {
598
+ awsParams.DryRun = params.DryRun.toString();
599
+ }
600
+
601
+ if (params.NextToken) {
602
+ awsParams.NextToken = params.NextToken;
603
+ }
604
+
605
+ if (params.MaxResults !== undefined) {
606
+ awsParams.MaxResults = params.MaxResults.toString();
607
+ }
608
+
609
+ return awsParams;
610
+ }
611
+
612
+ /**
613
+ * A single route table association as returned by the AWS API.
614
+ */
615
+ interface AwsRouteTableAssociation {
616
+ RouteTableAssociationId: string;
617
+ RouteTableId: string;
618
+ SubnetId?: string;
619
+ GatewayId?: string;
620
+ Main: boolean;
621
+ AssociationState: {
622
+ State: string;
623
+ StatusMessage?: string;
624
+ };
625
+ }
626
+
627
+ /**
628
+ * AWS Route Table Association API response types
629
+ */
630
+ interface AssociateRouteTableResponse {
631
+ AssociationId: string;
632
+ AssociationState?: {
633
+ State: string;
634
+ StatusMessage?: string;
635
+ };
636
+ }
637
+
638
+ interface DisassociateRouteTableResponse {
639
+ Return: boolean;
640
+ }
641
+
642
+ interface ReplaceRouteTableAssociationResponse {
643
+ NewAssociationId: string;
644
+ AssociationState?: {
645
+ State: string;
646
+ StatusMessage?: string;
647
+ };
648
+ }
649
+
650
+ interface DescribeRouteTablesResponse {
651
+ RouteTables: Array<{
652
+ RouteTableId: string;
653
+ VpcId: string;
654
+ Associations?: AwsRouteTableAssociation[];
655
+ }>;
656
+ }
657
+
658
+ /**
659
+ * Parse XML responses specifically for Route Table Association operations
660
+ */
661
+ function parseRouteTableAssociationXmlResponse<T>(xmlText: string): T {
662
+ const parser = new XMLParser({
663
+ ignoreAttributes: true,
664
+ parseAttributeValue: true,
665
+ parseTagValue: true,
666
+ trimValues: true,
667
+ });
668
+
669
+ const parsed = parser.parse(xmlText);
670
+ const result: Record<string, unknown> = {};
671
+
672
+ // Parse AssociateRouteTableResponse
673
+ if (parsed.AssociateRouteTableResponse) {
674
+ const response =
675
+ parsed.AssociateRouteTableResponse as AssociateRouteTableXmlResponse;
676
+ result.AssociationId = response.associationId;
677
+ if (response.associationState) {
678
+ result.AssociationState = {
679
+ State: response.associationState.state,
680
+ StatusMessage: response.associationState.statusMessage,
681
+ };
682
+ }
683
+ }
684
+
685
+ // Parse DisassociateRouteTableResponse
686
+ if (parsed.DisassociateRouteTableResponse) {
687
+ const response =
688
+ parsed.DisassociateRouteTableResponse as DisassociateRouteTableXmlResponse;
689
+ result.Return = response.return;
690
+ }
691
+
692
+ // Parse ReplaceRouteTableAssociationResponse
693
+ if (parsed.ReplaceRouteTableAssociationResponse) {
694
+ const response =
695
+ parsed.ReplaceRouteTableAssociationResponse as ReplaceRouteTableAssociationXmlResponse;
696
+ result.NewAssociationId = response.newAssociationId;
697
+ if (response.associationState) {
698
+ result.AssociationState = {
699
+ State: response.associationState.state,
700
+ StatusMessage: response.associationState.statusMessage,
701
+ };
702
+ }
703
+ }
704
+
705
+ // Parse DescribeRouteTablesResponse
706
+ if (parsed.DescribeRouteTablesResponse) {
707
+ const response =
708
+ parsed.DescribeRouteTablesResponse as DescribeRouteTablesXmlResponse;
709
+ const routeTableSet = response.routeTableSet;
710
+ if (routeTableSet?.item) {
711
+ const routeTables: RouteTableXmlResult[] = Array.isArray(
712
+ routeTableSet.item,
713
+ )
714
+ ? routeTableSet.item
715
+ : [routeTableSet.item];
716
+ result.RouteTables = routeTables.map((routeTable) => {
717
+ const associations = routeTable.associationSet?.item
718
+ ? Array.isArray(routeTable.associationSet.item)
719
+ ? routeTable.associationSet.item
720
+ : [routeTable.associationSet.item]
721
+ : [];
722
+ return {
723
+ RouteTableId: routeTable.routeTableId,
724
+ VpcId: routeTable.vpcId,
725
+ Associations: associations.map(
726
+ (association): AwsRouteTableAssociation => {
727
+ return {
728
+ RouteTableAssociationId: association.routeTableAssociationId,
729
+ RouteTableId: association.routeTableId,
730
+ SubnetId: association.subnetId,
731
+ GatewayId: association.gatewayId,
732
+ Main: association.main,
733
+ AssociationState: {
734
+ State: association.associationState?.state ?? "associated",
735
+ StatusMessage: association.associationState?.statusMessage,
736
+ },
737
+ };
738
+ },
739
+ ),
740
+ };
741
+ });
742
+ } else {
743
+ result.RouteTables = [];
744
+ }
745
+ }
746
+ return result as T;
747
+ }