alchemy 0.0.8 → 0.1.2

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/README.md +355 -0
  2. package/package.json +24 -20
  3. package/src/alchemize.ts +151 -0
  4. package/src/apply.ts +117 -0
  5. package/src/components/agent/agent.ts +45 -0
  6. package/src/components/agent/anthropic.ts +27 -0
  7. package/src/components/agent/index.ts +4 -0
  8. package/src/components/agent/model.ts +22 -0
  9. package/src/components/agent/openai.ts +44 -0
  10. package/src/components/agent/package.ts +126 -0
  11. package/src/components/agent/requirements.ts +111 -0
  12. package/src/components/agent/scrape.ts +47 -0
  13. package/src/components/agent/tsconfig.ts +150 -0
  14. package/src/components/agent/typescript.ts +177 -0
  15. package/src/components/aws/bucket.ts +168 -0
  16. package/src/components/aws/function.ts +302 -0
  17. package/src/components/aws/index.ts +6 -0
  18. package/src/components/aws/policy.ts +245 -0
  19. package/src/components/aws/queue.ts +222 -0
  20. package/src/components/aws/role.ts +245 -0
  21. package/src/components/aws/table.ts +172 -0
  22. package/src/components/esbuild.ts +128 -0
  23. package/src/components/fs.ts +45 -0
  24. package/src/destroy.ts +63 -0
  25. package/src/error.ts +16 -0
  26. package/src/global.ts +72 -0
  27. package/src/index.ts +6 -0
  28. package/src/input.ts +16 -0
  29. package/src/main.ts +34 -0
  30. package/src/output.ts +49 -0
  31. package/src/resource.ts +313 -0
  32. package/src/state.ts +100 -0
  33. package/test/aws/function.test.ts +144 -0
  34. package/test/aws/queue.test.ts +85 -0
  35. package/test/aws/role.test.ts +153 -0
  36. package/test/aws/table.test.ts +73 -0
  37. package/test/esbuild.test.ts +41 -0
  38. package/test/handler.ts +9 -0
  39. package/tsconfig.json +7 -0
  40. package/Column.js +0 -1
  41. package/Create.js +0 -1
  42. package/Delete.js +0 -1
  43. package/Drop.js +0 -1
  44. package/Insert.js +0 -1
  45. package/Makefile +0 -8
  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,177 @@
1
+ import type { CoreMessage } from "ai";
2
+ import { generateText } from "ai";
3
+ import { mkdir, unlink, writeFile } from "fs/promises";
4
+ import { dirname } from "path";
5
+ import { z } from "zod";
6
+ import { Agent } from "./agent";
7
+ import { resolveModel } from "./model";
8
+
9
+ export type CodeProps = z.infer<typeof CodeProps>;
10
+
11
+ export type File = z.infer<typeof File>;
12
+ export const File = z.object({
13
+ path: z.string(),
14
+ content: z.string(),
15
+ });
16
+
17
+ const CodeProps = z.object({
18
+ /**
19
+ * The ID of the model to use for generating TypeScript code
20
+ * @default "gpt-4o"
21
+ */
22
+ modelId: z.string().optional(),
23
+
24
+ /**
25
+ * The name of the file to write the code to
26
+ */
27
+ path: z.string(),
28
+
29
+ /**
30
+ * The requirements for the TypeScript file
31
+ */
32
+ requirements: z.string(),
33
+
34
+ /**
35
+ * List of other code files that it depends on
36
+ */
37
+ dependencies: z.array(File).optional(),
38
+
39
+ /**
40
+ * Temperature setting for model generation (higher = more creative, lower = more focused)
41
+ * @default 0.7
42
+ */
43
+ temperature: z.number().optional(),
44
+ });
45
+
46
+ export type CodeOutput = z.infer<typeof CodeOutput>;
47
+ export const CodeOutput = File;
48
+
49
+ export class TypeScriptFile extends Agent(
50
+ "code::TypeScriptFile",
51
+ {
52
+ description:
53
+ "This Agent is responsible for generating TypeScript code based on requirements and context from other code files.",
54
+ input: CodeProps,
55
+ output: CodeOutput,
56
+ },
57
+ async (ctx, props) => {
58
+ if (ctx.event === "delete") {
59
+ await unlink(props.path);
60
+ return;
61
+ }
62
+
63
+ // Get the appropriate model based on the ID
64
+ const { model } = await resolveModel(props.modelId ?? "gpt-4o");
65
+
66
+ // Generate the TypeScript code with retries
67
+ const content = await generateCodeWithRetries(model, props);
68
+
69
+ // Ensure the directory exists
70
+ await mkdir(dirname(props.path), { recursive: true });
71
+
72
+ // Write the TypeScript file
73
+ await writeFile(props.path, content);
74
+
75
+ return {
76
+ path: props.path,
77
+ content: content,
78
+ };
79
+ },
80
+ ) {}
81
+
82
+ /**
83
+ * Attempts to generate valid TypeScript code with retries
84
+ */
85
+ async function generateCodeWithRetries(
86
+ model: any,
87
+ props: CodeProps,
88
+ maxAttempts = 3,
89
+ ): Promise<string> {
90
+ const baseMessages: CoreMessage[] = [
91
+ {
92
+ role: "system",
93
+ content:
94
+ "You are an expert TypeScript developer that generates clean, well-documented code following best practices. " +
95
+ "Your response must follow this exact format:\n" +
96
+ "1. A single sentence describing what the code will do\n" +
97
+ "2. A single TypeScript code block surrounded by ```ts and ``` tags\n" +
98
+ "Do not include any other explanations or multiple code blocks.",
99
+ },
100
+ {
101
+ role: "user",
102
+ content: `Please generate TypeScript code based on the following specifications:
103
+
104
+ Requirements:
105
+ ${props.requirements}
106
+
107
+ ${
108
+ props.dependencies?.length
109
+ ? `Context (other code files):
110
+ ${props.dependencies.map((dep) => dep.content).join("\n\n")}`
111
+ : ""
112
+ }
113
+
114
+ The code should:
115
+ 1. Be well-documented with JSDoc comments
116
+ 2. Follow TypeScript best practices
117
+ 3. Include proper type definitions
118
+ 4. Be ready to use with the provided context files
119
+
120
+ Your response MUST follow this format:
121
+ - One sentence summary of what the code will do
122
+ - Single TypeScript code block surrounded by \`\`\`ts and \`\`\` tags
123
+ - No other explanations or multiple code blocks`,
124
+ },
125
+ ];
126
+
127
+ const messages = [...baseMessages];
128
+
129
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
130
+ const { text } = await generateText({
131
+ model,
132
+ temperature: Math.max(
133
+ 0.1,
134
+ (props.temperature ?? 0.7) * (1 - attempt * 0.2),
135
+ ), // Reduce temperature with each attempt
136
+ messages,
137
+ });
138
+
139
+ const code = extractTypeScriptCode(text);
140
+ if (code) {
141
+ return code;
142
+ }
143
+
144
+ if (attempt === maxAttempts) {
145
+ throw new Error(
146
+ "Failed to generate valid TypeScript code after maximum attempts",
147
+ );
148
+ }
149
+
150
+ // Add the failed attempt and correction request to the message chain
151
+ messages.push(
152
+ {
153
+ role: "assistant",
154
+ content: text,
155
+ },
156
+ {
157
+ role: "user",
158
+ content:
159
+ "Your response did not match the required format. Please provide your response with exactly one sentence summary" +
160
+ " followed by a single TypeScript code block surrounded by ```ts and ``` tags. No other text or explanations.",
161
+ },
162
+ );
163
+ }
164
+
165
+ // This should never be reached due to the throw above
166
+ throw new Error("Unexpected code generation failure");
167
+ }
168
+
169
+ /**
170
+ * Extracts TypeScript code from a markdown code block
171
+ * @param text Response text that may contain markdown
172
+ * @returns The TypeScript code if found, undefined otherwise
173
+ */
174
+ function extractTypeScriptCode(text: string): string | undefined {
175
+ const matches = text.match(/```(?:ts|typescript)\n([\s\S]*?)```/);
176
+ return matches?.[1]?.trim();
177
+ }
@@ -0,0 +1,168 @@
1
+ import {
2
+ CreateBucketCommand,
3
+ DeleteBucketCommand,
4
+ GetBucketAclCommand,
5
+ GetBucketLocationCommand,
6
+ GetBucketTaggingCommand,
7
+ GetBucketVersioningCommand,
8
+ HeadBucketCommand,
9
+ NoSuchBucket,
10
+ PutBucketTaggingCommand,
11
+ S3Client,
12
+ } from "@aws-sdk/client-s3";
13
+ import { ignore } from "../../error";
14
+ import { Resource } from "../../resource";
15
+
16
+ export interface BucketProps {
17
+ bucketName: string;
18
+ tags?: Record<string, string>;
19
+ }
20
+
21
+ export interface BucketOutput extends BucketProps {
22
+ id: string; // Same as bucketName for AWS
23
+ arn: string;
24
+ bucketDomainName: string; // Format: bucket-name.s3.amazonaws.com
25
+ bucketRegionalDomainName?: string; // Format: bucket-name.s3.region.amazonaws.com
26
+ hostedZoneId?: string; // S3 hosted zone ID for the region
27
+ region?: string; // AWS Region where bucket resides
28
+ websiteEndpoint?: string; // Only if website hosting is enabled
29
+ websiteDomain?: string; // Only if website hosting is enabled
30
+ versioningEnabled?: boolean;
31
+ acl?: string;
32
+ }
33
+
34
+ export class Bucket extends Resource(
35
+ "s3::Bucket",
36
+ async (ctx, props: BucketProps) => {
37
+ const client = new S3Client({});
38
+
39
+ if (ctx.event === "delete") {
40
+ await ignore(NoSuchBucket.name, () =>
41
+ client.send(
42
+ new DeleteBucketCommand({
43
+ Bucket: props.bucketName,
44
+ }),
45
+ ),
46
+ );
47
+ return props;
48
+ } else {
49
+ try {
50
+ // Check if bucket exists
51
+ await client.send(
52
+ new HeadBucketCommand({
53
+ Bucket: props.bucketName,
54
+ }),
55
+ );
56
+
57
+ // Update tags if they changed and bucket exists
58
+ if (ctx.event === "update" && props.tags) {
59
+ await client.send(
60
+ new PutBucketTaggingCommand({
61
+ Bucket: props.bucketName,
62
+ Tagging: {
63
+ TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
64
+ Key,
65
+ Value,
66
+ })),
67
+ },
68
+ }),
69
+ );
70
+ }
71
+ } catch (error: any) {
72
+ if (error.name === "NotFound") {
73
+ // Create bucket if it doesn't exist
74
+ await client.send(
75
+ new CreateBucketCommand({
76
+ Bucket: props.bucketName,
77
+ // Add tags during creation if specified
78
+ ...(props.tags && {
79
+ Tagging: {
80
+ TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
81
+ Key,
82
+ Value,
83
+ })),
84
+ },
85
+ }),
86
+ }),
87
+ );
88
+ } else {
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ // Get bucket details
94
+ const [locationResponse, versioningResponse, aclResponse] =
95
+ await Promise.all([
96
+ client.send(
97
+ new GetBucketLocationCommand({ Bucket: props.bucketName }),
98
+ ),
99
+ client.send(
100
+ new GetBucketVersioningCommand({ Bucket: props.bucketName }),
101
+ ),
102
+ client.send(new GetBucketAclCommand({ Bucket: props.bucketName })),
103
+ ]);
104
+
105
+ const region = locationResponse.LocationConstraint || "us-east-1";
106
+
107
+ // Get tags if they exist
108
+ let tags = props.tags;
109
+ if (!tags) {
110
+ try {
111
+ const taggingResponse = await client.send(
112
+ new GetBucketTaggingCommand({ Bucket: props.bucketName }),
113
+ );
114
+ tags = Object.fromEntries(
115
+ taggingResponse.TagSet?.map(({ Key, Value }) => [Key, Value]) || [],
116
+ );
117
+ } catch (error: any) {
118
+ if (error.name !== "NoSuchTagSet") {
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ const output: BucketOutput = {
125
+ bucketName: props.bucketName,
126
+ id: props.bucketName,
127
+ arn: `arn:aws:s3:::${props.bucketName}`,
128
+ bucketDomainName: `${props.bucketName}.s3.amazonaws.com`,
129
+ bucketRegionalDomainName: `${props.bucketName}.s3.${region}.amazonaws.com`,
130
+ region,
131
+ hostedZoneId: getHostedZoneId(region),
132
+ versioningEnabled: versioningResponse.Status === "Enabled",
133
+ acl: aclResponse.Grants?.[0]?.Permission?.toLowerCase(),
134
+ ...(tags && { tags }),
135
+ };
136
+
137
+ return output;
138
+ }
139
+ },
140
+ ) {}
141
+
142
+ // Helper function to get S3 hosted zone IDs by region
143
+ function getHostedZoneId(region: string): string {
144
+ const hostedZoneIds: Record<string, string> = {
145
+ "us-east-1": "Z3AQBSTGFYJSTF",
146
+ "us-east-2": "Z2O1EMRO9K5GLX",
147
+ "us-west-1": "Z2F56UZL2M1ACD",
148
+ "us-west-2": "Z3BJ6K6RIION7M",
149
+ "af-south-1": "Z11KHD8FBVPUYU",
150
+ "ap-east-1": "ZNB98KWMFR0R6",
151
+ "ap-south-1": "Z11RGJOFQNVJUP",
152
+ "ap-northeast-1": "Z2M4EHUR26P7ZW",
153
+ "ap-northeast-2": "Z3W03O7B5YMIYP",
154
+ "ap-northeast-3": "Z2YQB5RD63NC85",
155
+ "ap-southeast-1": "Z3O0J2DXBE1FTB",
156
+ "ap-southeast-2": "Z1WCIGYICN2BYD",
157
+ "ca-central-1": "Z1QDHH18159H29",
158
+ "eu-central-1": "Z21DNDUVLTQW6Q",
159
+ "eu-west-1": "Z1BKCTXD74EZPE",
160
+ "eu-west-2": "Z3GKZC51ZF0DB4",
161
+ "eu-west-3": "Z3R1K369G5AVDG",
162
+ "eu-north-1": "Z3BAZG2TWCNX0D",
163
+ "eu-south-1": "Z30OZKI7KPW7MI",
164
+ "me-south-1": "Z1MPMWCPA7YB62",
165
+ "sa-east-1": "Z7KQH4QJS55SO",
166
+ };
167
+ return hostedZoneIds[region] || "Z3AQBSTGFYJSTF"; // Default to us-east-1 if region not found
168
+ }
@@ -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";