stacktape 3.5.8 → 3.6.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/.tsconfig.bun-build.json +1 -0
- package/ai-docs/cli-ref/aws-profile-create.md +22 -0
- package/ai-docs/cli-ref/aws-profile-delete.md +22 -0
- package/ai-docs/cli-ref/aws-profile-list.md +20 -0
- package/ai-docs/cli-ref/aws-profile-update.md +22 -0
- package/ai-docs/cli-ref/bastion-session.md +29 -0
- package/ai-docs/cli-ref/bastion-tunnel.md +30 -0
- package/ai-docs/cli-ref/bucket-sync.md +30 -0
- package/ai-docs/cli-ref/cf-module-update.md +26 -0
- package/ai-docs/cli-ref/cf-rollback.md +28 -0
- package/ai-docs/cli-ref/codebuild-deploy.md +34 -0
- package/ai-docs/cli-ref/compile-template.md +25 -0
- package/ai-docs/cli-ref/container-session.md +30 -0
- package/ai-docs/cli-ref/debug-alarms.md +28 -0
- package/ai-docs/cli-ref/debug-aws-sdk.md +33 -0
- package/ai-docs/cli-ref/debug-container-exec.md +36 -0
- package/ai-docs/cli-ref/debug-dynamodb.md +35 -0
- package/ai-docs/cli-ref/debug-logs.md +34 -0
- package/ai-docs/cli-ref/debug-metrics.md +33 -0
- package/ai-docs/cli-ref/debug-opensearch.md +35 -0
- package/ai-docs/cli-ref/debug-redis.md +36 -0
- package/ai-docs/cli-ref/debug-sql.md +35 -0
- package/ai-docs/cli-ref/defaults-configure.md +29 -0
- package/ai-docs/cli-ref/defaults-list.md +20 -0
- package/ai-docs/cli-ref/delete.md +24 -0
- package/ai-docs/cli-ref/deploy.md +25 -0
- package/ai-docs/cli-ref/deployment-script-run.md +28 -0
- package/ai-docs/cli-ref/dev-stop.md +26 -0
- package/ai-docs/cli-ref/dev.md +45 -0
- package/ai-docs/cli-ref/domain-add.md +26 -0
- package/ai-docs/cli-ref/help.md +18 -0
- package/ai-docs/cli-ref/info-operations.md +22 -0
- package/ai-docs/cli-ref/info-stack.md +30 -0
- package/ai-docs/cli-ref/info-stacks.md +26 -0
- package/ai-docs/cli-ref/info-whoami.md +22 -0
- package/ai-docs/cli-ref/init.md +30 -0
- package/ai-docs/cli-ref/login.md +20 -0
- package/ai-docs/cli-ref/logout.md +18 -0
- package/ai-docs/cli-ref/mcp-add.md +22 -0
- package/ai-docs/cli-ref/mcp.md +20 -0
- package/ai-docs/cli-ref/org-create.md +24 -0
- package/ai-docs/cli-ref/org-delete.md +24 -0
- package/ai-docs/cli-ref/org-list.md +22 -0
- package/ai-docs/cli-ref/package-workloads.md +25 -0
- package/ai-docs/cli-ref/param-get.md +26 -0
- package/ai-docs/cli-ref/preview-changes.md +23 -0
- package/ai-docs/cli-ref/project-create.md +22 -0
- package/ai-docs/cli-ref/projects-list.md +22 -0
- package/ai-docs/cli-ref/rollback.md +28 -0
- package/ai-docs/cli-ref/script-run.md +29 -0
- package/ai-docs/cli-ref/secret-create.md +28 -0
- package/ai-docs/cli-ref/secret-delete.md +26 -0
- package/ai-docs/cli-ref/secret-get.md +26 -0
- package/ai-docs/cli-ref/upgrade.md +20 -0
- package/ai-docs/cli-ref/version.md +18 -0
- package/ai-docs/concept/connecting-resources.md +369 -0
- package/ai-docs/concept/directives.md +371 -0
- package/ai-docs/concept/extending-cloudformation.md +315 -0
- package/ai-docs/concept/overrides-and-transforms.md +352 -0
- package/ai-docs/concept/stages-and-environments.md +347 -0
- package/ai-docs/concept/typescript-config.md +447 -0
- package/ai-docs/concept/yaml-config.md +338 -0
- package/ai-docs/config-ref/_root.md +131 -0
- package/ai-docs/config-ref/application-load-balancer.md +1109 -0
- package/ai-docs/config-ref/astro-web.md +115 -0
- package/ai-docs/config-ref/aws-cdk-construct.md +68 -0
- package/ai-docs/config-ref/bastion.md +93 -0
- package/ai-docs/config-ref/batch-job.md +179 -0
- package/ai-docs/config-ref/bucket.md +348 -0
- package/ai-docs/config-ref/cdn.md +496 -0
- package/ai-docs/config-ref/custom-resource.md +80 -0
- package/ai-docs/config-ref/deployment-script.md +79 -0
- package/ai-docs/config-ref/dynamo-db-table.md +202 -0
- package/ai-docs/config-ref/edge-lambda-function.md +87 -0
- package/ai-docs/config-ref/efs-filesystem.md +72 -0
- package/ai-docs/config-ref/event-bus.md +63 -0
- package/ai-docs/config-ref/function.md +409 -0
- package/ai-docs/config-ref/hosting-bucket.md +171 -0
- package/ai-docs/config-ref/http-api-gateway.md +149 -0
- package/ai-docs/config-ref/http-endpoint.md +92 -0
- package/ai-docs/config-ref/kinesis-stream.md +97 -0
- package/ai-docs/config-ref/mongo-db-atlas-cluster.md +254 -0
- package/ai-docs/config-ref/multi-container-workload.md +399 -0
- package/ai-docs/config-ref/network-load-balancer.md +118 -0
- package/ai-docs/config-ref/nextjs-web.md +147 -0
- package/ai-docs/config-ref/nuxt-web.md +81 -0
- package/ai-docs/config-ref/open-search.md +206 -0
- package/ai-docs/config-ref/private-service.md +75 -0
- package/ai-docs/config-ref/redis-cluster.md +223 -0
- package/ai-docs/config-ref/relational-database.md +525 -0
- package/ai-docs/config-ref/remix-web.md +74 -0
- package/ai-docs/config-ref/sns-topic.md +69 -0
- package/ai-docs/config-ref/solidstart-web.md +75 -0
- package/ai-docs/config-ref/sqs-queue-not-empty.md +414 -0
- package/ai-docs/config-ref/sqs-queue.md +232 -0
- package/ai-docs/config-ref/state-machine.md +235 -0
- package/ai-docs/config-ref/sveltekit-web.md +81 -0
- package/ai-docs/config-ref/tanstack-web.md +75 -0
- package/ai-docs/config-ref/upstash-redis.md +59 -0
- package/ai-docs/config-ref/user-auth-pool.md +876 -0
- package/ai-docs/config-ref/web-app-firewall.md +212 -0
- package/ai-docs/config-ref/web-service.md +178 -0
- package/ai-docs/config-ref/worker-service.md +41 -0
- package/ai-docs/getting-started/console.md +232 -0
- package/ai-docs/getting-started/deployment.md +434 -0
- package/ai-docs/getting-started/dev-mode.md +118 -0
- package/ai-docs/getting-started/how-it-works.md +119 -0
- package/ai-docs/getting-started/intro.md +157 -0
- package/ai-docs/getting-started/using-with-ai.md +228 -0
- package/ai-docs/getting-started/workflow.md +197 -0
- package/ai-docs/index.json +1514 -0
- package/ai-docs/recipe/background-jobs.md +183 -0
- package/ai-docs/recipe/database-migrations.md +240 -0
- package/ai-docs/recipe/graphql-api.md +211 -0
- package/ai-docs/recipe/monorepo-setup.md +183 -0
- package/ai-docs/recipe/nextjs-full-stack.md +188 -0
- package/ai-docs/recipe/rest-api-with-database.md +156 -0
- package/ai-docs/recipe/scheduled-tasks.md +186 -0
- package/ai-docs/recipe/static-website.md +241 -0
- package/ai-docs/troubleshooting/cloudformation-stack-states.md +189 -0
- package/bin/stacktape.js +0 -12
- package/package.json +1 -1
- package/plain.d.ts +372 -111
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
docType: recipe
|
|
3
|
+
title: Monorepo Setup
|
|
4
|
+
tags:
|
|
5
|
+
- monorepo
|
|
6
|
+
- setup
|
|
7
|
+
- recipe
|
|
8
|
+
source: docs/_curated-docs/recipes/monorepo-setup.mdx
|
|
9
|
+
priority: 1
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Monorepo Setup
|
|
13
|
+
|
|
14
|
+
Deploy multiple services from a single repository.
|
|
15
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
## Shared Infrastructure Stack
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// packages/infra/stacktape.ts
|
|
22
|
+
import { defineConfig, RelationalDatabase, RdsEnginePostgres, $Secret } from 'stacktape';
|
|
23
|
+
|
|
24
|
+
export default defineConfig(({ stage }) => {
|
|
25
|
+
const database = new RelationalDatabase({
|
|
26
|
+
engine: new RdsEnginePostgres({ version: '16' }),
|
|
27
|
+
credentials: {
|
|
28
|
+
masterUserPassword: $Secret(`db-password-${stage}`)
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
resources: { database }
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API Service Stack
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// packages/api/stacktape.ts
|
|
42
|
+
import { defineConfig, LambdaFunction, HttpApiGateway, $CfStackOutput } from 'stacktape';
|
|
43
|
+
|
|
44
|
+
export default defineConfig(({ stage }) => {
|
|
45
|
+
const api = new LambdaFunction({
|
|
46
|
+
packaging: {
|
|
47
|
+
type: 'stacktape-lambda-buildpack',
|
|
48
|
+
properties: {
|
|
49
|
+
entryfilePath: './src/handler.ts'
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
environment: {
|
|
53
|
+
// Reference the shared database from infra stack
|
|
54
|
+
DATABASE_URL: $CfStackOutput(`infra-${stage}`, 'database', 'connectionString')
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const gateway = new HttpApiGateway({
|
|
59
|
+
routes: [{ path: '/{proxy+}', method: '*', integration: { type: 'function', properties: { function: api } } }]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
resources: { api, gateway }
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Web Service Stack
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// packages/web/stacktape.ts
|
|
72
|
+
import { defineConfig, NextjsWeb, $CfStackOutput } from 'stacktape';
|
|
73
|
+
|
|
74
|
+
export default defineConfig(({ stage }) => {
|
|
75
|
+
const website = new NextjsWeb({
|
|
76
|
+
appDirectory: './',
|
|
77
|
+
environment: [
|
|
78
|
+
{
|
|
79
|
+
name: 'NEXT_PUBLIC_API_URL',
|
|
80
|
+
value: $CfStackOutput(`api-${stage}`, 'gateway', 'url')
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
resources: { website }
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Deployment Scripts
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
// package.json (root)
|
|
95
|
+
{
|
|
96
|
+
"scripts": {
|
|
97
|
+
"deploy:infra": "cd packages/infra && stacktape deploy",
|
|
98
|
+
"deploy:api": "cd packages/api && stacktape deploy",
|
|
99
|
+
"deploy:web": "cd packages/web && stacktape deploy",
|
|
100
|
+
"deploy:all": "npm run deploy:infra && npm run deploy:api && npm run deploy:web"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Deploy Order
|
|
106
|
+
|
|
107
|
+
Infrastructure must be deployed first since other stacks reference it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 1. Deploy shared infrastructure
|
|
111
|
+
cd packages/infra
|
|
112
|
+
stacktape deploy --stage dev --region us-east-1
|
|
113
|
+
|
|
114
|
+
# 2. Deploy API (references infra)
|
|
115
|
+
cd packages/api
|
|
116
|
+
stacktape deploy --stage dev --region us-east-1
|
|
117
|
+
|
|
118
|
+
# 3. Deploy Web (references API)
|
|
119
|
+
cd packages/web
|
|
120
|
+
stacktape deploy --stage dev --region us-east-1
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## CI/CD with GitHub Actions
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# .github/workflows/deploy.yml
|
|
127
|
+
name: Deploy
|
|
128
|
+
|
|
129
|
+
on:
|
|
130
|
+
push:
|
|
131
|
+
branches: [main]
|
|
132
|
+
|
|
133
|
+
jobs:
|
|
134
|
+
deploy:
|
|
135
|
+
runs-on: ubuntu-latest
|
|
136
|
+
steps:
|
|
137
|
+
- uses: actions/checkout@v4
|
|
138
|
+
|
|
139
|
+
- uses: oven-sh/setup-bun@v1
|
|
140
|
+
|
|
141
|
+
- run: bun install
|
|
142
|
+
|
|
143
|
+
# Deploy in order
|
|
144
|
+
- name: Deploy Infrastructure
|
|
145
|
+
run: |
|
|
146
|
+
cd packages/infra
|
|
147
|
+
stacktape deploy --stage production --region us-east-1 --autoConfirmOperation
|
|
148
|
+
env:
|
|
149
|
+
STACKTAPE_API_KEY: ${{ secrets.STACKTAPE_API_KEY }}
|
|
150
|
+
|
|
151
|
+
- name: Deploy API
|
|
152
|
+
run: |
|
|
153
|
+
cd packages/api
|
|
154
|
+
stacktape deploy --stage production --region us-east-1 --autoConfirmOperation
|
|
155
|
+
env:
|
|
156
|
+
STACKTAPE_API_KEY: ${{ secrets.STACKTAPE_API_KEY }}
|
|
157
|
+
|
|
158
|
+
- name: Deploy Web
|
|
159
|
+
run: |
|
|
160
|
+
cd packages/web
|
|
161
|
+
stacktape deploy --stage production --region us-east-1 --autoConfirmOperation
|
|
162
|
+
env:
|
|
163
|
+
STACKTAPE_API_KEY: ${{ secrets.STACKTAPE_API_KEY }}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## With Turborepo
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
// turbo.json
|
|
170
|
+
{
|
|
171
|
+
"pipeline": {
|
|
172
|
+
"deploy": {
|
|
173
|
+
"dependsOn": ["^deploy"],
|
|
174
|
+
"cache": false
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Deploy all in dependency order
|
|
182
|
+
turbo run deploy
|
|
183
|
+
```
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
docType: recipe
|
|
3
|
+
title: Next.js Full-Stack
|
|
4
|
+
tags:
|
|
5
|
+
- next.js
|
|
6
|
+
- full-stack
|
|
7
|
+
- recipe
|
|
8
|
+
source: docs/_curated-docs/recipes/nextjs-full-stack.mdx
|
|
9
|
+
priority: 1
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Next.js Full-Stack Application
|
|
13
|
+
|
|
14
|
+
A complete Next.js application with database, authentication, and file uploads.
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import {
|
|
20
|
+
defineConfig,
|
|
21
|
+
NextjsWeb,
|
|
22
|
+
RelationalDatabase,
|
|
23
|
+
RdsEnginePostgres,
|
|
24
|
+
UserAuthPool,
|
|
25
|
+
Bucket,
|
|
26
|
+
$Secret,
|
|
27
|
+
$ResourceParam
|
|
28
|
+
} from 'stacktape';
|
|
29
|
+
|
|
30
|
+
export default defineConfig(({ stage }) => {
|
|
31
|
+
const isProduction = stage === 'production';
|
|
32
|
+
|
|
33
|
+
// PostgreSQL database
|
|
34
|
+
const database = new RelationalDatabase({
|
|
35
|
+
engine: new RdsEnginePostgres({
|
|
36
|
+
version: '16',
|
|
37
|
+
primaryInstance: {
|
|
38
|
+
instanceSize: isProduction ? 'db.t4g.small' : 'db.t4g.micro'
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
credentials: {
|
|
42
|
+
masterUserPassword: $Secret(`db-password-${stage}`)
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// User authentication
|
|
47
|
+
const auth = new UserAuthPool({
|
|
48
|
+
passwordPolicy: {
|
|
49
|
+
minimumLength: 8,
|
|
50
|
+
requireNumbers: true
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// File uploads bucket
|
|
55
|
+
const uploads = new Bucket({
|
|
56
|
+
directoryUpload: {
|
|
57
|
+
directoryPath: './public/uploads'
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Next.js application
|
|
62
|
+
const website = new NextjsWeb({
|
|
63
|
+
appDirectory: './',
|
|
64
|
+
connectTo: [database, auth, uploads],
|
|
65
|
+
serverLambda: {
|
|
66
|
+
joinDefaultVpc: true, // Required for database access
|
|
67
|
+
memory: 1024
|
|
68
|
+
},
|
|
69
|
+
customDomains: isProduction ? [{ domainName: 'myapp.com' }, { domainName: 'www.myapp.com' }] : [],
|
|
70
|
+
environment: [
|
|
71
|
+
{ name: 'NEXTAUTH_SECRET', value: $Secret('nextauth-secret') },
|
|
72
|
+
{ name: 'NEXTAUTH_URL', value: isProduction ? 'https://myapp.com' : `https://${stage}.myapp.com` }
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
hooks: {
|
|
78
|
+
afterDeploy: [{ scriptName: 'migrate' }]
|
|
79
|
+
},
|
|
80
|
+
scripts: {
|
|
81
|
+
migrate: {
|
|
82
|
+
executeCommand: 'npx prisma migrate deploy',
|
|
83
|
+
environment: {
|
|
84
|
+
DATABASE_URL: $ResourceParam('database', 'connectionString')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
resources: { database, auth, uploads, website }
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Project Structure
|
|
94
|
+
|
|
95
|
+
## Database Access
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// lib/db.ts
|
|
99
|
+
import { PrismaClient } from '@prisma/client';
|
|
100
|
+
|
|
101
|
+
const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
|
102
|
+
|
|
103
|
+
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
|
104
|
+
|
|
105
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// app/api/users/route.ts
|
|
110
|
+
import { prisma } from '@/lib/db';
|
|
111
|
+
import { NextResponse } from 'next/server';
|
|
112
|
+
|
|
113
|
+
export async function GET() {
|
|
114
|
+
const users = await prisma.user.findMany();
|
|
115
|
+
return NextResponse.json(users);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Authentication
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// app/api/auth/[...nextauth]/route.ts
|
|
123
|
+
import NextAuth from 'next-auth';
|
|
124
|
+
import CognitoProvider from 'next-auth/providers/cognito';
|
|
125
|
+
|
|
126
|
+
const handler = NextAuth({
|
|
127
|
+
providers: [
|
|
128
|
+
CognitoProvider({
|
|
129
|
+
clientId: process.env.STP_AUTH_USER_POOL_CLIENT_ID!,
|
|
130
|
+
clientSecret: process.env.STP_AUTH_USER_POOL_CLIENT_SECRET!,
|
|
131
|
+
issuer: process.env.STP_AUTH_USER_POOL_ISSUER_URL!
|
|
132
|
+
})
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export { handler as GET, handler as POST };
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## File Uploads
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// app/api/upload/route.ts
|
|
143
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
144
|
+
import { NextResponse } from 'next/server';
|
|
145
|
+
|
|
146
|
+
const s3 = new S3Client({});
|
|
147
|
+
|
|
148
|
+
export async function POST(request: Request) {
|
|
149
|
+
const formData = await request.formData();
|
|
150
|
+
const file = formData.get('file') as File;
|
|
151
|
+
|
|
152
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
153
|
+
const key = `uploads/${Date.now()}-${file.name}`;
|
|
154
|
+
|
|
155
|
+
await s3.send(
|
|
156
|
+
new PutObjectCommand({
|
|
157
|
+
Bucket: process.env.STP_UPLOADS_BUCKET_NAME,
|
|
158
|
+
Key: key,
|
|
159
|
+
Body: buffer,
|
|
160
|
+
ContentType: file.type
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return NextResponse.json({ key });
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Start dev mode
|
|
172
|
+
stacktape dev --stage dev --region us-east-1
|
|
173
|
+
|
|
174
|
+
# Opens: http://localhost:3000
|
|
175
|
+
# Database emulated locally
|
|
176
|
+
# Auth pool connected to AWS
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Deploy
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Create secrets
|
|
183
|
+
stacktape secret:create --region us-east-1 # db-password-dev
|
|
184
|
+
stacktape secret:create --region us-east-1 # nextauth-secret
|
|
185
|
+
|
|
186
|
+
# Deploy
|
|
187
|
+
stacktape deploy --stage dev --region us-east-1
|
|
188
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
docType: recipe
|
|
3
|
+
title: REST API + Database
|
|
4
|
+
tags:
|
|
5
|
+
- rest
|
|
6
|
+
- api
|
|
7
|
+
- database
|
|
8
|
+
- recipe
|
|
9
|
+
source: docs/_curated-docs/recipes/rest-api-with-database.mdx
|
|
10
|
+
priority: 1
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# REST API with PostgreSQL
|
|
14
|
+
|
|
15
|
+
A complete REST API using Lambda functions connected to a PostgreSQL database.
|
|
16
|
+
|
|
17
|
+
## Final Configuration
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import {
|
|
21
|
+
defineConfig,
|
|
22
|
+
LambdaFunction,
|
|
23
|
+
RelationalDatabase,
|
|
24
|
+
RdsEnginePostgres,
|
|
25
|
+
HttpApiGateway,
|
|
26
|
+
$Secret
|
|
27
|
+
} from 'stacktape';
|
|
28
|
+
|
|
29
|
+
export default defineConfig(({ stage }) => {
|
|
30
|
+
const isProduction = stage === 'production';
|
|
31
|
+
|
|
32
|
+
// PostgreSQL database
|
|
33
|
+
const database = new RelationalDatabase({
|
|
34
|
+
engine: new RdsEnginePostgres({
|
|
35
|
+
version: '16',
|
|
36
|
+
primaryInstance: {
|
|
37
|
+
instanceSize: isProduction ? 'db.t4g.small' : 'db.t4g.micro'
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
credentials: {
|
|
41
|
+
masterUserPassword: $Secret(`db-password-${stage}`)
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// API handler
|
|
46
|
+
const api = new LambdaFunction({
|
|
47
|
+
packaging: {
|
|
48
|
+
type: 'stacktape-lambda-buildpack',
|
|
49
|
+
properties: {
|
|
50
|
+
entryfilePath: './src/handler.ts'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
connectTo: [database],
|
|
54
|
+
environment: {
|
|
55
|
+
NODE_ENV: isProduction ? 'production' : 'development'
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// HTTP Gateway
|
|
60
|
+
const gateway = new HttpApiGateway({
|
|
61
|
+
routes: [{ path: '/{proxy+}', method: '*', integration: { type: 'function', properties: { function: api } } }]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
resources: { database, api, gateway }
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Project Structure
|
|
71
|
+
|
|
72
|
+
## Handler Code
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// src/handler.ts
|
|
76
|
+
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
77
|
+
import { Pool } from 'pg';
|
|
78
|
+
|
|
79
|
+
// Connection pool (reused across invocations)
|
|
80
|
+
const pool = new Pool({
|
|
81
|
+
connectionString: process.env.STP_DATABASE_CONNECTION_STRING
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
|
|
85
|
+
const { httpMethod, path } = event.requestContext.http;
|
|
86
|
+
|
|
87
|
+
if (path === '/users' && httpMethod === 'GET') {
|
|
88
|
+
const result = await pool.query('SELECT * FROM users LIMIT 100');
|
|
89
|
+
return {
|
|
90
|
+
statusCode: 200,
|
|
91
|
+
body: JSON.stringify(result.rows)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (path === '/users' && httpMethod === 'POST') {
|
|
96
|
+
const body = JSON.parse(event.body || '{}');
|
|
97
|
+
const result = await pool.query('INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [
|
|
98
|
+
body.name,
|
|
99
|
+
body.email
|
|
100
|
+
]);
|
|
101
|
+
return {
|
|
102
|
+
statusCode: 201,
|
|
103
|
+
body: JSON.stringify(result.rows[0])
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { statusCode: 404, body: 'Not found' };
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Adding Migrations
|
|
112
|
+
|
|
113
|
+
Run database migrations on every deployment:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export default defineConfig(({ stage }) => {
|
|
117
|
+
// ... resources ...
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
hooks: {
|
|
121
|
+
afterDeploy: [{ scriptName: 'migrate' }]
|
|
122
|
+
},
|
|
123
|
+
scripts: {
|
|
124
|
+
migrate: {
|
|
125
|
+
executeCommand: 'npx prisma migrate deploy',
|
|
126
|
+
environment: {
|
|
127
|
+
DATABASE_URL: $ResourceParam('database', 'connectionString')
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
resources: { database, api, gateway }
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Start local development
|
|
140
|
+
stacktape dev --stage dev --region us-east-1
|
|
141
|
+
|
|
142
|
+
# Database runs locally in Docker
|
|
143
|
+
# API runs locally with hot-reload
|
|
144
|
+
# Requests tunnel through AWS
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Deploy
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Create the database password secret first
|
|
151
|
+
stacktape secret:create --region us-east-1
|
|
152
|
+
# name: db-password-dev
|
|
153
|
+
|
|
154
|
+
# Deploy
|
|
155
|
+
stacktape deploy --stage dev --region us-east-1
|
|
156
|
+
```
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
docType: recipe
|
|
3
|
+
title: Scheduled Tasks
|
|
4
|
+
tags:
|
|
5
|
+
- scheduled
|
|
6
|
+
- tasks
|
|
7
|
+
- recipe
|
|
8
|
+
source: docs/_curated-docs/recipes/scheduled-tasks.mdx
|
|
9
|
+
priority: 1
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Scheduled Tasks (Cron Jobs)
|
|
13
|
+
|
|
14
|
+
Run Lambda functions on a schedule using CloudWatch Events.
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { defineConfig, LambdaFunction, RelationalDatabase, RdsEnginePostgres, $Secret } from 'stacktape';
|
|
20
|
+
|
|
21
|
+
export default defineConfig(() => {
|
|
22
|
+
const database = new RelationalDatabase({
|
|
23
|
+
engine: new RdsEnginePostgres({ version: '16' }),
|
|
24
|
+
credentials: {
|
|
25
|
+
masterUserPassword: $Secret('db-password')
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Daily cleanup job - runs at 3 AM UTC
|
|
30
|
+
const dailyCleanup = new LambdaFunction({
|
|
31
|
+
packaging: {
|
|
32
|
+
type: 'stacktape-lambda-buildpack',
|
|
33
|
+
properties: {
|
|
34
|
+
entryfilePath: './src/jobs/cleanup.ts'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
timeout: 900, // 15 minutes
|
|
38
|
+
connectTo: [database],
|
|
39
|
+
events: [
|
|
40
|
+
{
|
|
41
|
+
type: 'schedule',
|
|
42
|
+
properties: {
|
|
43
|
+
scheduleRate: 'cron(0 3 * * ? *)' // 3 AM UTC daily
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Hourly metrics job
|
|
50
|
+
const hourlyMetrics = new LambdaFunction({
|
|
51
|
+
packaging: {
|
|
52
|
+
type: 'stacktape-lambda-buildpack',
|
|
53
|
+
properties: {
|
|
54
|
+
entryfilePath: './src/jobs/metrics.ts'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
timeout: 300,
|
|
58
|
+
connectTo: [database],
|
|
59
|
+
events: [
|
|
60
|
+
{
|
|
61
|
+
type: 'schedule',
|
|
62
|
+
properties: {
|
|
63
|
+
scheduleRate: 'rate(1 hour)'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Weekly report - runs every Monday at 9 AM UTC
|
|
70
|
+
const weeklyReport = new LambdaFunction({
|
|
71
|
+
packaging: {
|
|
72
|
+
type: 'stacktape-lambda-buildpack',
|
|
73
|
+
properties: {
|
|
74
|
+
entryfilePath: './src/jobs/weekly-report.ts'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
timeout: 900,
|
|
78
|
+
connectTo: [database],
|
|
79
|
+
events: [
|
|
80
|
+
{
|
|
81
|
+
type: 'schedule',
|
|
82
|
+
properties: {
|
|
83
|
+
scheduleRate: 'cron(0 9 ? * MON *)' // Monday 9 AM UTC
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
resources: { database, dailyCleanup, hourlyMetrics, weeklyReport }
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Schedule Expressions
|
|
96
|
+
|
|
97
|
+
### Rate Expressions
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
rate(1 minute)
|
|
101
|
+
rate(5 minutes)
|
|
102
|
+
rate(1 hour)
|
|
103
|
+
rate(12 hours)
|
|
104
|
+
rate(1 day)
|
|
105
|
+
rate(7 days)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Cron Expressions
|
|
109
|
+
|
|
110
|
+
Format: `cron(minutes hours day-of-month month day-of-week year)`
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
cron(0 3 * * ? *) # 3:00 AM UTC every day
|
|
114
|
+
cron(0 */2 * * ? *) # Every 2 hours
|
|
115
|
+
cron(0 9 ? * MON *) # 9 AM every Monday
|
|
116
|
+
cron(0 0 1 * ? *) # Midnight on the 1st of each month
|
|
117
|
+
cron(0 12 ? * MON-FRI *)# Noon on weekdays
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Job Handler
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// src/jobs/cleanup.ts
|
|
124
|
+
import { ScheduledHandler } from 'aws-lambda';
|
|
125
|
+
import { Pool } from 'pg';
|
|
126
|
+
|
|
127
|
+
const pool = new Pool({
|
|
128
|
+
connectionString: process.env.STP_DATABASE_CONNECTION_STRING
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const handler: ScheduledHandler = async (event) => {
|
|
132
|
+
console.log('Starting cleanup job', event);
|
|
133
|
+
|
|
134
|
+
// Delete old sessions
|
|
135
|
+
const sessionsResult = await pool.query("DELETE FROM sessions WHERE expires_at < NOW() - INTERVAL '30 days'");
|
|
136
|
+
console.log(`Deleted ${sessionsResult.rowCount} expired sessions`);
|
|
137
|
+
|
|
138
|
+
// Delete old logs
|
|
139
|
+
const logsResult = await pool.query("DELETE FROM audit_logs WHERE created_at < NOW() - INTERVAL '90 days'");
|
|
140
|
+
console.log(`Deleted ${logsResult.rowCount} old audit logs`);
|
|
141
|
+
|
|
142
|
+
// Vacuum analyze for performance
|
|
143
|
+
await pool.query('VACUUM ANALYZE');
|
|
144
|
+
|
|
145
|
+
console.log('Cleanup complete');
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Weekly Report Example
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// src/jobs/weekly-report.ts
|
|
153
|
+
import { ScheduledHandler } from 'aws-lambda';
|
|
154
|
+
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
155
|
+
|
|
156
|
+
const ses = new SESClient({});
|
|
157
|
+
|
|
158
|
+
export const handler: ScheduledHandler = async () => {
|
|
159
|
+
// Generate report data
|
|
160
|
+
const report = await generateWeeklyStats();
|
|
161
|
+
|
|
162
|
+
// Send email
|
|
163
|
+
await ses.send(
|
|
164
|
+
new SendEmailCommand({
|
|
165
|
+
Source: 'reports@myapp.com',
|
|
166
|
+
Destination: {
|
|
167
|
+
ToAddresses: ['team@myapp.com']
|
|
168
|
+
},
|
|
169
|
+
Message: {
|
|
170
|
+
Subject: { Data: `Weekly Report - ${new Date().toLocaleDateString()}` },
|
|
171
|
+
Body: {
|
|
172
|
+
Html: { Data: formatReportHtml(report) }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Monitoring
|
|
181
|
+
|
|
182
|
+
View scheduled job logs:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
stacktape logs --stage dev --region us-east-1 --resourceName dailyCleanup --startTime 24h
|
|
186
|
+
```
|