alchemy 0.17.2 → 0.18.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 (54) hide show
  1. package/lib/ai/document.d.ts +11 -0
  2. package/lib/ai/document.d.ts.map +1 -1
  3. package/lib/ai/document.js +1 -1
  4. package/lib/ai/document.js.map +1 -1
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +1 -2
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/aws/control/client.d.ts +134 -0
  9. package/lib/aws/control/client.d.ts.map +1 -0
  10. package/lib/aws/control/client.js +232 -0
  11. package/lib/aws/control/client.js.map +1 -0
  12. package/lib/aws/control/error.d.ts +63 -0
  13. package/lib/aws/control/error.d.ts.map +1 -0
  14. package/lib/aws/control/error.js +93 -0
  15. package/lib/aws/control/error.js.map +1 -0
  16. package/lib/aws/control/index.d.ts +6 -0
  17. package/lib/aws/control/index.d.ts.map +1 -0
  18. package/lib/aws/control/index.js +6 -0
  19. package/lib/aws/control/index.js.map +1 -0
  20. package/lib/aws/control/properties.d.ts +1874 -0
  21. package/lib/aws/control/properties.d.ts.map +1 -0
  22. package/lib/aws/control/properties.js +6945 -0
  23. package/lib/aws/control/properties.js.map +1 -0
  24. package/lib/aws/control/proxy.d.ts +7 -0
  25. package/lib/aws/control/proxy.d.ts.map +1 -0
  26. package/lib/aws/control/proxy.js +29 -0
  27. package/lib/aws/control/proxy.js.map +1 -0
  28. package/lib/aws/control/resource.d.ts +110 -0
  29. package/lib/aws/control/resource.d.ts.map +1 -0
  30. package/lib/aws/control/resource.js +192 -0
  31. package/lib/aws/control/resource.js.map +1 -0
  32. package/lib/destroy.d.ts.map +1 -1
  33. package/lib/destroy.js +2 -2
  34. package/lib/destroy.js.map +1 -1
  35. package/lib/resource.d.ts +7 -1
  36. package/lib/resource.d.ts.map +1 -1
  37. package/lib/resource.js +20 -0
  38. package/lib/resource.js.map +1 -1
  39. package/lib/test/bun.d.ts.map +1 -1
  40. package/lib/test/bun.js +1 -2
  41. package/lib/test/bun.js.map +1 -1
  42. package/package.json +5 -1
  43. package/src/ai/document.ts +1 -1
  44. package/src/alchemy.ts +2 -3
  45. package/src/aws/control/client.ts +450 -0
  46. package/src/aws/control/error.ts +84 -0
  47. package/src/aws/control/index.ts +6 -0
  48. package/src/aws/control/properties.ts +6946 -0
  49. package/src/aws/control/proxy.ts +40 -0
  50. package/src/aws/control/resource.ts +346 -0
  51. package/src/aws/control/types.d.ts +85129 -0
  52. package/src/destroy.ts +4 -7
  53. package/src/resource.ts +32 -1
  54. package/src/test/bun.ts +1 -2
@@ -0,0 +1,40 @@
1
+ import { createResourceType } from "./resource.js";
2
+
3
+ /**
4
+ * Proxy-based interface for dynamically accessing AWS resource handlers.
5
+ * This allows for a natural, namespace-like access pattern (e.g., AWS.S3.Bucket)
6
+ * while lazily creating resource handlers as needed.
7
+ */
8
+ export const AWS = new Proxy(
9
+ {},
10
+ {
11
+ get(_, serviceName: string) {
12
+ // Skip internal properties
13
+ if (typeof serviceName !== "string" || serviceName.startsWith("_")) {
14
+ return undefined;
15
+ }
16
+
17
+ // Create a nested proxy for the service namespace
18
+ return new Proxy(
19
+ {},
20
+ {
21
+ get(_, resourceName: string) {
22
+ // Skip internal properties
23
+ if (
24
+ typeof resourceName !== "string" ||
25
+ resourceName.startsWith("_")
26
+ ) {
27
+ return undefined;
28
+ }
29
+
30
+ // Construct the CloudFormation resource type name
31
+ const typeName = `AWS::${serviceName}::${resourceName}`;
32
+
33
+ // Return the memoized resource handler
34
+ return createResourceType(typeName);
35
+ },
36
+ },
37
+ );
38
+ },
39
+ },
40
+ ) as typeof import("./types");
@@ -0,0 +1,346 @@
1
+ import { compare } from "fast-json-patch";
2
+ import type { Context } from "../../context.js";
3
+ import {
4
+ registerDynamicResource,
5
+ Resource,
6
+ type Provider,
7
+ } from "../../resource.js";
8
+ import { createCloudControlClient, type ProgressEvent } from "./client.js";
9
+ import {
10
+ AlreadyExistsError,
11
+ ConcurrentOperationError,
12
+ UpdateFailedError,
13
+ } from "./error.js";
14
+ import readOnlyPropertiesMap from "./properties.js";
15
+
16
+ /**
17
+ * Properties for creating or updating a Cloud Control resource
18
+ */
19
+ export interface CloudControlResourceProps {
20
+ /**
21
+ * The type name of the resource (e.g. AWS::S3::Bucket)
22
+ */
23
+ typeName: string;
24
+
25
+ /**
26
+ * The desired state of the resource
27
+ */
28
+ desiredState: Record<string, any>;
29
+
30
+ /**
31
+ * If true, adopt existing resource instead of failing when resource already exists
32
+ */
33
+ adopt?: boolean;
34
+
35
+ /**
36
+ * Optional AWS region
37
+ * @default AWS_REGION environment variable
38
+ */
39
+ region?: string;
40
+
41
+ /**
42
+ * AWS access key ID (overrides environment variable)
43
+ */
44
+ accessKeyId?: string;
45
+
46
+ /**
47
+ * AWS secret access key (overrides environment variable)
48
+ */
49
+ secretAccessKey?: string;
50
+
51
+ /**
52
+ * AWS session token for temporary credentials
53
+ */
54
+ sessionToken?: string;
55
+ }
56
+
57
+ /**
58
+ * Output returned after Cloud Control resource creation/update
59
+ */
60
+ export interface CloudControlResource
61
+ extends Resource<"aws::CloudControlResource">,
62
+ CloudControlResourceProps {
63
+ /**
64
+ * The identifier of the resource
65
+ */
66
+ id: string;
67
+
68
+ /**
69
+ * Time at which the resource was created
70
+ */
71
+ createdAt: number;
72
+ }
73
+
74
+ // Register wildcard deletion handler for AWS::* pattern
75
+ // registerDeletionHandler(
76
+ // "AWS::*",
77
+ // //
78
+ // );
79
+
80
+ // Cache for memoizing resource handlers
81
+ const resourceHandlers: Record<string, any> = {};
82
+
83
+ /**
84
+ * Filters out read-only properties from a resource state
85
+ * @param typeName AWS resource type name (e.g., "AWS::S3::Bucket")
86
+ * @param state Resource state object
87
+ * @returns Filtered state object without read-only properties
88
+ */
89
+ function filterReadOnlyProperties(
90
+ typeName: string,
91
+ state: Record<string, any>,
92
+ ): Record<string, any> {
93
+ // Parse the type name to get service and resource
94
+ const [service, resource] = typeName.replace("AWS::", "").split("::");
95
+ const readOnlyProps =
96
+ (readOnlyPropertiesMap as any)[service]?.[resource] || [];
97
+
98
+ const filtered: Record<string, any> = {};
99
+ for (const [key, value] of Object.entries(state)) {
100
+ if (!readOnlyProps.includes(key)) {
101
+ filtered[key] = value;
102
+ }
103
+ }
104
+
105
+ return filtered;
106
+ }
107
+
108
+ /**
109
+ * Creates a memoized Resource handler for a CloudFormation resource type
110
+ *
111
+ * @param typeName CloudFormation resource type (e.g., "AWS::S3::Bucket")
112
+ * @returns A memoized Resource handler for the specified type
113
+ */
114
+ export function createResourceType(typeName: string) {
115
+ return (resourceHandlers[typeName] ??= Resource(
116
+ typeName,
117
+ function (
118
+ this: Context<CloudControlResource, CloudControlResourceProps>,
119
+ id: string,
120
+ props: Record<string, any> & {
121
+ adopt?: boolean;
122
+ region?: string;
123
+ accessKeyId?: string;
124
+ secretAccessKey?: string;
125
+ sessionToken?: string;
126
+ },
127
+ ) {
128
+ // Extract Alchemy-specific properties
129
+ const {
130
+ adopt,
131
+ region,
132
+ accessKeyId,
133
+ secretAccessKey,
134
+ sessionToken,
135
+ ...desiredState
136
+ } = props;
137
+
138
+ return CloudControlLifecycle.bind(this)(id, {
139
+ typeName,
140
+ desiredState,
141
+ adopt,
142
+ region,
143
+ accessKeyId,
144
+ secretAccessKey,
145
+ sessionToken,
146
+ });
147
+ },
148
+ ));
149
+ }
150
+
151
+ /**
152
+ * AWS Cloud Control Resource (Generic Handler)
153
+ *
154
+ * This exported resource provides a generic way to manage any AWS resource
155
+ * supported by the Cloud Control API by explicitly passing the `typeName`.
156
+ * It is intended for direct use when the specific resource type might not be
157
+ * known at compile time or when not using the typed Proxy interface.
158
+ *
159
+ * For the strongly-typed Proxy interface (e.g., `AWS.S3.Bucket(...)`), Alchemy
160
+ * uses internal handlers generated by the `createResourceType` factory function.
161
+ *
162
+ * Creates and manages AWS resources using the Cloud Control API.
163
+ *
164
+ * @example
165
+ * // Create an S3 bucket
166
+ * const bucket = await CloudControlResource("my-bucket", {
167
+ * typeName: "AWS::S3::Bucket",
168
+ * desiredState: {
169
+ * BucketName: "my-unique-bucket-name",
170
+ * VersioningConfiguration: {
171
+ * Status: "Enabled"
172
+ * }
173
+ * }
174
+ * });
175
+ *
176
+ * @example
177
+ * // Create a DynamoDB table
178
+ * const table = await CloudControlResource("users-table", {
179
+ * typeName: "AWS::DynamoDB::Table",
180
+ * desiredState: {
181
+ * TableName: "users",
182
+ * AttributeDefinitions: [
183
+ * {
184
+ * AttributeName: "id",
185
+ * AttributeType: "S"
186
+ * }
187
+ * ],
188
+ * KeySchema: [
189
+ * {
190
+ * AttributeName: "id",
191
+ * KeyType: "HASH"
192
+ * }
193
+ * ],
194
+ * ProvisionedThroughput: {
195
+ * ReadCapacityUnits: 5,
196
+ * WriteCapacityUnits: 5
197
+ * }
198
+ * }
199
+ * });
200
+ */
201
+ export const CloudControlResource = Resource(
202
+ "aws::CloudControlResource",
203
+ CloudControlLifecycle,
204
+ );
205
+
206
+ // register a catch-all for AWS::* resources (Resources created with the Control API)
207
+ registerDynamicResource((typeName) => {
208
+ if (typeName.startsWith("AWS::")) {
209
+ return Resource(typeName, CloudControlLifecycle) as unknown as Provider;
210
+ }
211
+ return undefined;
212
+ });
213
+
214
+ async function CloudControlLifecycle(
215
+ this: Context<CloudControlResource, CloudControlResourceProps>,
216
+ id: string,
217
+ props: CloudControlResourceProps,
218
+ ) {
219
+ const client = await createCloudControlClient({
220
+ region: props.region,
221
+ accessKeyId: props.accessKeyId,
222
+ secretAccessKey: props.secretAccessKey,
223
+ sessionToken: props.sessionToken,
224
+ });
225
+
226
+ if (this.phase === "delete") {
227
+ if (this.output?.id) {
228
+ try {
229
+ await client.deleteResource(props.typeName, this.output.id);
230
+ } catch (error) {
231
+ // Log but don't throw on cleanup errors
232
+ console.error(`Error deleting resource ${id}:`, error);
233
+ }
234
+ }
235
+ return this.destroy();
236
+ }
237
+
238
+ let response: ProgressEvent | undefined;
239
+ if (this.phase === "update" && this.output?.id) {
240
+ // Update existing resource
241
+ response = await updateResourceWithPatch(
242
+ client,
243
+ props.typeName,
244
+ this.output.id,
245
+ this.output.desiredState,
246
+ props.desiredState,
247
+ );
248
+ } else {
249
+ // Create new resource
250
+ try {
251
+ response = await client.createResource(
252
+ props.typeName,
253
+ props.desiredState,
254
+ );
255
+ } catch (error) {
256
+ if (error instanceof AlreadyExistsError && props.adopt) {
257
+ const resource = (await client.getResource(
258
+ props.typeName,
259
+ error.progressEvent.Identifier!,
260
+ ))!;
261
+
262
+ response = await updateResourceWithPatch(
263
+ client,
264
+ props.typeName,
265
+ error.progressEvent.Identifier!,
266
+ resource,
267
+ props.desiredState,
268
+ );
269
+ } else if (error instanceof ConcurrentOperationError) {
270
+ // Handle concurrent operation exception
271
+ console.log(error.message);
272
+ if (!props.adopt) {
273
+ // If adopt is not true, concurrent operations are an error
274
+ throw error;
275
+ }
276
+ console.log(
277
+ `Waiting for concurrent operation with request token '${error.requestToken}' to complete`,
278
+ );
279
+
280
+ // Wait for the concurrent operation to complete by polling it
281
+ try {
282
+ // Poll the concurrent operation until it completes
283
+ const concurrentResult = await client.poll(error.requestToken);
284
+
285
+ // The concurrent operation succeeded, now adopt the resource
286
+ const resource = (await client.getResource(
287
+ props.typeName,
288
+ concurrentResult.Identifier!,
289
+ ))!;
290
+
291
+ // Apply our desired state as a patch to the existing resource
292
+ response = await updateResourceWithPatch(
293
+ client,
294
+ props.typeName,
295
+ concurrentResult.Identifier!,
296
+ resource,
297
+ props.desiredState,
298
+ );
299
+ } catch (pollError) {
300
+ // If the concurrent operation failed, we can try to create the resource ourselves
301
+ if (pollError instanceof UpdateFailedError) {
302
+ response = await client.createResource(
303
+ props.typeName,
304
+ props.desiredState,
305
+ );
306
+ } else {
307
+ throw pollError;
308
+ }
309
+ }
310
+ } else {
311
+ throw error;
312
+ }
313
+ }
314
+ }
315
+
316
+ if (response.OperationStatus === "FAILED") {
317
+ throw new Error(
318
+ `Failed to ${this.phase} resource ${id}: ${response.ErrorCode}`,
319
+ );
320
+ }
321
+
322
+ return this({
323
+ ...props,
324
+ id: response.Identifier!,
325
+ createdAt: Date.now(),
326
+ ...(await client.getResource(props.typeName, response.Identifier!)),
327
+ });
328
+ }
329
+
330
+ async function updateResourceWithPatch(
331
+ client: any,
332
+ typeName: string,
333
+ resourceId: string,
334
+ currentState: Record<string, any>,
335
+ desiredState: Record<string, any>,
336
+ ): Promise<ProgressEvent> {
337
+ // Filter out read-only properties to avoid patch conflicts
338
+ const filteredCurrentState = filterReadOnlyProperties(typeName, currentState);
339
+
340
+ // Create and apply patch
341
+ return await client.updateResource(
342
+ typeName,
343
+ resourceId,
344
+ compare(filteredCurrentState, desiredState),
345
+ );
346
+ }