alchemy 0.0.8 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/package.json +23 -22
  2. package/src/alchemize.ts +151 -0
  3. package/src/apply.ts +117 -0
  4. package/src/components/agent/agent.ts +45 -0
  5. package/src/components/agent/anthropic.ts +27 -0
  6. package/src/components/agent/index.ts +4 -0
  7. package/src/components/agent/model.ts +22 -0
  8. package/src/components/agent/openai.ts +44 -0
  9. package/src/components/agent/package.ts +126 -0
  10. package/src/components/agent/requirements.ts +111 -0
  11. package/src/components/agent/scrape.ts +47 -0
  12. package/src/components/agent/tsconfig.ts +150 -0
  13. package/src/components/agent/typescript.ts +177 -0
  14. package/src/components/aws/bucket.ts +168 -0
  15. package/src/components/aws/function.ts +302 -0
  16. package/src/components/aws/index.ts +6 -0
  17. package/src/components/aws/policy.ts +245 -0
  18. package/src/components/aws/queue.ts +222 -0
  19. package/src/components/aws/role.ts +245 -0
  20. package/src/components/aws/table.ts +172 -0
  21. package/src/components/esbuild.ts +128 -0
  22. package/src/components/fs.ts +45 -0
  23. package/src/destroy.ts +63 -0
  24. package/src/error.ts +16 -0
  25. package/src/global.ts +72 -0
  26. package/src/index.ts +6 -0
  27. package/src/input.ts +16 -0
  28. package/src/main.ts +34 -0
  29. package/src/output.ts +49 -0
  30. package/src/resource.ts +313 -0
  31. package/src/state.ts +100 -0
  32. package/test/aws/function.test.ts +144 -0
  33. package/test/aws/queue.test.ts +85 -0
  34. package/test/aws/role.test.ts +153 -0
  35. package/test/aws/table.test.ts +73 -0
  36. package/test/esbuild.test.ts +41 -0
  37. package/test/handler.ts +9 -0
  38. package/tsconfig.json +7 -0
  39. package/Column.js +0 -1
  40. package/Create.js +0 -1
  41. package/Delete.js +0 -1
  42. package/Drop.js +0 -1
  43. package/Insert.js +0 -1
  44. package/Makefile +0 -8
  45. package/README.md +0 -0
  46. package/Select.js +0 -1
  47. package/Table.js +0 -1
  48. package/Transaction.js +0 -1
  49. package/Types.js +0 -1
  50. package/Update.js +0 -1
  51. package/alchemy.js +0 -57
  52. package/alchemy.sublime-project +0 -9
  53. package/alchemy.sublime-workspace +0 -2304
  54. package/config/app.js +0 -8
  55. package/config/db.js +0 -20
  56. package/copy.js +0 -1
  57. package/defaultEngine.js +0 -8
  58. package/engine.js +0 -282
  59. package/getInstantiatorFunction.js +0 -28
  60. package/index.js +0 -52
  61. package/jstest.js +0 -3
  62. package/loggerFactory.js +0 -27
  63. package/logs/alchemy.log +0 -46
  64. package/logs/sql.log +0 -41
  65. package/sql/Column.js +0 -158
  66. package/sql/Columnable.js +0 -21
  67. package/sql/Create.js +0 -15
  68. package/sql/Delete.js +0 -34
  69. package/sql/Drop.js +0 -36
  70. package/sql/Insert.js +0 -96
  71. package/sql/SchemaElement.js +0 -11
  72. package/sql/Select.js +0 -192
  73. package/sql/SqlBase.js +0 -134
  74. package/sql/SqlStatement.js +0 -80
  75. package/sql/Table.js +0 -96
  76. package/sql/Transaction.js +0 -45
  77. package/sql/Update.js +0 -105
  78. package/sql/defineElement.js +0 -26
  79. package/sql/defineStatement.js +0 -26
  80. package/sql/types/BaseType.js +0 -43
  81. package/sql/types/BigSerialType.js +0 -5
  82. package/sql/types/IntegerType.js +0 -5
  83. package/sql/types/JsonType.js +0 -5
  84. package/sql/types/SerialType.js +0 -5
  85. package/sql/types/StringType.js +0 -31
  86. package/sql/types/TimestampType.js +0 -5
  87. package/sql/types/UUIDType.js +0 -5
  88. package/sql/types/defineType.js +0 -25
  89. package/sql/types/getType.js +0 -12
  90. package/sql/types/index.js +0 -11
  91. package/sql/where.js +0 -31
  92. package/sql/whereToString.js +0 -29
  93. package/test/ColumnTests.js +0 -80
  94. package/test/CreateTable.js +0 -54
  95. package/test/DeleteTests.js +0 -28
  96. package/test/DropTests.js +0 -23
  97. package/test/GetTypeTests.js +0 -56
  98. package/test/InsertTests.js +0 -86
  99. package/test/SelectExistsTests.js +0 -26
  100. package/test/SelectTests.js +0 -130
  101. package/test/TableTests.js +0 -46
  102. package/test.js +0 -8
  103. package/test.ls +0 -4
  104. package/validate.js +0 -26
  105. package/x.js +0 -5
@@ -0,0 +1,302 @@
1
+ import {
2
+ Architecture,
3
+ CreateFunctionCommand,
4
+ DeleteFunctionCommand,
5
+ GetFunctionCommand,
6
+ GetFunctionConfigurationCommand,
7
+ LambdaClient,
8
+ ResourceNotFoundException,
9
+ Runtime,
10
+ UpdateFunctionCodeCommand,
11
+ UpdateFunctionConfigurationCommand,
12
+ } from "@aws-sdk/client-lambda";
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { ignore } from "../../error";
16
+ import { Resource, type Context } from "../../resource";
17
+
18
+ async function resolveRegion(client: LambdaClient): Promise<string> {
19
+ const region = client.config.region;
20
+ if (typeof region === "string") return region;
21
+ if (typeof region === "function") return region();
22
+ throw new Error("Could not resolve AWS region");
23
+ }
24
+
25
+ export interface FunctionProps {
26
+ functionName: string;
27
+ zipPath: string;
28
+ roleArn: string;
29
+ handler?: string;
30
+ runtime?: Runtime;
31
+ architecture?: Architecture;
32
+ description?: string;
33
+ timeout?: number;
34
+ memorySize?: number;
35
+ environment?: Record<string, string>;
36
+ tags?: Record<string, string>;
37
+ url?: {
38
+ authType?: "AWS_IAM" | "NONE";
39
+ cors?: {
40
+ allowCredentials?: boolean;
41
+ allowHeaders?: string[];
42
+ allowMethods?: string[];
43
+ allowOrigins?: string[];
44
+ exposeHeaders?: string[];
45
+ maxAge?: number;
46
+ };
47
+ };
48
+ }
49
+
50
+ export interface FunctionOutput extends FunctionProps {
51
+ id: string; // Same as functionName
52
+ arn: string;
53
+ lastModified: string;
54
+ version: string;
55
+ qualifiedArn: string; // ARN with version
56
+ invokeArn: string; // ARN for API Gateway
57
+ sourceCodeHash: string;
58
+ sourceCodeSize: number;
59
+ ephemeralStorageSize?: number;
60
+ architectures: string[];
61
+ masterArn?: string; // Only for Lambda@Edge
62
+ revisionId: string;
63
+ state?: string;
64
+ stateReason?: string;
65
+ stateReasonCode?: string;
66
+ lastUpdateStatus?: string;
67
+ lastUpdateStatusReason?: string;
68
+ lastUpdateStatusReasonCode?: string;
69
+ packageType: string;
70
+ signingProfileVersionArn?: string;
71
+ signingJobArn?: string;
72
+ }
73
+
74
+ export class Function extends Resource(
75
+ "lambda::Function",
76
+ async (ctx: Context<FunctionOutput>, props: FunctionProps) => {
77
+ const client = new LambdaClient({});
78
+ const region = await resolveRegion(client);
79
+
80
+ const code = await zipCode(props.zipPath);
81
+
82
+ if (ctx.event === "delete") {
83
+ await ignore(ResourceNotFoundException.name, () =>
84
+ client.send(
85
+ new DeleteFunctionCommand({
86
+ FunctionName: props.functionName,
87
+ }),
88
+ ),
89
+ );
90
+
91
+ // Return a minimal FunctionOutput for deleted state
92
+ return {
93
+ ...props,
94
+ id: props.functionName,
95
+ arn: `arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}`,
96
+ lastModified: new Date().toISOString(),
97
+ version: "$LATEST",
98
+ qualifiedArn: `arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}:$LATEST`,
99
+ invokeArn: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}/invocations`,
100
+ sourceCodeHash: "",
101
+ sourceCodeSize: 0,
102
+ architectures: props.architecture
103
+ ? [props.architecture]
104
+ : [Architecture.x86_64],
105
+ revisionId: "",
106
+ state: "Inactive",
107
+ packageType: "Zip",
108
+ };
109
+ } else {
110
+ try {
111
+ // Check if function exists
112
+ const func = await client.send(
113
+ new GetFunctionCommand({
114
+ FunctionName: props.functionName,
115
+ }),
116
+ );
117
+
118
+ if (ctx.event === "update") {
119
+ // Wait for function to stabilize
120
+ await waitForFunctionStabilization(client, props.functionName);
121
+
122
+ // Update function code
123
+ await client.send(
124
+ new UpdateFunctionCodeCommand({
125
+ FunctionName: props.functionName,
126
+ ZipFile: code,
127
+ }),
128
+ );
129
+
130
+ // Wait for code update to stabilize
131
+ await waitForFunctionStabilization(client, props.functionName);
132
+
133
+ // Update function configuration
134
+ await client.send(
135
+ new UpdateFunctionConfigurationCommand({
136
+ FunctionName: props.functionName,
137
+ Handler: props.handler,
138
+ Runtime: props.runtime,
139
+ Role: props.roleArn,
140
+ Description: props.description,
141
+ Timeout: props.timeout,
142
+ MemorySize: props.memorySize,
143
+ Environment: props.environment
144
+ ? { Variables: props.environment }
145
+ : undefined,
146
+ }),
147
+ );
148
+
149
+ // Wait for configuration update to stabilize
150
+ await waitForFunctionStabilization(client, props.functionName);
151
+ }
152
+ } catch (error: any) {
153
+ if (error.name === "ResourceNotFoundException") {
154
+ // Create function if it doesn't exist
155
+ const startTime = Date.now();
156
+ let delay = 100; // Start with 100ms delay
157
+
158
+ while (true) {
159
+ try {
160
+ await client.send(
161
+ new CreateFunctionCommand({
162
+ FunctionName: props.functionName,
163
+ Code: { ZipFile: code },
164
+ Handler: props.handler || "index.handler",
165
+ Runtime: props.runtime || Runtime.nodejs20x,
166
+ Role: props.roleArn,
167
+ Description: props.description,
168
+ Timeout: props.timeout || 3,
169
+ MemorySize: props.memorySize || 128,
170
+ Environment: props.environment
171
+ ? { Variables: props.environment }
172
+ : undefined,
173
+ Architectures: props.architecture
174
+ ? [props.architecture]
175
+ : [Architecture.x86_64],
176
+ Tags: props.tags,
177
+ }),
178
+ );
179
+ break; // Success - exit retry loop
180
+ } catch (createError: any) {
181
+ if (
182
+ createError.name !== "InvalidParameterValueException" ||
183
+ !createError.message?.includes("cannot be assumed by Lambda")
184
+ ) {
185
+ throw createError; // Different error - rethrow
186
+ }
187
+
188
+ if (Date.now() - startTime > 10000) {
189
+ throw new Error(
190
+ "Timeout waiting for IAM role to be assumable by Lambda after 10s",
191
+ );
192
+ }
193
+
194
+ await new Promise((resolve) => setTimeout(resolve, delay));
195
+ delay = Math.min(delay * 2, 1000); // Exponential backoff capped at 1s
196
+ }
197
+ }
198
+
199
+ // Wait for function to be active
200
+ let isCreating = true;
201
+ while (isCreating) {
202
+ const config = await client.send(
203
+ new GetFunctionConfigurationCommand({
204
+ FunctionName: props.functionName,
205
+ }),
206
+ );
207
+ isCreating = config.State === "Pending";
208
+ if (isCreating) {
209
+ await new Promise((resolve) => setTimeout(resolve, 1000));
210
+ }
211
+ }
212
+ } else {
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ // Get complete function details
218
+ const [func, config] = await Promise.all([
219
+ client.send(
220
+ new GetFunctionCommand({
221
+ FunctionName: props.functionName,
222
+ }),
223
+ ),
224
+ client.send(
225
+ new GetFunctionConfigurationCommand({
226
+ FunctionName: props.functionName,
227
+ }),
228
+ ),
229
+ ]);
230
+
231
+ const output: FunctionOutput = {
232
+ ...props,
233
+ id: props.functionName,
234
+ arn: config.FunctionArn!,
235
+ lastModified: config.LastModified!,
236
+ version: config.Version!,
237
+ qualifiedArn: `${config.FunctionArn}:${config.Version}`,
238
+ invokeArn: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${config.FunctionArn}/invocations`,
239
+ sourceCodeHash: config.CodeSha256!,
240
+ sourceCodeSize: config.CodeSize!,
241
+ ephemeralStorageSize: config.EphemeralStorage?.Size,
242
+ architectures: config.Architectures || [],
243
+ masterArn: config.MasterArn,
244
+ revisionId: config.RevisionId!,
245
+ state: config.State,
246
+ stateReason: config.StateReason,
247
+ stateReasonCode: config.StateReasonCode,
248
+ lastUpdateStatus: config.LastUpdateStatus,
249
+ lastUpdateStatusReason: config.LastUpdateStatusReason,
250
+ lastUpdateStatusReasonCode: config.LastUpdateStatusReasonCode,
251
+ packageType: config.PackageType!,
252
+ signingProfileVersionArn: config.SigningProfileVersionArn,
253
+ signingJobArn: config.SigningJobArn,
254
+ };
255
+
256
+ return output;
257
+ }
258
+ },
259
+ ) {}
260
+
261
+ // Helper to wait for function to stabilize
262
+ async function waitForFunctionStabilization(
263
+ client: LambdaClient,
264
+ functionName: string,
265
+ ) {
266
+ while (true) {
267
+ const config = await client.send(
268
+ new GetFunctionConfigurationCommand({
269
+ FunctionName: functionName,
270
+ }),
271
+ );
272
+
273
+ // Check if function is in a stable state
274
+ if (config.State === "Active" && config.LastUpdateStatus === "Successful") {
275
+ break;
276
+ }
277
+
278
+ // If there's a failure, throw an error
279
+ if (config.State === "Failed" || config.LastUpdateStatus === "Failed") {
280
+ throw new Error(
281
+ `Function failed to stabilize: ${config.StateReason || config.LastUpdateStatusReason}`,
282
+ );
283
+ }
284
+
285
+ await new Promise((resolve) => setTimeout(resolve, 1000));
286
+ }
287
+ }
288
+
289
+ // Helper to zip the code
290
+ async function zipCode(filePath: string): Promise<Buffer> {
291
+ const fileContent = await fs.promises.readFile(filePath);
292
+ const fileName = path.basename(filePath);
293
+
294
+ // Create a zip buffer in memory
295
+ const zip = new (await import("jszip")).default();
296
+ zip.file(fileName, fileContent);
297
+ return zip.generateAsync({
298
+ type: "nodebuffer",
299
+ compression: "DEFLATE",
300
+ platform: "UNIX",
301
+ });
302
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./bucket";
2
+ export * from "./function";
3
+ export * from "./policy";
4
+ export * from "./queue";
5
+ export * from "./role";
6
+ export * from "./table";
@@ -0,0 +1,245 @@
1
+ import {
2
+ AttachRolePolicyCommand,
3
+ CreatePolicyCommand,
4
+ CreatePolicyVersionCommand,
5
+ DeletePolicyCommand,
6
+ DeletePolicyVersionCommand,
7
+ DetachRolePolicyCommand,
8
+ GetPolicyCommand,
9
+ GetPolicyVersionCommand,
10
+ IAMClient,
11
+ ListPolicyVersionsCommand,
12
+ NoSuchEntityException,
13
+ } from "@aws-sdk/client-iam";
14
+ import { ignore } from "../../error";
15
+ import { Resource } from "../../resource";
16
+
17
+ // Type-safe policy document types
18
+ export type Effect = "Allow" | "Deny";
19
+
20
+ export interface PolicyStatement {
21
+ Sid?: string;
22
+ Effect: Effect;
23
+ Action: string | string[];
24
+ Resource?: string | string[];
25
+ Condition?: Record<string, Record<string, string | string[]>>;
26
+ Principal?: Record<string, string | string[]>;
27
+ NotPrincipal?: Record<string, string | string[]>;
28
+ NotAction?: string | string[];
29
+ NotResource?: string | string[];
30
+ }
31
+
32
+ export interface PolicyDocument {
33
+ Version: "2012-10-17";
34
+ Statement: PolicyStatement[];
35
+ }
36
+
37
+ // Policy resource
38
+ export interface PolicyProps {
39
+ policyName: string;
40
+ document: PolicyDocument;
41
+ description?: string;
42
+ path?: string;
43
+ tags?: Record<string, string>;
44
+ }
45
+
46
+ export interface PolicyOutput extends PolicyProps {
47
+ id: string; // Same as policyName
48
+ arn: string;
49
+ defaultVersionId: string;
50
+ attachmentCount: number;
51
+ createDate: Date;
52
+ updateDate: Date;
53
+ isAttachable: boolean;
54
+ }
55
+
56
+ export class Policy extends Resource(
57
+ "iam::Policy",
58
+ async (ctx, props: PolicyProps): Promise<PolicyOutput> => {
59
+ const client = new IAMClient({});
60
+ const policyArn = `arn:aws:iam::${process.env.AWS_ACCOUNT_ID}:policy${props.path || "/"}${props.policyName}`;
61
+
62
+ if (ctx.event === "delete") {
63
+ try {
64
+ // List and delete all non-default versions first
65
+ const versions = await client.send(
66
+ new ListPolicyVersionsCommand({
67
+ PolicyArn: policyArn,
68
+ }),
69
+ );
70
+
71
+ for (const version of versions.Versions || []) {
72
+ if (!version.IsDefaultVersion) {
73
+ await client.send(
74
+ new DeletePolicyVersionCommand({
75
+ PolicyArn: policyArn,
76
+ VersionId: version.VersionId,
77
+ }),
78
+ );
79
+ }
80
+ }
81
+
82
+ // Delete the policy
83
+ await client.send(
84
+ new DeletePolicyCommand({
85
+ PolicyArn: policyArn,
86
+ }),
87
+ );
88
+ } catch (error: any) {
89
+ if (error.name !== NoSuchEntityException.name) {
90
+ throw error;
91
+ }
92
+ }
93
+ return {
94
+ ...props,
95
+ id: props.policyName,
96
+ arn: policyArn,
97
+ defaultVersionId: "v1",
98
+ attachmentCount: 0,
99
+ createDate: new Date(),
100
+ updateDate: new Date(),
101
+ isAttachable: true,
102
+ };
103
+ } else {
104
+ try {
105
+ // Check if policy exists
106
+ const existingPolicy = await client.send(
107
+ new GetPolicyCommand({
108
+ PolicyArn: policyArn,
109
+ }),
110
+ );
111
+
112
+ // Get current policy version
113
+ const currentVersion = await client.send(
114
+ new GetPolicyVersionCommand({
115
+ PolicyArn: policyArn,
116
+ VersionId: existingPolicy.Policy!.DefaultVersionId!,
117
+ }),
118
+ );
119
+
120
+ const currentDocument = JSON.parse(
121
+ decodeURIComponent(currentVersion.PolicyVersion!.Document!),
122
+ );
123
+
124
+ // If policy document changed, create new version
125
+ if (
126
+ JSON.stringify(currentDocument) !== JSON.stringify(props.document)
127
+ ) {
128
+ // List versions to check if we need to delete old ones
129
+ const versions = await client.send(
130
+ new ListPolicyVersionsCommand({
131
+ PolicyArn: policyArn,
132
+ }),
133
+ );
134
+
135
+ // Delete oldest version if we have 5 versions (maximum allowed)
136
+ if (versions.Versions?.length === 5) {
137
+ const oldestVersion = versions.Versions.sort(
138
+ (a, b) => a.CreateDate!.getTime() - b.CreateDate!.getTime(),
139
+ )[0];
140
+
141
+ if (!oldestVersion.IsDefaultVersion) {
142
+ await client.send(
143
+ new DeletePolicyVersionCommand({
144
+ PolicyArn: policyArn,
145
+ VersionId: oldestVersion.VersionId!,
146
+ }),
147
+ );
148
+ }
149
+ }
150
+
151
+ // Create new version
152
+ await client.send(
153
+ new CreatePolicyVersionCommand({
154
+ PolicyArn: policyArn,
155
+ PolicyDocument: JSON.stringify(props.document),
156
+ SetAsDefault: true,
157
+ }),
158
+ );
159
+ }
160
+
161
+ const policy = await client.send(
162
+ new GetPolicyCommand({
163
+ PolicyArn: policyArn,
164
+ }),
165
+ );
166
+
167
+ return {
168
+ ...props,
169
+ id: props.policyName,
170
+ arn: policy.Policy!.Arn!,
171
+ defaultVersionId: policy.Policy!.DefaultVersionId!,
172
+ attachmentCount: policy.Policy!.AttachmentCount!,
173
+ createDate: policy.Policy!.CreateDate!,
174
+ updateDate: policy.Policy!.UpdateDate!,
175
+ isAttachable: policy.Policy!.IsAttachable!,
176
+ };
177
+ } catch (error: any) {
178
+ if (error.name === "NoSuchEntity") {
179
+ // Create new policy
180
+ const newPolicy = await client.send(
181
+ new CreatePolicyCommand({
182
+ PolicyName: props.policyName,
183
+ PolicyDocument: JSON.stringify(props.document),
184
+ Description: props.description,
185
+ Path: props.path,
186
+ Tags: props.tags
187
+ ? Object.entries(props.tags).map(([Key, Value]) => ({
188
+ Key,
189
+ Value,
190
+ }))
191
+ : undefined,
192
+ }),
193
+ );
194
+
195
+ return {
196
+ ...props,
197
+ id: props.policyName,
198
+ arn: newPolicy.Policy!.Arn!,
199
+ defaultVersionId: newPolicy.Policy!.DefaultVersionId!,
200
+ attachmentCount: newPolicy.Policy!.AttachmentCount!,
201
+ createDate: newPolicy.Policy!.CreateDate!,
202
+ updateDate: newPolicy.Policy!.UpdateDate!,
203
+ isAttachable: newPolicy.Policy!.IsAttachable!,
204
+ };
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+ },
210
+ ) {}
211
+
212
+ // PolicyAttachment resource
213
+ export interface PolicyAttachmentProps {
214
+ policyArn: string;
215
+ roleName: string;
216
+ }
217
+
218
+ export interface PolicyAttachmentOutput extends PolicyAttachmentProps {}
219
+
220
+ export class PolicyAttachment extends Resource(
221
+ "iam::PolicyAttachment",
222
+ async (ctx, props: PolicyAttachmentProps) => {
223
+ const client = new IAMClient({});
224
+
225
+ if (ctx.event === "delete") {
226
+ await ignore(NoSuchEntityException.name, () =>
227
+ client.send(
228
+ new DetachRolePolicyCommand({
229
+ PolicyArn: props.policyArn,
230
+ RoleName: props.roleName,
231
+ }),
232
+ ),
233
+ );
234
+ } else {
235
+ await client.send(
236
+ new AttachRolePolicyCommand({
237
+ PolicyArn: props.policyArn,
238
+ RoleName: props.roleName,
239
+ }),
240
+ );
241
+ }
242
+
243
+ return props;
244
+ },
245
+ ) {}