alchemy-effect 0.3.0 → 0.4.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 (158) hide show
  1. package/bin/alchemy-effect.js +539 -223
  2. package/bin/alchemy-effect.js.map +1 -1
  3. package/lib/apply.d.ts +4 -4
  4. package/lib/apply.d.ts.map +1 -1
  5. package/lib/apply.js +411 -131
  6. package/lib/apply.js.map +1 -1
  7. package/lib/aws/dynamodb/table.provider.d.ts.map +1 -1
  8. package/lib/aws/dynamodb/table.provider.js +1 -0
  9. package/lib/aws/dynamodb/table.provider.js.map +1 -1
  10. package/lib/aws/ec2/index.d.ts +8 -0
  11. package/lib/aws/ec2/index.d.ts.map +1 -1
  12. package/lib/aws/ec2/index.js +8 -0
  13. package/lib/aws/ec2/index.js.map +1 -1
  14. package/lib/aws/ec2/internet-gateway.d.ts +65 -0
  15. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
  16. package/lib/aws/ec2/internet-gateway.js +4 -0
  17. package/lib/aws/ec2/internet-gateway.js.map +1 -0
  18. package/lib/aws/ec2/internet-gateway.provider.d.ts +6 -0
  19. package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -0
  20. package/lib/aws/ec2/internet-gateway.provider.js +193 -0
  21. package/lib/aws/ec2/internet-gateway.provider.js.map +1 -0
  22. package/lib/aws/ec2/route-table-association.d.ts +63 -0
  23. package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
  24. package/lib/aws/ec2/route-table-association.js +4 -0
  25. package/lib/aws/ec2/route-table-association.js.map +1 -0
  26. package/lib/aws/ec2/route-table-association.provider.d.ts +4 -0
  27. package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -0
  28. package/lib/aws/ec2/route-table-association.provider.js +121 -0
  29. package/lib/aws/ec2/route-table-association.provider.js.map +1 -0
  30. package/lib/aws/ec2/route-table.d.ts +159 -0
  31. package/lib/aws/ec2/route-table.d.ts.map +1 -0
  32. package/lib/aws/ec2/route-table.js +4 -0
  33. package/lib/aws/ec2/route-table.js.map +1 -0
  34. package/lib/aws/ec2/route-table.provider.d.ts +6 -0
  35. package/lib/aws/ec2/route-table.provider.d.ts.map +1 -0
  36. package/lib/aws/ec2/route-table.provider.js +213 -0
  37. package/lib/aws/ec2/route-table.provider.js.map +1 -0
  38. package/lib/aws/ec2/route.d.ts +155 -0
  39. package/lib/aws/ec2/route.d.ts.map +1 -0
  40. package/lib/aws/ec2/route.js +3 -0
  41. package/lib/aws/ec2/route.js.map +1 -0
  42. package/lib/aws/ec2/route.provider.d.ts +4 -0
  43. package/lib/aws/ec2/route.provider.d.ts.map +1 -0
  44. package/lib/aws/ec2/route.provider.js +166 -0
  45. package/lib/aws/ec2/route.provider.js.map +1 -0
  46. package/lib/aws/ec2/subnet.provider.d.ts.map +1 -1
  47. package/lib/aws/ec2/subnet.provider.js +1 -1
  48. package/lib/aws/ec2/subnet.provider.js.map +1 -1
  49. package/lib/aws/ec2/vpc.d.ts +1 -0
  50. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  51. package/lib/aws/ec2/vpc.provider.d.ts +2 -2
  52. package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
  53. package/lib/aws/ec2/vpc.provider.js +38 -15
  54. package/lib/aws/ec2/vpc.provider.js.map +1 -1
  55. package/lib/aws/index.d.ts +2 -3
  56. package/lib/aws/index.d.ts.map +1 -1
  57. package/lib/aws/index.js +2 -1
  58. package/lib/aws/index.js.map +1 -1
  59. package/lib/aws/lambda/function.provider.d.ts +2 -2
  60. package/lib/aws/lambda/function.provider.d.ts.map +1 -1
  61. package/lib/aws/lambda/function.provider.js +21 -20
  62. package/lib/aws/lambda/function.provider.js.map +1 -1
  63. package/lib/aws/sqs/queue.provider.d.ts +2 -2
  64. package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
  65. package/lib/aws/sqs/queue.provider.js +3 -2
  66. package/lib/aws/sqs/queue.provider.js.map +1 -1
  67. package/lib/cli/index.d.ts +178 -99
  68. package/lib/cli/index.d.ts.map +1 -1
  69. package/lib/cloudflare/kv/namespace.client.d.ts +1 -1
  70. package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
  71. package/lib/cloudflare/kv/namespace.provider.js +1 -0
  72. package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
  73. package/lib/cloudflare/r2/bucket.provider.d.ts.map +1 -1
  74. package/lib/cloudflare/r2/bucket.provider.js +6 -1
  75. package/lib/cloudflare/r2/bucket.provider.js.map +1 -1
  76. package/lib/cloudflare/worker/worker.provider.d.ts +1 -1
  77. package/lib/cloudflare/worker/worker.provider.d.ts.map +1 -1
  78. package/lib/cloudflare/worker/worker.provider.js +6 -2
  79. package/lib/cloudflare/worker/worker.provider.js.map +1 -1
  80. package/lib/diff.d.ts +8 -6
  81. package/lib/diff.d.ts.map +1 -1
  82. package/lib/diff.js +13 -0
  83. package/lib/diff.js.map +1 -1
  84. package/lib/event.d.ts +1 -1
  85. package/lib/event.d.ts.map +1 -1
  86. package/lib/instance-id.d.ts +8 -0
  87. package/lib/instance-id.d.ts.map +1 -0
  88. package/lib/instance-id.js +12 -0
  89. package/lib/instance-id.js.map +1 -0
  90. package/lib/output.d.ts +4 -2
  91. package/lib/output.d.ts.map +1 -1
  92. package/lib/output.js +18 -4
  93. package/lib/output.js.map +1 -1
  94. package/lib/physical-name.d.ts +14 -1
  95. package/lib/physical-name.d.ts.map +1 -1
  96. package/lib/physical-name.js +41 -2
  97. package/lib/physical-name.js.map +1 -1
  98. package/lib/plan.d.ts +49 -42
  99. package/lib/plan.d.ts.map +1 -1
  100. package/lib/plan.js +359 -127
  101. package/lib/plan.js.map +1 -1
  102. package/lib/provider.d.ts +26 -9
  103. package/lib/provider.d.ts.map +1 -1
  104. package/lib/provider.js +9 -0
  105. package/lib/provider.js.map +1 -1
  106. package/lib/resource.d.ts +2 -2
  107. package/lib/resource.d.ts.map +1 -1
  108. package/lib/resource.js.map +1 -1
  109. package/lib/state.d.ts +86 -9
  110. package/lib/state.d.ts.map +1 -1
  111. package/lib/state.js +21 -18
  112. package/lib/state.js.map +1 -1
  113. package/lib/tags.d.ts +15 -0
  114. package/lib/tags.d.ts.map +1 -1
  115. package/lib/tags.js +27 -0
  116. package/lib/tags.js.map +1 -1
  117. package/lib/test.d.ts +2 -2
  118. package/lib/test.d.ts.map +1 -1
  119. package/lib/test.js +4 -4
  120. package/lib/test.js.map +1 -1
  121. package/lib/todo.d.ts +3 -0
  122. package/lib/todo.d.ts.map +1 -0
  123. package/lib/todo.js +3 -0
  124. package/lib/todo.js.map +1 -0
  125. package/lib/tsconfig.test.tsbuildinfo +1 -1
  126. package/package.json +2 -2
  127. package/src/apply.ts +758 -374
  128. package/src/aws/dynamodb/table.provider.ts +1 -0
  129. package/src/aws/ec2/index.ts +8 -0
  130. package/src/aws/ec2/internet-gateway.provider.ts +316 -0
  131. package/src/aws/ec2/internet-gateway.ts +79 -0
  132. package/src/aws/ec2/route-table-association.provider.ts +214 -0
  133. package/src/aws/ec2/route-table-association.ts +82 -0
  134. package/src/aws/ec2/route-table.provider.ts +306 -0
  135. package/src/aws/ec2/route-table.ts +175 -0
  136. package/src/aws/ec2/route.provider.ts +213 -0
  137. package/src/aws/ec2/route.ts +192 -0
  138. package/src/aws/ec2/subnet.provider.ts +2 -2
  139. package/src/aws/ec2/vpc.provider.ts +43 -19
  140. package/src/aws/ec2/vpc.ts +2 -0
  141. package/src/aws/index.ts +4 -1
  142. package/src/aws/lambda/function.provider.ts +25 -23
  143. package/src/aws/sqs/queue.provider.ts +3 -2
  144. package/src/cloudflare/kv/namespace.provider.ts +1 -0
  145. package/src/cloudflare/r2/bucket.provider.ts +7 -1
  146. package/src/cloudflare/worker/worker.provider.ts +6 -2
  147. package/src/diff.ts +35 -17
  148. package/src/event.ts +6 -0
  149. package/src/instance-id.ts +16 -0
  150. package/src/output.ts +29 -5
  151. package/src/physical-name.ts +57 -2
  152. package/src/plan.ts +488 -197
  153. package/src/provider.ts +46 -9
  154. package/src/resource.ts +50 -4
  155. package/src/state.ts +150 -35
  156. package/src/tags.ts +31 -0
  157. package/src/test.ts +5 -5
  158. package/src/todo.ts +4 -0
@@ -0,0 +1,213 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schedule from "effect/Schedule";
3
+
4
+ import type { EC2 } from "itty-aws/ec2";
5
+
6
+ import { somePropsAreDifferent } from "../../diff.ts";
7
+ import type { ProviderService } from "../../provider.ts";
8
+ import { EC2Client } from "./client.ts";
9
+ import { Route, type RouteAttrs, type RouteProps } from "./route.ts";
10
+
11
+ export const routeProvider = () =>
12
+ Route.provider.effect(
13
+ Effect.gen(function* () {
14
+ const ec2 = yield* EC2Client;
15
+
16
+ return {
17
+ diff: Effect.fn(function* ({ news, olds }) {
18
+ // Route table change requires replacement
19
+ if (olds.routeTableId !== news.routeTableId) {
20
+ return { action: "replace" };
21
+ }
22
+
23
+ // Destination change requires replacement
24
+ if (
25
+ somePropsAreDifferent(olds, news, [
26
+ "destinationCidrBlock",
27
+ "destinationIpv6CidrBlock",
28
+ "destinationPrefixListId",
29
+ ])
30
+ ) {
31
+ return { action: "replace" };
32
+ }
33
+
34
+ // Target change can be done via ReplaceRoute (update)
35
+ }),
36
+
37
+ create: Effect.fn(function* ({ news, session }) {
38
+ // Call CreateRoute
39
+ yield* ec2
40
+ .createRoute({
41
+ RouteTableId: news.routeTableId,
42
+ DestinationCidrBlock: news.destinationCidrBlock,
43
+ DestinationIpv6CidrBlock: news.destinationIpv6CidrBlock,
44
+ DestinationPrefixListId: news.destinationPrefixListId,
45
+ GatewayId: news.gatewayId,
46
+ NatGatewayId: news.natGatewayId,
47
+ InstanceId: news.instanceId,
48
+ NetworkInterfaceId: news.networkInterfaceId,
49
+ VpcPeeringConnectionId: news.vpcPeeringConnectionId,
50
+ TransitGatewayId: news.transitGatewayId,
51
+ LocalGatewayId: news.localGatewayId,
52
+ CarrierGatewayId: news.carrierGatewayId,
53
+ EgressOnlyInternetGatewayId: news.egressOnlyInternetGatewayId,
54
+ CoreNetworkArn: news.coreNetworkArn,
55
+ VpcEndpointId: news.vpcEndpointId,
56
+ DryRun: false,
57
+ })
58
+ .pipe(
59
+ Effect.retry({
60
+ // Retry if route table is not yet available
61
+ while: (e) => e._tag === "InvalidRouteTableID.NotFound",
62
+ schedule: Schedule.exponential(100),
63
+ }),
64
+ );
65
+
66
+ const dest =
67
+ news.destinationCidrBlock ||
68
+ news.destinationIpv6CidrBlock ||
69
+ news.destinationPrefixListId ||
70
+ "unknown";
71
+ yield* session.note(`Route created: ${dest}`);
72
+
73
+ // Describe to get route details
74
+ const route = yield* describeRoute(ec2, news.routeTableId, news);
75
+
76
+ // Return attributes
77
+ return {
78
+ routeTableId: news.routeTableId,
79
+ destinationCidrBlock: news.destinationCidrBlock,
80
+ destinationIpv6CidrBlock: news.destinationIpv6CidrBlock,
81
+ destinationPrefixListId: news.destinationPrefixListId,
82
+ origin: route?.Origin ?? "CreateRoute",
83
+ state: route?.State ?? "active",
84
+ gatewayId: route?.GatewayId,
85
+ natGatewayId: route?.NatGatewayId,
86
+ instanceId: route?.InstanceId,
87
+ networkInterfaceId: route?.NetworkInterfaceId,
88
+ vpcPeeringConnectionId: route?.VpcPeeringConnectionId,
89
+ transitGatewayId: route?.TransitGatewayId,
90
+ localGatewayId: route?.LocalGatewayId,
91
+ carrierGatewayId: route?.CarrierGatewayId,
92
+ egressOnlyInternetGatewayId: route?.EgressOnlyInternetGatewayId,
93
+ coreNetworkArn: route?.CoreNetworkArn,
94
+ } satisfies RouteAttrs<RouteProps>;
95
+ }),
96
+
97
+ update: Effect.fn(function* ({ news, output, session }) {
98
+ // Use ReplaceRoute to update the target
99
+ yield* ec2
100
+ .replaceRoute({
101
+ RouteTableId: news.routeTableId,
102
+ DestinationCidrBlock: news.destinationCidrBlock,
103
+ DestinationIpv6CidrBlock: news.destinationIpv6CidrBlock,
104
+ DestinationPrefixListId: news.destinationPrefixListId,
105
+ GatewayId: news.gatewayId,
106
+ NatGatewayId: news.natGatewayId,
107
+ InstanceId: news.instanceId,
108
+ NetworkInterfaceId: news.networkInterfaceId,
109
+ VpcPeeringConnectionId: news.vpcPeeringConnectionId,
110
+ TransitGatewayId: news.transitGatewayId,
111
+ LocalGatewayId: news.localGatewayId,
112
+ CarrierGatewayId: news.carrierGatewayId,
113
+ EgressOnlyInternetGatewayId: news.egressOnlyInternetGatewayId,
114
+ CoreNetworkArn: news.coreNetworkArn,
115
+ DryRun: false,
116
+ })
117
+ .pipe(
118
+ Effect.tapError(Effect.log),
119
+ Effect.retry({
120
+ while: (e) => e._tag === "InvalidRouteTableID.NotFound",
121
+ schedule: Schedule.exponential(100),
122
+ }),
123
+ );
124
+
125
+ yield* session.note("Route target updated");
126
+
127
+ // Describe to get updated route details
128
+ const route = yield* describeRoute(ec2, news.routeTableId, news);
129
+
130
+ return {
131
+ ...output,
132
+ origin: route?.Origin ?? output.origin,
133
+ state: route?.State ?? output.state,
134
+ gatewayId: route?.GatewayId,
135
+ natGatewayId: route?.NatGatewayId,
136
+ instanceId: route?.InstanceId,
137
+ networkInterfaceId: route?.NetworkInterfaceId,
138
+ vpcPeeringConnectionId: route?.VpcPeeringConnectionId,
139
+ transitGatewayId: route?.TransitGatewayId,
140
+ localGatewayId: route?.LocalGatewayId,
141
+ carrierGatewayId: route?.CarrierGatewayId,
142
+ egressOnlyInternetGatewayId: route?.EgressOnlyInternetGatewayId,
143
+ coreNetworkArn: route?.CoreNetworkArn,
144
+ };
145
+ }),
146
+
147
+ delete: Effect.fn(function* ({ output, session }) {
148
+ const dest =
149
+ output.destinationCidrBlock ||
150
+ output.destinationIpv6CidrBlock ||
151
+ output.destinationPrefixListId ||
152
+ "unknown";
153
+
154
+ yield* session.note(`Deleting route: ${dest}`);
155
+
156
+ // Delete the route
157
+ yield* ec2
158
+ .deleteRoute({
159
+ RouteTableId: output.routeTableId,
160
+ DestinationCidrBlock: output.destinationCidrBlock,
161
+ DestinationIpv6CidrBlock: output.destinationIpv6CidrBlock,
162
+ DestinationPrefixListId: output.destinationPrefixListId,
163
+ DryRun: false,
164
+ })
165
+ .pipe(
166
+ Effect.tapError(Effect.logDebug),
167
+ Effect.catchTag("InvalidRoute.NotFound", () => Effect.void),
168
+ Effect.catchTag(
169
+ "InvalidRouteTableID.NotFound",
170
+ () => Effect.void,
171
+ ),
172
+ );
173
+
174
+ yield* session.note(`Route ${dest} deleted successfully`);
175
+ }),
176
+ } satisfies ProviderService<Route>;
177
+ }),
178
+ );
179
+
180
+ /**
181
+ * Find a specific route in a route table
182
+ */
183
+ const describeRoute = (ec2: EC2, routeTableId: string, props: RouteProps) =>
184
+ Effect.gen(function* () {
185
+ const result = yield* ec2
186
+ .describeRouteTables({ RouteTableIds: [routeTableId] })
187
+ .pipe(
188
+ Effect.catchTag("InvalidRouteTableID.NotFound", () =>
189
+ Effect.succeed({ RouteTables: [] }),
190
+ ),
191
+ );
192
+
193
+ const routeTable = result.RouteTables?.[0];
194
+ if (!routeTable) {
195
+ return undefined;
196
+ }
197
+
198
+ // Find the matching route
199
+ const route = routeTable.Routes?.find((r) => {
200
+ if (props.destinationCidrBlock) {
201
+ return r.DestinationCidrBlock === props.destinationCidrBlock;
202
+ }
203
+ if (props.destinationIpv6CidrBlock) {
204
+ return r.DestinationIpv6CidrBlock === props.destinationIpv6CidrBlock;
205
+ }
206
+ if (props.destinationPrefixListId) {
207
+ return r.DestinationPrefixListId === props.destinationPrefixListId;
208
+ }
209
+ return false;
210
+ });
211
+
212
+ return route;
213
+ });
@@ -0,0 +1,192 @@
1
+ import type * as EC2 from "itty-aws/ec2";
2
+ import type { Input } from "../../input.ts";
3
+ import { Resource } from "../../resource.ts";
4
+ import type { RouteTableId } from "./route-table.ts";
5
+
6
+ export const Route = Resource<{
7
+ <const ID extends string, const Props extends RouteProps>(
8
+ id: ID,
9
+ props: Props,
10
+ ): Route<ID, Props>;
11
+ }>("AWS.EC2.Route");
12
+
13
+ export interface Route<
14
+ ID extends string = string,
15
+ Props extends RouteProps = RouteProps,
16
+ > extends Resource<
17
+ "AWS.EC2.Route",
18
+ ID,
19
+ Props,
20
+ RouteAttrs<Input.Resolve<Props>>,
21
+ Route
22
+ > {}
23
+
24
+ export interface RouteProps {
25
+ /**
26
+ * The ID of the route table where the route will be added.
27
+ * Required.
28
+ */
29
+ routeTableId: Input<RouteTableId>;
30
+
31
+ /**
32
+ * The IPv4 CIDR block used for the destination match.
33
+ * Either destinationCidrBlock, destinationIpv6CidrBlock, or destinationPrefixListId is required.
34
+ * @example "0.0.0.0/0"
35
+ */
36
+ destinationCidrBlock?: string;
37
+
38
+ /**
39
+ * The IPv6 CIDR block used for the destination match.
40
+ * Either destinationCidrBlock, destinationIpv6CidrBlock, or destinationPrefixListId is required.
41
+ * @example "::/0"
42
+ */
43
+ destinationIpv6CidrBlock?: string;
44
+
45
+ /**
46
+ * The ID of a prefix list used for the destination match.
47
+ * Either destinationCidrBlock, destinationIpv6CidrBlock, or destinationPrefixListId is required.
48
+ */
49
+ destinationPrefixListId?: string;
50
+
51
+ // ---- Target properties (exactly one required) ----
52
+
53
+ /**
54
+ * The ID of an internet gateway or virtual private gateway.
55
+ */
56
+ gatewayId?: Input<string>;
57
+
58
+ /**
59
+ * The ID of a NAT gateway.
60
+ */
61
+ natGatewayId?: Input<string>;
62
+
63
+ /**
64
+ * The ID of a NAT instance in your VPC.
65
+ * This operation fails unless exactly one network interface is attached.
66
+ */
67
+ instanceId?: Input<string>;
68
+
69
+ /**
70
+ * The ID of a network interface.
71
+ */
72
+ networkInterfaceId?: Input<string>;
73
+
74
+ /**
75
+ * The ID of a VPC peering connection.
76
+ */
77
+ vpcPeeringConnectionId?: Input<string>;
78
+
79
+ /**
80
+ * The ID of a transit gateway.
81
+ */
82
+ transitGatewayId?: Input<string>;
83
+
84
+ /**
85
+ * The ID of a local gateway.
86
+ */
87
+ localGatewayId?: Input<string>;
88
+
89
+ /**
90
+ * The ID of a carrier gateway.
91
+ * Use for Wavelength Zones only.
92
+ */
93
+ carrierGatewayId?: Input<string>;
94
+
95
+ /**
96
+ * The ID of an egress-only internet gateway.
97
+ * IPv6 traffic only.
98
+ */
99
+ egressOnlyInternetGatewayId?: Input<string>;
100
+
101
+ /**
102
+ * The Amazon Resource Name (ARN) of the core network.
103
+ */
104
+ coreNetworkArn?: Input<string>;
105
+
106
+ /**
107
+ * The ID of a VPC endpoint for Gateway Load Balancer.
108
+ */
109
+ vpcEndpointId?: Input<string>;
110
+ }
111
+
112
+ export interface RouteAttrs<Props extends RouteProps> {
113
+ /**
114
+ * The ID of the route table.
115
+ */
116
+ routeTableId: Props["routeTableId"];
117
+
118
+ /**
119
+ * The IPv4 CIDR block used for the destination match.
120
+ */
121
+ destinationCidrBlock?: Props["destinationCidrBlock"];
122
+
123
+ /**
124
+ * The IPv6 CIDR block used for the destination match.
125
+ */
126
+ destinationIpv6CidrBlock?: Props["destinationIpv6CidrBlock"];
127
+
128
+ /**
129
+ * The ID of a prefix list used for the destination match.
130
+ */
131
+ destinationPrefixListId?: Props["destinationPrefixListId"];
132
+
133
+ /**
134
+ * Describes how the route was created.
135
+ */
136
+ origin: EC2.RouteOrigin;
137
+
138
+ /**
139
+ * The state of the route.
140
+ */
141
+ state: EC2.RouteState;
142
+
143
+ /**
144
+ * The ID of the gateway (if applicable).
145
+ */
146
+ gatewayId?: string;
147
+
148
+ /**
149
+ * The ID of the NAT gateway (if applicable).
150
+ */
151
+ natGatewayId?: string;
152
+
153
+ /**
154
+ * The ID of the NAT instance (if applicable).
155
+ */
156
+ instanceId?: string;
157
+
158
+ /**
159
+ * The ID of the network interface (if applicable).
160
+ */
161
+ networkInterfaceId?: string;
162
+
163
+ /**
164
+ * The ID of the VPC peering connection (if applicable).
165
+ */
166
+ vpcPeeringConnectionId?: string;
167
+
168
+ /**
169
+ * The ID of the transit gateway (if applicable).
170
+ */
171
+ transitGatewayId?: string;
172
+
173
+ /**
174
+ * The ID of the local gateway (if applicable).
175
+ */
176
+ localGatewayId?: string;
177
+
178
+ /**
179
+ * The ID of the carrier gateway (if applicable).
180
+ */
181
+ carrierGatewayId?: string;
182
+
183
+ /**
184
+ * The ID of the egress-only internet gateway (if applicable).
185
+ */
186
+ egressOnlyInternetGatewayId?: string;
187
+
188
+ /**
189
+ * The Amazon Resource Name (ARN) of the core network (if applicable).
190
+ */
191
+ coreNetworkArn?: string;
192
+ }
@@ -11,8 +11,8 @@ import { EC2Client } from "./client.ts";
11
11
  import {
12
12
  Subnet,
13
13
  type SubnetAttrs,
14
- type SubnetProps,
15
14
  type SubnetId,
15
+ type SubnetProps,
16
16
  } from "./subnet.ts";
17
17
 
18
18
  export const subnetProvider = () =>
@@ -22,6 +22,7 @@ export const subnetProvider = () =>
22
22
  const tagged = yield* createTagger();
23
23
 
24
24
  return {
25
+ stables: ["subnetId", "subnetArn", "ownerId", "vpcId"],
25
26
  diff: Effect.fn(function* ({ news, olds }) {
26
27
  if (
27
28
  somePropsAreDifferent(olds, news, [
@@ -71,7 +72,6 @@ export const subnetProvider = () =>
71
72
  })
72
73
  .pipe(
73
74
  Effect.retry({
74
- // @ts-expect-error - this is unknown to itty-aws
75
75
  while: (e) => e._tag === "InvalidVpcID.NotFound",
76
76
  schedule: Schedule.exponential(100),
77
77
  }),
@@ -3,15 +3,15 @@ import * as Schedule from "effect/Schedule";
3
3
 
4
4
  import type { EC2 } from "itty-aws/ec2";
5
5
 
6
- import type { VpcId } from "./vpc.ts";
7
6
  import type { ScopedPlanStatusSession } from "../../cli/service.ts";
8
7
  import { somePropsAreDifferent } from "../../diff.ts";
9
8
  import type { ProviderService } from "../../provider.ts";
10
- import { createTagger, createTagsList } from "../../tags.ts";
9
+ import { createTagger, createTagsList, diffTags } from "../../tags.ts";
10
+ import { Account } from "../account.ts";
11
+ import { Region } from "../region.ts";
11
12
  import { EC2Client } from "./client.ts";
13
+ import type { VpcId } from "./vpc.ts";
12
14
  import { Vpc, type VpcAttrs, type VpcProps } from "./vpc.ts";
13
- import { Region } from "../region.ts";
14
- import { Account } from "../account.ts";
15
15
 
16
16
  export const vpcProvider = () =>
17
17
  Vpc.provider.effect(
@@ -21,7 +21,17 @@ export const vpcProvider = () =>
21
21
  const accountId = yield* Account;
22
22
  const tagged = yield* createTagger();
23
23
 
24
+ const createTags = (
25
+ id: string,
26
+ tags?: Record<string, string>,
27
+ ): Record<string, string> => ({
28
+ Name: id,
29
+ ...tagged(id),
30
+ ...tags,
31
+ });
32
+
24
33
  return {
34
+ stables: ["vpcId", "vpcArn", "ownerId", "isDefault"],
25
35
  diff: Effect.fn(function* ({ news, olds }) {
26
36
  if (
27
37
  somePropsAreDifferent(olds, news, [
@@ -35,14 +45,10 @@ export const vpcProvider = () =>
35
45
  return { action: "replace" };
36
46
  }
37
47
  }),
38
-
39
48
  create: Effect.fn(function* ({ id, news, session }) {
40
- // 1. Prepare tags
41
- const alchemyTags = tagged(id);
42
- const userTags = news.tags ?? {};
43
- const allTags = { ...alchemyTags, ...userTags };
49
+ const tags = createTags(id, news.tags);
44
50
 
45
- // 2. Call CreateVpc
51
+ // 1. Call CreateVpc
46
52
  const createResult = yield* ec2.createVpc({
47
53
  // TODO(sam): add all properties
48
54
  AmazonProvidedIpv6CidrBlock: news.amazonProvidedIpv6CidrBlock,
@@ -59,7 +65,7 @@ export const vpcProvider = () =>
59
65
  TagSpecifications: [
60
66
  {
61
67
  ResourceType: "vpc",
62
- Tags: createTagsList(allTags),
68
+ Tags: createTagsList(tags),
63
69
  },
64
70
  ],
65
71
  DryRun: false,
@@ -68,7 +74,7 @@ export const vpcProvider = () =>
68
74
  const vpcId = createResult.Vpc!.VpcId! as VpcId;
69
75
  yield* session.note(`VPC created: ${vpcId}`);
70
76
 
71
- // 3. Modify DNS attributes if specified (separate API calls)
77
+ // 2. Modify DNS attributes if specified (separate API calls)
72
78
  yield* ec2.modifyVpcAttribute({
73
79
  VpcId: vpcId,
74
80
  EnableDnsSupport: { Value: news.enableDnsSupport ?? true },
@@ -116,10 +122,11 @@ export const vpcProvider = () =>
116
122
  ipv6Pool: assoc.Ipv6Pool,
117
123
  }),
118
124
  ),
125
+ tags,
119
126
  } satisfies VpcAttrs<VpcProps>;
120
127
  }),
121
128
 
122
- update: Effect.fn(function* ({ news, olds, output, session }) {
129
+ update: Effect.fn(function* ({ id, news, olds, output, session }) {
123
130
  const vpcId = output.vpcId;
124
131
 
125
132
  // Only DNS and metrics settings can be updated
@@ -141,9 +148,29 @@ export const vpcProvider = () =>
141
148
  yield* session.note("Updated DNS hostnames");
142
149
  }
143
150
 
144
- // Note: Tag updates would go here if we support user tag changes
151
+ // Handle user tag updates
152
+ const newTags = createTags(id, news.tags);
153
+ const oldTags = output.tags ?? {};
154
+ const { removed, upsert } = diffTags(oldTags, newTags);
155
+ if (removed.length > 0) {
156
+ yield* ec2.deleteTags({
157
+ Resources: [vpcId],
158
+ Tags: removed.map((key) => ({ Key: key })),
159
+ DryRun: false,
160
+ });
161
+ }
162
+ if (upsert.length > 0) {
163
+ yield* ec2.createTags({
164
+ Resources: [vpcId],
165
+ Tags: upsert,
166
+ DryRun: false,
167
+ });
168
+ }
145
169
 
146
- return output; // VPC attributes don't change from these updates
170
+ return {
171
+ ...output,
172
+ tags: newTags,
173
+ }; // VPC attributes don't change from these updates
147
174
  }),
148
175
 
149
176
  delete: Effect.fn(function* ({ output, session }) {
@@ -165,10 +192,7 @@ export const vpcProvider = () =>
165
192
  while: (e) => {
166
193
  // DependencyViolation means there are still dependent resources
167
194
  // This can happen if subnets/IGW are being deleted concurrently
168
- return (
169
- e._tag === "ValidationError" &&
170
- e.message?.includes("DependencyViolation")
171
- );
195
+ return e._tag === "DependencyViolation";
172
196
  },
173
197
  schedule: Schedule.exponential(1000, 1.5).pipe(
174
198
  Schedule.intersect(Schedule.recurs(10)), // Try up to 10 times
@@ -160,4 +160,6 @@ export interface VpcAttrs<_Props extends VpcProps = VpcProps> {
160
160
  networkBorderGroup?: string;
161
161
  ipv6Pool?: string;
162
162
  }>;
163
+
164
+ tags?: Record<string, string>;
163
165
  }
package/src/aws/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as Layer from "effect/Layer";
2
2
 
3
3
  // oxlint-disable-next-line no-unused-vars - needed or else provider types are transitively resolved through DynamoDB.Provider<..> lol
4
- import type { Provider } from "../provider.ts";
5
4
 
6
5
  import * as ESBuild from "../esbuild.ts";
7
6
  import * as Account from "./account.ts";
@@ -22,6 +21,10 @@ import "./config.ts";
22
21
  export const resources = () =>
23
22
  Layer.mergeAll(
24
23
  DynamoDB.tableProvider(),
24
+ EC2.internetGatewayProvider(),
25
+ EC2.routeProvider(),
26
+ EC2.routeTableAssociationProvider(),
27
+ EC2.routeTableProvider(),
25
28
  EC2.subnetProvider(),
26
29
  EC2.vpcProvider(),
27
30
  Lambda.functionProvider(),
@@ -14,12 +14,12 @@ import { App } from "../../app.ts";
14
14
  import { DotAlchemy } from "../../dot-alchemy.ts";
15
15
  import type { ProviderService } from "../../provider.ts";
16
16
  import { createTagger, createTagsList, hasTags } from "../../tags.ts";
17
+ import { Account } from "../account.ts";
17
18
  import * as IAM from "../iam.ts";
19
+ import { Region } from "../region.ts";
18
20
  import { zipCode } from "../zip.ts";
19
21
  import { LambdaClient } from "./client.ts";
20
22
  import { Function, type FunctionAttr, type FunctionProps } from "./function.ts";
21
- import { Account } from "../account.ts";
22
- import { Region } from "../region.ts";
23
23
 
24
24
  export const functionProvider = () =>
25
25
  Function.provider.effect(
@@ -426,6 +426,28 @@ export const functionProvider = () =>
426
426
  }`;
427
427
 
428
428
  return {
429
+ stables: ["functionArn", "functionName", "roleName"],
430
+ diff: Effect.fn(function* ({ id, olds, news, output }) {
431
+ if (
432
+ // function name changed
433
+ output.functionName !==
434
+ (news.functionName ?? createFunctionName(id)) ||
435
+ // url changed
436
+ olds.url !== news.url
437
+ ) {
438
+ return { action: "replace" };
439
+ }
440
+ if (
441
+ output.code.hash !==
442
+ (yield* bundleCode(id, {
443
+ main: news.main,
444
+ handler: news.handler,
445
+ })).hash
446
+ ) {
447
+ // code changed
448
+ return { action: "update" };
449
+ }
450
+ }),
429
451
  read: Effect.fn(function* ({ id, output }) {
430
452
  if (output) {
431
453
  yield* Effect.logDebug(`reading function ${id}`);
@@ -452,27 +474,7 @@ export const functionProvider = () =>
452
474
  }
453
475
  return output;
454
476
  }),
455
- diff: Effect.fn(function* ({ id, olds, news, output }) {
456
- if (
457
- // function name changed
458
- output.functionName !==
459
- (news.functionName ?? createFunctionName(id)) ||
460
- // url changed
461
- olds.url !== news.url
462
- ) {
463
- return { action: "replace" };
464
- }
465
- if (
466
- output.code.hash !==
467
- (yield* bundleCode(id, {
468
- main: news.main,
469
- handler: news.handler,
470
- })).hash
471
- ) {
472
- // code changed
473
- return { action: "update" };
474
- }
475
- }),
477
+
476
478
  precreate: Effect.fn(function* ({ id, news }) {
477
479
  const { roleName, functionName, roleArn } = createPhysicalNames(id);
478
480
 
@@ -3,10 +3,10 @@ import * as Schedule from "effect/Schedule";
3
3
 
4
4
  import { App } from "../../app.ts";
5
5
  import type { ProviderService } from "../../provider.ts";
6
+ import { Account } from "../account.ts";
7
+ import { Region } from "../region.ts";
6
8
  import { SQSClient } from "./client.ts";
7
9
  import { Queue, type QueueProps } from "./queue.ts";
8
- import { Region } from "../region.ts";
9
- import { Account } from "../account.ts";
10
10
 
11
11
  export const queueProvider = () =>
12
12
  Queue.provider.effect(
@@ -39,6 +39,7 @@ export const queueProvider = () =>
39
39
  VisibilityTimeout: props.visibilityTimeout?.toString(),
40
40
  });
41
41
  return {
42
+ stables: ["queueName", "queueUrl", "queueArn"],
42
43
  diff: Effect.fn(function* ({ id, news, olds }) {
43
44
  const oldFifo = olds.fifo ?? false;
44
45
  const newFifo = news.fifo ?? false;
@@ -29,6 +29,7 @@ export const namespaceProvider = () =>
29
29
  });
30
30
 
31
31
  return {
32
+ stables: ["namespaceId", "accountId"],
32
33
  diff: ({ id, news, output }) =>
33
34
  Effect.sync(() => {
34
35
  if (output.accountId !== accountId) {