omgkit 2.2.0 → 2.3.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.
- package/package.json +1 -1
- package/plugin/skills/databases/mongodb/SKILL.md +60 -776
- package/plugin/skills/databases/prisma/SKILL.md +53 -744
- package/plugin/skills/databases/redis/SKILL.md +53 -860
- package/plugin/skills/devops/aws/SKILL.md +68 -672
- package/plugin/skills/devops/github-actions/SKILL.md +54 -657
- package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +87 -853
- package/plugin/skills/frameworks/express/SKILL.md +95 -1301
- package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
- package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
- package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
- package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
- package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
- package/plugin/skills/frontend/responsive/SKILL.md +76 -799
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
- package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
- package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
- package/plugin/skills/languages/javascript/SKILL.md +106 -849
- package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
- package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
- package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
- package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
- package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
- package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
- package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
- package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
- package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
- package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
- package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
- package/plugin/skills/security/better-auth/SKILL.md +46 -1034
- package/plugin/skills/security/oauth/SKILL.md +80 -934
- package/plugin/skills/security/owasp/SKILL.md +78 -862
- package/plugin/skills/testing/playwright/SKILL.md +77 -700
- package/plugin/skills/testing/pytest/SKILL.md +73 -811
- package/plugin/skills/testing/vitest/SKILL.md +60 -920
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
- package/plugin/skills/SKILL_STANDARDS.md +0 -743
|
@@ -1,709 +1,105 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: AWS cloud
|
|
4
|
-
category: devops
|
|
5
|
-
triggers:
|
|
6
|
-
- aws
|
|
7
|
-
- amazon web services
|
|
8
|
-
- lambda
|
|
9
|
-
- s3
|
|
10
|
-
- dynamodb
|
|
11
|
-
- ecs
|
|
12
|
-
- cloudformation
|
|
13
|
-
- cdk
|
|
2
|
+
name: Deploying to AWS
|
|
3
|
+
description: The agent implements AWS cloud solutions with Lambda, S3, DynamoDB, ECS, and CDK infrastructure as code. Use when building serverless functions, deploying containers, managing cloud storage, or defining infrastructure as code.
|
|
14
4
|
---
|
|
15
5
|
|
|
16
|
-
# AWS
|
|
6
|
+
# Deploying to AWS
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## Purpose
|
|
21
|
-
|
|
22
|
-
Build scalable cloud applications on AWS:
|
|
23
|
-
|
|
24
|
-
- Deploy serverless functions with Lambda
|
|
25
|
-
- Store and retrieve data with S3 and DynamoDB
|
|
26
|
-
- Run containerized applications with ECS
|
|
27
|
-
- Build APIs with API Gateway
|
|
28
|
-
- Implement infrastructure as code with CDK
|
|
29
|
-
- Monitor applications with CloudWatch
|
|
30
|
-
- Secure resources with IAM
|
|
31
|
-
|
|
32
|
-
## Features
|
|
33
|
-
|
|
34
|
-
### 1. Lambda Functions
|
|
8
|
+
## Quick Start
|
|
35
9
|
|
|
36
10
|
```typescript
|
|
37
|
-
//
|
|
38
|
-
import { APIGatewayProxyEvent, APIGatewayProxyResult
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
interface User {
|
|
48
|
-
id: string;
|
|
49
|
-
email: string;
|
|
50
|
-
name: string;
|
|
51
|
-
createdAt: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Response helpers
|
|
55
|
-
const response = (statusCode: number, body: unknown): APIGatewayProxyResult => ({
|
|
56
|
-
statusCode,
|
|
57
|
-
headers: {
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
'Access-Control-Allow-Origin': '*',
|
|
60
|
-
'Access-Control-Allow-Credentials': true,
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify(body),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const success = (data: unknown) => response(200, { data });
|
|
66
|
-
const created = (data: unknown) => response(201, { data });
|
|
67
|
-
const notFound = (message: string) => response(404, { error: { code: 'NOT_FOUND', message } });
|
|
68
|
-
const badRequest = (message: string) => response(400, { error: { code: 'BAD_REQUEST', message } });
|
|
69
|
-
const serverError = (error: Error) => response(500, { error: { code: 'SERVER_ERROR', message: error.message } });
|
|
70
|
-
|
|
71
|
-
// GET /users/{id}
|
|
72
|
-
export const getUser = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
|
73
|
-
try {
|
|
74
|
-
const userId = event.pathParameters?.id;
|
|
75
|
-
if (!userId) return badRequest('User ID is required');
|
|
76
|
-
|
|
77
|
-
const result = await docClient.send(new GetCommand({
|
|
78
|
-
TableName: TABLE_NAME,
|
|
79
|
-
Key: { id: userId },
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
if (!result.Item) {
|
|
83
|
-
return notFound(`User ${userId} not found`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return success(result.Item);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error('Error getting user:', error);
|
|
89
|
-
return serverError(error as Error);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// POST /users
|
|
94
|
-
export const createUser = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
|
95
|
-
try {
|
|
96
|
-
if (!event.body) return badRequest('Request body is required');
|
|
97
|
-
|
|
98
|
-
const body = JSON.parse(event.body);
|
|
99
|
-
const { email, name } = body;
|
|
100
|
-
|
|
101
|
-
if (!email || !name) {
|
|
102
|
-
return badRequest('Email and name are required');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const user: User = {
|
|
106
|
-
id: crypto.randomUUID(),
|
|
107
|
-
email: email.toLowerCase(),
|
|
108
|
-
name,
|
|
109
|
-
createdAt: new Date().toISOString(),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
await docClient.send(new PutCommand({
|
|
113
|
-
TableName: TABLE_NAME,
|
|
114
|
-
Item: user,
|
|
115
|
-
ConditionExpression: 'attribute_not_exists(id)',
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
return created(user);
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error('Error creating user:', error);
|
|
121
|
-
return serverError(error as Error);
|
|
122
|
-
}
|
|
11
|
+
// Lambda handler
|
|
12
|
+
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
|
13
|
+
|
|
14
|
+
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
|
15
|
+
const { id } = event.pathParameters || {};
|
|
16
|
+
return {
|
|
17
|
+
statusCode: 200,
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({ id, message: 'Success' }),
|
|
20
|
+
};
|
|
123
21
|
};
|
|
22
|
+
```
|
|
124
23
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const lastKey = event.queryStringParameters?.cursor;
|
|
130
|
-
|
|
131
|
-
const params: any = {
|
|
132
|
-
TableName: TABLE_NAME,
|
|
133
|
-
Limit: limit,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
if (lastKey) {
|
|
137
|
-
params.ExclusiveStartKey = JSON.parse(Buffer.from(lastKey, 'base64').toString());
|
|
138
|
-
}
|
|
24
|
+
```bash
|
|
25
|
+
# Deploy with CDK
|
|
26
|
+
npx cdk deploy --all
|
|
27
|
+
```
|
|
139
28
|
|
|
140
|
-
|
|
29
|
+
## Features
|
|
141
30
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
31
|
+
| Feature | Description | Guide |
|
|
32
|
+
|---------|-------------|-------|
|
|
33
|
+
| Lambda | Serverless function execution | Use for APIs, event processing, scheduled tasks |
|
|
34
|
+
| S3 | Object storage with presigned URLs | Store files, serve static assets, data lakes |
|
|
35
|
+
| DynamoDB | NoSQL database with single-digit ms latency | Design single-table schemas with GSIs |
|
|
36
|
+
| ECS/Fargate | Container orchestration | Run Docker containers without managing servers |
|
|
37
|
+
| API Gateway | REST and WebSocket API management | Throttling, auth, request validation |
|
|
38
|
+
| CDK | Infrastructure as TypeScript code | Define stacks, manage deployments programmatically |
|
|
145
39
|
|
|
146
|
-
|
|
147
|
-
items: result.Items,
|
|
148
|
-
cursor,
|
|
149
|
-
hasMore: !!result.LastEvaluatedKey,
|
|
150
|
-
});
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error('Error listing users:', error);
|
|
153
|
-
return serverError(error as Error);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
```
|
|
40
|
+
## Common Patterns
|
|
157
41
|
|
|
158
|
-
###
|
|
42
|
+
### S3 Presigned Upload URL
|
|
159
43
|
|
|
160
44
|
```typescript
|
|
161
|
-
|
|
162
|
-
import {
|
|
163
|
-
S3Client,
|
|
164
|
-
PutObjectCommand,
|
|
165
|
-
GetObjectCommand,
|
|
166
|
-
DeleteObjectCommand,
|
|
167
|
-
ListObjectsV2Command,
|
|
168
|
-
CopyObjectCommand,
|
|
169
|
-
} from '@aws-sdk/client-s3';
|
|
45
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
170
46
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
171
|
-
import { Readable } from 'stream';
|
|
172
|
-
|
|
173
|
-
const s3Client = new S3Client({ region: process.env.AWS_REGION });
|
|
174
|
-
const BUCKET = process.env.S3_BUCKET!;
|
|
175
|
-
|
|
176
|
-
export class S3Service {
|
|
177
|
-
// Upload file
|
|
178
|
-
async upload(key: string, body: Buffer | Readable, contentType: string): Promise<string> {
|
|
179
|
-
await s3Client.send(new PutObjectCommand({
|
|
180
|
-
Bucket: BUCKET,
|
|
181
|
-
Key: key,
|
|
182
|
-
Body: body,
|
|
183
|
-
ContentType: contentType,
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
return `s3://${BUCKET}/${key}`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Upload with metadata
|
|
190
|
-
async uploadWithMetadata(
|
|
191
|
-
key: string,
|
|
192
|
-
body: Buffer,
|
|
193
|
-
options: {
|
|
194
|
-
contentType: string;
|
|
195
|
-
metadata?: Record<string, string>;
|
|
196
|
-
tags?: Record<string, string>;
|
|
197
|
-
}
|
|
198
|
-
): Promise<string> {
|
|
199
|
-
await s3Client.send(new PutObjectCommand({
|
|
200
|
-
Bucket: BUCKET,
|
|
201
|
-
Key: key,
|
|
202
|
-
Body: body,
|
|
203
|
-
ContentType: options.contentType,
|
|
204
|
-
Metadata: options.metadata,
|
|
205
|
-
Tagging: options.tags
|
|
206
|
-
? Object.entries(options.tags).map(([k, v]) => `${k}=${v}`).join('&')
|
|
207
|
-
: undefined,
|
|
208
|
-
}));
|
|
209
|
-
|
|
210
|
-
return `https://${BUCKET}.s3.amazonaws.com/${key}`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Get file
|
|
214
|
-
async get(key: string): Promise<Buffer> {
|
|
215
|
-
const response = await s3Client.send(new GetObjectCommand({
|
|
216
|
-
Bucket: BUCKET,
|
|
217
|
-
Key: key,
|
|
218
|
-
}));
|
|
219
|
-
|
|
220
|
-
const chunks: Buffer[] = [];
|
|
221
|
-
for await (const chunk of response.Body as Readable) {
|
|
222
|
-
chunks.push(chunk);
|
|
223
|
-
}
|
|
224
|
-
return Buffer.concat(chunks);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Generate presigned upload URL
|
|
228
|
-
async getUploadUrl(key: string, contentType: string, expiresIn = 3600): Promise<string> {
|
|
229
|
-
const command = new PutObjectCommand({
|
|
230
|
-
Bucket: BUCKET,
|
|
231
|
-
Key: key,
|
|
232
|
-
ContentType: contentType,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
return getSignedUrl(s3Client, command, { expiresIn });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Generate presigned download URL
|
|
239
|
-
async getDownloadUrl(key: string, expiresIn = 3600): Promise<string> {
|
|
240
|
-
const command = new GetObjectCommand({
|
|
241
|
-
Bucket: BUCKET,
|
|
242
|
-
Key: key,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
return getSignedUrl(s3Client, command, { expiresIn });
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// List objects with prefix
|
|
249
|
-
async list(prefix: string, maxKeys = 1000): Promise<string[]> {
|
|
250
|
-
const response = await s3Client.send(new ListObjectsV2Command({
|
|
251
|
-
Bucket: BUCKET,
|
|
252
|
-
Prefix: prefix,
|
|
253
|
-
MaxKeys: maxKeys,
|
|
254
|
-
}));
|
|
255
|
-
|
|
256
|
-
return response.Contents?.map(obj => obj.Key!) || [];
|
|
257
|
-
}
|
|
258
47
|
|
|
259
|
-
|
|
260
|
-
async delete(key: string): Promise<void> {
|
|
261
|
-
await s3Client.send(new DeleteObjectCommand({
|
|
262
|
-
Bucket: BUCKET,
|
|
263
|
-
Key: key,
|
|
264
|
-
}));
|
|
265
|
-
}
|
|
48
|
+
const s3 = new S3Client({});
|
|
266
49
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Bucket: BUCKET,
|
|
271
|
-
CopySource: `${BUCKET}/${sourceKey}`,
|
|
272
|
-
Key: destKey,
|
|
273
|
-
}));
|
|
274
|
-
}
|
|
50
|
+
async function getUploadUrl(key: string, contentType: string): Promise<string> {
|
|
51
|
+
const command = new PutObjectCommand({ Bucket: process.env.BUCKET!, Key: key, ContentType: contentType });
|
|
52
|
+
return getSignedUrl(s3, command, { expiresIn: 3600 });
|
|
275
53
|
}
|
|
276
54
|
```
|
|
277
55
|
|
|
278
|
-
###
|
|
56
|
+
### DynamoDB Single-Table Pattern
|
|
279
57
|
|
|
280
58
|
```typescript
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
TransactWriteCommand,
|
|
292
|
-
} from '@aws-sdk/lib-dynamodb';
|
|
293
|
-
|
|
294
|
-
const client = new DynamoDBClient({});
|
|
295
|
-
const docClient = DynamoDBDocumentClient.from(client, {
|
|
296
|
-
marshallOptions: { removeUndefinedValues: true },
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
export class DynamoDBService<T extends { pk: string; sk: string }> {
|
|
300
|
-
constructor(private tableName: string) {}
|
|
301
|
-
|
|
302
|
-
async get(pk: string, sk: string): Promise<T | null> {
|
|
303
|
-
const result = await docClient.send(new GetCommand({
|
|
304
|
-
TableName: this.tableName,
|
|
305
|
-
Key: { pk, sk },
|
|
306
|
-
}));
|
|
307
|
-
return (result.Item as T) || null;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async put(item: T): Promise<T> {
|
|
311
|
-
await docClient.send(new PutCommand({
|
|
312
|
-
TableName: this.tableName,
|
|
313
|
-
Item: item,
|
|
314
|
-
}));
|
|
315
|
-
return item;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async update(pk: string, sk: string, updates: Partial<T>): Promise<T> {
|
|
319
|
-
const updateExpressions: string[] = [];
|
|
320
|
-
const expressionNames: Record<string, string> = {};
|
|
321
|
-
const expressionValues: Record<string, unknown> = {};
|
|
322
|
-
|
|
323
|
-
Object.entries(updates).forEach(([key, value], index) => {
|
|
324
|
-
if (key !== 'pk' && key !== 'sk') {
|
|
325
|
-
updateExpressions.push(`#attr${index} = :val${index}`);
|
|
326
|
-
expressionNames[`#attr${index}`] = key;
|
|
327
|
-
expressionValues[`:val${index}`] = value;
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
const result = await docClient.send(new UpdateCommand({
|
|
332
|
-
TableName: this.tableName,
|
|
333
|
-
Key: { pk, sk },
|
|
334
|
-
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
|
|
335
|
-
ExpressionAttributeNames: expressionNames,
|
|
336
|
-
ExpressionAttributeValues: expressionValues,
|
|
337
|
-
ReturnValues: 'ALL_NEW',
|
|
338
|
-
}));
|
|
339
|
-
|
|
340
|
-
return result.Attributes as T;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async delete(pk: string, sk: string): Promise<void> {
|
|
344
|
-
await docClient.send(new DeleteCommand({
|
|
345
|
-
TableName: this.tableName,
|
|
346
|
-
Key: { pk, sk },
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async query(
|
|
351
|
-
pk: string,
|
|
352
|
-
options?: {
|
|
353
|
-
skPrefix?: string;
|
|
354
|
-
limit?: number;
|
|
355
|
-
scanForward?: boolean;
|
|
356
|
-
}
|
|
357
|
-
): Promise<T[]> {
|
|
358
|
-
const params: any = {
|
|
359
|
-
TableName: this.tableName,
|
|
360
|
-
KeyConditionExpression: 'pk = :pk',
|
|
361
|
-
ExpressionAttributeValues: { ':pk': pk },
|
|
362
|
-
Limit: options?.limit,
|
|
363
|
-
ScanIndexForward: options?.scanForward ?? true,
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
if (options?.skPrefix) {
|
|
367
|
-
params.KeyConditionExpression += ' AND begins_with(sk, :skPrefix)';
|
|
368
|
-
params.ExpressionAttributeValues[':skPrefix'] = options.skPrefix;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const result = await docClient.send(new QueryCommand(params));
|
|
372
|
-
return (result.Items as T[]) || [];
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
async batchWrite(items: T[]): Promise<void> {
|
|
376
|
-
const chunks = this.chunkArray(items, 25);
|
|
377
|
-
|
|
378
|
-
for (const chunk of chunks) {
|
|
379
|
-
await docClient.send(new BatchWriteCommand({
|
|
380
|
-
RequestItems: {
|
|
381
|
-
[this.tableName]: chunk.map(item => ({
|
|
382
|
-
PutRequest: { Item: item },
|
|
383
|
-
})),
|
|
384
|
-
},
|
|
385
|
-
}));
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async transactWrite(operations: Array<{ type: 'put' | 'delete'; item: T }>): Promise<void> {
|
|
390
|
-
await docClient.send(new TransactWriteCommand({
|
|
391
|
-
TransactItems: operations.map(op => {
|
|
392
|
-
if (op.type === 'put') {
|
|
393
|
-
return { Put: { TableName: this.tableName, Item: op.item } };
|
|
394
|
-
}
|
|
395
|
-
return { Delete: { TableName: this.tableName, Key: { pk: op.item.pk, sk: op.item.sk } } };
|
|
396
|
-
}),
|
|
397
|
-
}));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
private chunkArray<U>(array: U[], size: number): U[][] {
|
|
401
|
-
const chunks: U[][] = [];
|
|
402
|
-
for (let i = 0; i < array.length; i += size) {
|
|
403
|
-
chunks.push(array.slice(i, i + size));
|
|
404
|
-
}
|
|
405
|
-
return chunks;
|
|
406
|
-
}
|
|
59
|
+
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';
|
|
60
|
+
|
|
61
|
+
// pk: USER#123, sk: PROFILE | ORDER#timestamp
|
|
62
|
+
async function getUserWithOrders(userId: string) {
|
|
63
|
+
const result = await docClient.send(new QueryCommand({
|
|
64
|
+
TableName: process.env.TABLE!,
|
|
65
|
+
KeyConditionExpression: 'pk = :pk',
|
|
66
|
+
ExpressionAttributeValues: { ':pk': `USER#${userId}` },
|
|
67
|
+
}));
|
|
68
|
+
return result.Items;
|
|
407
69
|
}
|
|
408
70
|
```
|
|
409
71
|
|
|
410
|
-
###
|
|
72
|
+
### CDK Stack Definition
|
|
411
73
|
|
|
412
74
|
```typescript
|
|
413
|
-
// lib/app-stack.ts
|
|
414
75
|
import * as cdk from 'aws-cdk-lib';
|
|
415
76
|
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
416
|
-
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
417
77
|
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
418
|
-
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
419
|
-
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
420
|
-
import { Construct } from 'constructs';
|
|
421
|
-
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
422
|
-
|
|
423
|
-
export class AppStack extends cdk.Stack {
|
|
424
|
-
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
425
|
-
super(scope, id, props);
|
|
426
|
-
|
|
427
|
-
// DynamoDB Table
|
|
428
|
-
const usersTable = new dynamodb.Table(this, 'UsersTable', {
|
|
429
|
-
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
430
|
-
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
|
431
|
-
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
432
|
-
pointInTimeRecovery: true,
|
|
433
|
-
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
usersTable.addGlobalSecondaryIndex({
|
|
437
|
-
indexName: 'GSI1',
|
|
438
|
-
partitionKey: { name: 'gsi1pk', type: dynamodb.AttributeType.STRING },
|
|
439
|
-
sortKey: { name: 'gsi1sk', type: dynamodb.AttributeType.STRING },
|
|
440
|
-
projectionType: dynamodb.ProjectionType.ALL,
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// S3 Bucket
|
|
444
|
-
const bucket = new s3.Bucket(this, 'AssetsBucket', {
|
|
445
|
-
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
446
|
-
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
447
|
-
versioned: true,
|
|
448
|
-
lifecycleRules: [
|
|
449
|
-
{
|
|
450
|
-
expiration: cdk.Duration.days(365),
|
|
451
|
-
noncurrentVersionExpiration: cdk.Duration.days(30),
|
|
452
|
-
},
|
|
453
|
-
],
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// Lambda Functions
|
|
457
|
-
const getUserFn = new NodejsFunction(this, 'GetUserFunction', {
|
|
458
|
-
entry: 'src/handlers/user.handler.ts',
|
|
459
|
-
handler: 'getUser',
|
|
460
|
-
runtime: lambda.Runtime.NODEJS_20_X,
|
|
461
|
-
timeout: cdk.Duration.seconds(30),
|
|
462
|
-
memorySize: 256,
|
|
463
|
-
environment: {
|
|
464
|
-
USERS_TABLE: usersTable.tableName,
|
|
465
|
-
S3_BUCKET: bucket.bucketName,
|
|
466
|
-
},
|
|
467
|
-
tracing: lambda.Tracing.ACTIVE,
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
const createUserFn = new NodejsFunction(this, 'CreateUserFunction', {
|
|
471
|
-
entry: 'src/handlers/user.handler.ts',
|
|
472
|
-
handler: 'createUser',
|
|
473
|
-
runtime: lambda.Runtime.NODEJS_20_X,
|
|
474
|
-
timeout: cdk.Duration.seconds(30),
|
|
475
|
-
memorySize: 256,
|
|
476
|
-
environment: {
|
|
477
|
-
USERS_TABLE: usersTable.tableName,
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// Grant permissions
|
|
482
|
-
usersTable.grantReadData(getUserFn);
|
|
483
|
-
usersTable.grantReadWriteData(createUserFn);
|
|
484
|
-
bucket.grantRead(getUserFn);
|
|
485
|
-
|
|
486
|
-
// API Gateway
|
|
487
|
-
const api = new apigateway.RestApi(this, 'UsersApi', {
|
|
488
|
-
restApiName: 'Users Service',
|
|
489
|
-
deployOptions: {
|
|
490
|
-
stageName: 'prod',
|
|
491
|
-
throttlingRateLimit: 1000,
|
|
492
|
-
throttlingBurstLimit: 500,
|
|
493
|
-
},
|
|
494
|
-
defaultCorsPreflightOptions: {
|
|
495
|
-
allowOrigins: apigateway.Cors.ALL_ORIGINS,
|
|
496
|
-
allowMethods: apigateway.Cors.ALL_METHODS,
|
|
497
|
-
},
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
const users = api.root.addResource('users');
|
|
501
|
-
users.addMethod('GET', new apigateway.LambdaIntegration(getUserFn));
|
|
502
|
-
users.addMethod('POST', new apigateway.LambdaIntegration(createUserFn));
|
|
503
|
-
|
|
504
|
-
const user = users.addResource('{id}');
|
|
505
|
-
user.addMethod('GET', new apigateway.LambdaIntegration(getUserFn));
|
|
506
|
-
|
|
507
|
-
// Outputs
|
|
508
|
-
new cdk.CfnOutput(this, 'ApiUrl', { value: api.url });
|
|
509
|
-
new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName });
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### 5. ECS Container Deployment
|
|
515
|
-
|
|
516
|
-
```typescript
|
|
517
|
-
// lib/ecs-stack.ts
|
|
518
|
-
import * as cdk from 'aws-cdk-lib';
|
|
519
|
-
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
520
|
-
import * as ecs from 'aws-cdk-lib/aws-ecs';
|
|
521
|
-
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
|
|
522
|
-
import * as ecr from 'aws-cdk-lib/aws-ecr';
|
|
523
|
-
import { Construct } from 'constructs';
|
|
524
|
-
|
|
525
|
-
export class EcsStack extends cdk.Stack {
|
|
526
|
-
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
527
|
-
super(scope, id, props);
|
|
528
|
-
|
|
529
|
-
// VPC
|
|
530
|
-
const vpc = new ec2.Vpc(this, 'AppVpc', {
|
|
531
|
-
maxAzs: 2,
|
|
532
|
-
natGateways: 1,
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
// ECS Cluster
|
|
536
|
-
const cluster = new ecs.Cluster(this, 'AppCluster', {
|
|
537
|
-
vpc,
|
|
538
|
-
containerInsights: true,
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Fargate Service with ALB
|
|
542
|
-
const service = new ecsPatterns.ApplicationLoadBalancedFargateService(
|
|
543
|
-
this,
|
|
544
|
-
'AppService',
|
|
545
|
-
{
|
|
546
|
-
cluster,
|
|
547
|
-
taskImageOptions: {
|
|
548
|
-
image: ecs.ContainerImage.fromRegistry('nginx:latest'),
|
|
549
|
-
containerPort: 80,
|
|
550
|
-
environment: {
|
|
551
|
-
NODE_ENV: 'production',
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
desiredCount: 2,
|
|
555
|
-
cpu: 256,
|
|
556
|
-
memoryLimitMiB: 512,
|
|
557
|
-
publicLoadBalancer: true,
|
|
558
|
-
healthCheckGracePeriod: cdk.Duration.seconds(60),
|
|
559
|
-
}
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
// Auto Scaling
|
|
563
|
-
const scaling = service.service.autoScaleTaskCount({
|
|
564
|
-
minCapacity: 2,
|
|
565
|
-
maxCapacity: 10,
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
scaling.scaleOnCpuUtilization('CpuScaling', {
|
|
569
|
-
targetUtilizationPercent: 70,
|
|
570
|
-
scaleInCooldown: cdk.Duration.seconds(60),
|
|
571
|
-
scaleOutCooldown: cdk.Duration.seconds(60),
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
scaling.scaleOnMemoryUtilization('MemoryScaling', {
|
|
575
|
-
targetUtilizationPercent: 80,
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
```
|
|
580
78
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
|
|
587
|
-
|
|
588
|
-
const sqsClient = new SQSClient({});
|
|
589
|
-
const snsClient = new SNSClient({});
|
|
590
|
-
|
|
591
|
-
export class MessagingService {
|
|
592
|
-
// Send message to SQS
|
|
593
|
-
async sendToQueue(queueUrl: string, message: unknown, delaySeconds = 0): Promise<string> {
|
|
594
|
-
const result = await sqsClient.send(new SendMessageCommand({
|
|
595
|
-
QueueUrl: queueUrl,
|
|
596
|
-
MessageBody: JSON.stringify(message),
|
|
597
|
-
DelaySeconds: delaySeconds,
|
|
598
|
-
}));
|
|
599
|
-
return result.MessageId!;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Receive messages from SQS
|
|
603
|
-
async receiveFromQueue(queueUrl: string, maxMessages = 10): Promise<Array<{ id: string; body: unknown; receiptHandle: string }>> {
|
|
604
|
-
const result = await sqsClient.send(new ReceiveMessageCommand({
|
|
605
|
-
QueueUrl: queueUrl,
|
|
606
|
-
MaxNumberOfMessages: maxMessages,
|
|
607
|
-
WaitTimeSeconds: 20,
|
|
608
|
-
}));
|
|
609
|
-
|
|
610
|
-
return (result.Messages || []).map(msg => ({
|
|
611
|
-
id: msg.MessageId!,
|
|
612
|
-
body: JSON.parse(msg.Body!),
|
|
613
|
-
receiptHandle: msg.ReceiptHandle!,
|
|
614
|
-
}));
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Delete message from SQS
|
|
618
|
-
async deleteFromQueue(queueUrl: string, receiptHandle: string): Promise<void> {
|
|
619
|
-
await sqsClient.send(new DeleteMessageCommand({
|
|
620
|
-
QueueUrl: queueUrl,
|
|
621
|
-
ReceiptHandle: receiptHandle,
|
|
622
|
-
}));
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Publish to SNS
|
|
626
|
-
async publishToTopic(topicArn: string, message: unknown, subject?: string): Promise<string> {
|
|
627
|
-
const result = await snsClient.send(new PublishCommand({
|
|
628
|
-
TopicArn: topicArn,
|
|
629
|
-
Message: JSON.stringify(message),
|
|
630
|
-
Subject: subject,
|
|
631
|
-
}));
|
|
632
|
-
return result.MessageId!;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
## Use Cases
|
|
638
|
-
|
|
639
|
-
### File Upload with Presigned URL
|
|
640
|
-
|
|
641
|
-
```typescript
|
|
642
|
-
// Lambda handler for file upload
|
|
643
|
-
export const getUploadUrl = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
|
644
|
-
const { filename, contentType } = JSON.parse(event.body || '{}');
|
|
645
|
-
const userId = event.requestContext.authorizer?.claims?.sub;
|
|
646
|
-
|
|
647
|
-
const key = `uploads/${userId}/${Date.now()}-${filename}`;
|
|
648
|
-
const s3Service = new S3Service();
|
|
649
|
-
const uploadUrl = await s3Service.getUploadUrl(key, contentType);
|
|
650
|
-
|
|
651
|
-
return success({ uploadUrl, key });
|
|
652
|
-
};
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### Event-Driven Processing
|
|
656
|
-
|
|
657
|
-
```typescript
|
|
658
|
-
// SQS Event Handler
|
|
659
|
-
import { SQSHandler, SQSEvent } from 'aws-lambda';
|
|
79
|
+
const table = new dynamodb.Table(this, 'Table', {
|
|
80
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
81
|
+
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
|
82
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
83
|
+
});
|
|
660
84
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
85
|
+
const fn = new lambda.Function(this, 'Handler', {
|
|
86
|
+
runtime: lambda.Runtime.NODEJS_20_X,
|
|
87
|
+
handler: 'index.handler',
|
|
88
|
+
code: lambda.Code.fromAsset('dist'),
|
|
89
|
+
environment: { TABLE_NAME: table.tableName },
|
|
90
|
+
});
|
|
664
91
|
|
|
665
|
-
|
|
666
|
-
await processMessage(message);
|
|
667
|
-
} catch (error) {
|
|
668
|
-
console.error('Failed to process message:', error);
|
|
669
|
-
throw error; // Message returns to queue
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
};
|
|
92
|
+
table.grantReadWriteData(fn);
|
|
673
93
|
```
|
|
674
94
|
|
|
675
95
|
## Best Practices
|
|
676
96
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
-
|
|
684
|
-
-
|
|
685
|
-
|
|
686
|
-
- Implement least privilege access
|
|
687
|
-
- Use environment variables for configuration
|
|
688
|
-
- Enable X-Ray tracing for debugging
|
|
689
|
-
|
|
690
|
-
### Don'ts
|
|
691
|
-
|
|
692
|
-
- Don't hardcode credentials in code
|
|
693
|
-
- Don't use root account for deployments
|
|
694
|
-
- Don't expose S3 buckets publicly
|
|
695
|
-
- Don't skip encryption for sensitive data
|
|
696
|
-
- Don't ignore CloudWatch alarms
|
|
697
|
-
- Don't over-provision resources
|
|
698
|
-
- Don't skip VPC for production workloads
|
|
699
|
-
- Don't use synchronous invocations for long tasks
|
|
700
|
-
- Don't ignore cost optimization
|
|
701
|
-
- Don't skip backup and disaster recovery
|
|
702
|
-
|
|
703
|
-
## References
|
|
704
|
-
|
|
705
|
-
- [AWS Documentation](https://docs.aws.amazon.com/)
|
|
706
|
-
- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/)
|
|
707
|
-
- [AWS SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/)
|
|
708
|
-
- [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/)
|
|
709
|
-
- [AWS Best Practices](https://aws.amazon.com/architecture/best-practices/)
|
|
97
|
+
| Do | Avoid |
|
|
98
|
+
|----|-------|
|
|
99
|
+
| Use IAM roles, never access keys in code | Hardcoding credentials or secrets |
|
|
100
|
+
| Enable encryption at rest and in transit | Exposing S3 buckets publicly |
|
|
101
|
+
| Tag resources for cost tracking | Over-provisioning resources |
|
|
102
|
+
| Use environment variables for config | Skipping CloudWatch alarms |
|
|
103
|
+
| Implement least-privilege IAM policies | Using root account for deployments |
|
|
104
|
+
| Enable X-Ray tracing for debugging | Synchronous invocations for long tasks |
|
|
105
|
+
| Use VPC for sensitive workloads | Ignoring backup and disaster recovery |
|