motia 0.5.11-beta.120-011950 → 0.5.11-beta.120-356715
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/dist/cjs/create/templates/python/steps/api_step.py.txt +1 -1
- package/dist/cjs/create/templates/python/tutorial.tsx.txt +1 -1
- package/dist/cjs/create/templates/typescript/steps/api.step.ts.txt +2 -2
- package/dist/cjs/create/templates/typescript/tutorial.tsx.txt +1 -1
- package/dist/dot-files/.cursor/rules/ai-agent-patterns.mdc +725 -0
- package/dist/dot-files/.cursor/rules/api-design-patterns.mdc +740 -0
- package/dist/dot-files/.cursor/rules/api-steps.mdc +125 -64
- package/dist/dot-files/.cursor/rules/authentication-patterns.mdc +620 -0
- package/dist/dot-files/.cursor/rules/background-job-patterns.mdc +628 -0
- package/dist/dot-files/.cursor/rules/complete-application-patterns.mdc +433 -0
- package/dist/dot-files/.cursor/rules/complete-backend-generator.mdc +415 -0
- package/dist/dot-files/.cursor/rules/event-steps.mdc +271 -133
- package/dist/dot-files/.cursor/rules/multi-language-workflows.mdc +1059 -0
- package/dist/dot-files/.cursor/rules/production-deployment.mdc +668 -0
- package/dist/dot-files/.cursor/rules/realtime-streaming.mdc +656 -0
- package/dist/dot-files/.cursor/rules/state-management.mdc +133 -87
- package/dist/dot-files/.cursor/rules/steps.mdc +68 -12
- package/dist/dot-files/.cursor/rules/workflow-patterns.mdc +938 -0
- package/dist/dot-files/CLAUDE.md +334 -129
- package/dist/esm/create/templates/python/steps/api_step.py.txt +1 -1
- package/dist/esm/create/templates/python/tutorial.tsx.txt +1 -1
- package/dist/esm/create/templates/typescript/steps/api.step.ts.txt +2 -2
- package/dist/esm/create/templates/typescript/tutorial.tsx.txt +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Production deployment patterns and DevOps best practices for Motia applications
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
# Production Deployment Patterns
|
|
7
|
+
|
|
8
|
+
Deploy robust, scalable Motia applications to production environments with confidence.
|
|
9
|
+
|
|
10
|
+
## Environment Configuration
|
|
11
|
+
|
|
12
|
+
### Environment Variables
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# .env.example
|
|
16
|
+
# Application
|
|
17
|
+
NODE_ENV=production
|
|
18
|
+
PORT=3000
|
|
19
|
+
APP_NAME=my-motia-app
|
|
20
|
+
|
|
21
|
+
# Authentication
|
|
22
|
+
JWT_SECRET=your-super-secure-jwt-secret-here
|
|
23
|
+
JWT_EXPIRES_IN=15m
|
|
24
|
+
REFRESH_TOKEN_EXPIRES_IN=30d
|
|
25
|
+
BCRYPT_ROUNDS=12
|
|
26
|
+
|
|
27
|
+
# Database
|
|
28
|
+
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
|
|
29
|
+
REDIS_URL=redis://localhost:6379
|
|
30
|
+
|
|
31
|
+
# External Services
|
|
32
|
+
OPENAI_API_KEY=sk-...
|
|
33
|
+
SENDGRID_API_KEY=SG...
|
|
34
|
+
AWS_ACCESS_KEY_ID=AKIA...
|
|
35
|
+
AWS_SECRET_ACCESS_KEY=...
|
|
36
|
+
AWS_S3_BUCKET=my-app-storage
|
|
37
|
+
AWS_REGION=us-east-1
|
|
38
|
+
|
|
39
|
+
# Monitoring
|
|
40
|
+
SENTRY_DSN=https://...@sentry.io/...
|
|
41
|
+
LOG_LEVEL=info
|
|
42
|
+
|
|
43
|
+
# Rate Limiting
|
|
44
|
+
RATE_LIMIT_WINDOW_MS=900000
|
|
45
|
+
RATE_LIMIT_MAX_REQUESTS=100
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Configuration Management
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// config/environment.ts
|
|
52
|
+
import { z } from 'zod'
|
|
53
|
+
|
|
54
|
+
const envSchema = z.object({
|
|
55
|
+
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
|
|
56
|
+
PORT: z.coerce.number().default(3000),
|
|
57
|
+
APP_NAME: z.string().default('motia-app'),
|
|
58
|
+
|
|
59
|
+
// Authentication
|
|
60
|
+
JWT_SECRET: z.string().min(32),
|
|
61
|
+
JWT_EXPIRES_IN: z.string().default('15m'),
|
|
62
|
+
REFRESH_TOKEN_EXPIRES_IN: z.string().default('30d'),
|
|
63
|
+
BCRYPT_ROUNDS: z.coerce.number().default(12),
|
|
64
|
+
|
|
65
|
+
// Database
|
|
66
|
+
DATABASE_URL: z.string().url().optional(),
|
|
67
|
+
REDIS_URL: z.string().url().optional(),
|
|
68
|
+
|
|
69
|
+
// External Services
|
|
70
|
+
OPENAI_API_KEY: z.string().optional(),
|
|
71
|
+
SENDGRID_API_KEY: z.string().optional(),
|
|
72
|
+
|
|
73
|
+
// AWS
|
|
74
|
+
AWS_ACCESS_KEY_ID: z.string().optional(),
|
|
75
|
+
AWS_SECRET_ACCESS_KEY: z.string().optional(),
|
|
76
|
+
AWS_S3_BUCKET: z.string().optional(),
|
|
77
|
+
AWS_REGION: z.string().default('us-east-1'),
|
|
78
|
+
|
|
79
|
+
// Monitoring
|
|
80
|
+
SENTRY_DSN: z.string().url().optional(),
|
|
81
|
+
LOG_LEVEL: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
|
|
82
|
+
|
|
83
|
+
// Rate Limiting
|
|
84
|
+
RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900000),
|
|
85
|
+
RATE_LIMIT_MAX_REQUESTS: z.coerce.number().default(100)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export type Environment = z.infer<typeof envSchema>
|
|
89
|
+
|
|
90
|
+
export const env = envSchema.parse(process.env)
|
|
91
|
+
|
|
92
|
+
export const isDevelopment = env.NODE_ENV === 'development'
|
|
93
|
+
export const isProduction = env.NODE_ENV === 'production'
|
|
94
|
+
export const isStaging = env.NODE_ENV === 'staging'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Docker Configuration
|
|
98
|
+
|
|
99
|
+
```dockerfile
|
|
100
|
+
# Dockerfile
|
|
101
|
+
FROM node:18-alpine AS base
|
|
102
|
+
|
|
103
|
+
# Install Python for multi-language support
|
|
104
|
+
RUN apk add --no-cache python3 py3-pip build-base
|
|
105
|
+
|
|
106
|
+
# Install pnpm
|
|
107
|
+
RUN npm install -g pnpm
|
|
108
|
+
|
|
109
|
+
WORKDIR /app
|
|
110
|
+
|
|
111
|
+
# Copy package files
|
|
112
|
+
COPY package.json pnpm-lock.yaml ./
|
|
113
|
+
COPY requirements.txt ./
|
|
114
|
+
|
|
115
|
+
# Install dependencies
|
|
116
|
+
RUN pnpm install --frozen-lockfile --production
|
|
117
|
+
RUN pip3 install -r requirements.txt
|
|
118
|
+
|
|
119
|
+
# Copy source code
|
|
120
|
+
COPY . .
|
|
121
|
+
|
|
122
|
+
# Build if necessary
|
|
123
|
+
RUN pnpm build || true
|
|
124
|
+
|
|
125
|
+
# Create non-root user
|
|
126
|
+
RUN addgroup -g 1001 -S nodejs
|
|
127
|
+
RUN adduser -S motia -u 1001
|
|
128
|
+
|
|
129
|
+
# Change ownership
|
|
130
|
+
RUN chown -R motia:nodejs /app
|
|
131
|
+
USER motia
|
|
132
|
+
|
|
133
|
+
# Expose port
|
|
134
|
+
EXPOSE 3000
|
|
135
|
+
|
|
136
|
+
# Health check
|
|
137
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
138
|
+
CMD curl -f http://localhost:3000/health || exit 1
|
|
139
|
+
|
|
140
|
+
# Start application
|
|
141
|
+
CMD ["pnpm", "start"]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
# docker-compose.yml
|
|
146
|
+
version: '3.8'
|
|
147
|
+
|
|
148
|
+
services:
|
|
149
|
+
app:
|
|
150
|
+
build: .
|
|
151
|
+
ports:
|
|
152
|
+
- "3000:3000"
|
|
153
|
+
environment:
|
|
154
|
+
- NODE_ENV=production
|
|
155
|
+
- DATABASE_URL=postgresql://postgres:password@db:5432/motia_prod
|
|
156
|
+
- REDIS_URL=redis://redis:6379
|
|
157
|
+
depends_on:
|
|
158
|
+
- db
|
|
159
|
+
- redis
|
|
160
|
+
restart: unless-stopped
|
|
161
|
+
healthcheck:
|
|
162
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
163
|
+
interval: 30s
|
|
164
|
+
timeout: 10s
|
|
165
|
+
retries: 3
|
|
166
|
+
|
|
167
|
+
db:
|
|
168
|
+
image: postgres:15-alpine
|
|
169
|
+
environment:
|
|
170
|
+
POSTGRES_DB: motia_prod
|
|
171
|
+
POSTGRES_USER: postgres
|
|
172
|
+
POSTGRES_PASSWORD: password
|
|
173
|
+
volumes:
|
|
174
|
+
- postgres_data:/var/lib/postgresql/data
|
|
175
|
+
ports:
|
|
176
|
+
- "5432:5432"
|
|
177
|
+
restart: unless-stopped
|
|
178
|
+
healthcheck:
|
|
179
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
180
|
+
interval: 10s
|
|
181
|
+
timeout: 5s
|
|
182
|
+
retries: 5
|
|
183
|
+
|
|
184
|
+
redis:
|
|
185
|
+
image: redis:7-alpine
|
|
186
|
+
command: redis-server --appendonly yes
|
|
187
|
+
volumes:
|
|
188
|
+
- redis_data:/data
|
|
189
|
+
ports:
|
|
190
|
+
- "6379:6379"
|
|
191
|
+
restart: unless-stopped
|
|
192
|
+
healthcheck:
|
|
193
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
194
|
+
interval: 10s
|
|
195
|
+
timeout: 5s
|
|
196
|
+
retries: 3
|
|
197
|
+
|
|
198
|
+
volumes:
|
|
199
|
+
postgres_data:
|
|
200
|
+
redis_data:
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Health Checks & Monitoring
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// steps/monitoring/health-check.step.ts
|
|
207
|
+
import { ApiRouteConfig, Handlers } from 'motia'
|
|
208
|
+
import { z } from 'zod'
|
|
209
|
+
|
|
210
|
+
export const config: ApiRouteConfig = {
|
|
211
|
+
type: 'api',
|
|
212
|
+
name: 'HealthCheck',
|
|
213
|
+
description: 'Application health check endpoint',
|
|
214
|
+
method: 'GET',
|
|
215
|
+
path: '/health',
|
|
216
|
+
responseSchema: {
|
|
217
|
+
200: z.object({
|
|
218
|
+
status: z.string(),
|
|
219
|
+
timestamp: z.string(),
|
|
220
|
+
uptime: z.number(),
|
|
221
|
+
version: z.string(),
|
|
222
|
+
environment: z.string(),
|
|
223
|
+
services: z.record(z.object({
|
|
224
|
+
status: z.enum(['healthy', 'unhealthy', 'degraded']),
|
|
225
|
+
responseTime: z.number().optional(),
|
|
226
|
+
error: z.string().optional()
|
|
227
|
+
}))
|
|
228
|
+
}),
|
|
229
|
+
503: z.object({
|
|
230
|
+
status: z.string(),
|
|
231
|
+
error: z.string()
|
|
232
|
+
})
|
|
233
|
+
},
|
|
234
|
+
emits: [],
|
|
235
|
+
flows: ['monitoring']
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const handler: Handlers['HealthCheck'] = async (req, { logger, state }) => {
|
|
239
|
+
try {
|
|
240
|
+
const startTime = Date.now()
|
|
241
|
+
const services: Record<string, any> = {}
|
|
242
|
+
|
|
243
|
+
// Check database connectivity
|
|
244
|
+
try {
|
|
245
|
+
const dbStart = Date.now()
|
|
246
|
+
await state.get('health', 'db-check')
|
|
247
|
+
services.database = {
|
|
248
|
+
status: 'healthy',
|
|
249
|
+
responseTime: Date.now() - dbStart
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
services.database = {
|
|
253
|
+
status: 'unhealthy',
|
|
254
|
+
error: error.message
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check Redis connectivity
|
|
259
|
+
try {
|
|
260
|
+
const redisStart = Date.now()
|
|
261
|
+
await checkRedisHealth()
|
|
262
|
+
services.redis = {
|
|
263
|
+
status: 'healthy',
|
|
264
|
+
responseTime: Date.now() - redisStart
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
services.redis = {
|
|
268
|
+
status: 'unhealthy',
|
|
269
|
+
error: error.message
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check external services
|
|
274
|
+
services.openai = await checkOpenAIHealth()
|
|
275
|
+
services.email = await checkEmailServiceHealth()
|
|
276
|
+
|
|
277
|
+
const allHealthy = Object.values(services).every(
|
|
278
|
+
(service: any) => service.status === 'healthy'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const healthStatus = {
|
|
282
|
+
status: allHealthy ? 'healthy' : 'degraded',
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
uptime: process.uptime(),
|
|
285
|
+
version: process.env.npm_package_version || '1.0.0',
|
|
286
|
+
environment: process.env.NODE_ENV || 'unknown',
|
|
287
|
+
services
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!allHealthy) {
|
|
291
|
+
logger.warn('Health check showing degraded status', { services })
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
status: allHealthy ? 200 : 503,
|
|
296
|
+
body: healthStatus
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
logger.error('Health check failed', { error: error.message })
|
|
300
|
+
return {
|
|
301
|
+
status: 503,
|
|
302
|
+
body: {
|
|
303
|
+
status: 'unhealthy',
|
|
304
|
+
error: error.message
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function checkRedisHealth(): Promise<void> {
|
|
311
|
+
// Implement Redis health check
|
|
312
|
+
// Could use ioredis client to ping Redis
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function checkOpenAIHealth() {
|
|
316
|
+
try {
|
|
317
|
+
// Simple OpenAI API check
|
|
318
|
+
const response = await fetch('https://api.openai.com/v1/models', {
|
|
319
|
+
method: 'GET',
|
|
320
|
+
headers: {
|
|
321
|
+
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
322
|
+
},
|
|
323
|
+
signal: AbortSignal.timeout(5000)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
status: response.ok ? 'healthy' : 'unhealthy',
|
|
328
|
+
responseTime: Date.now()
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
return { status: 'unhealthy', error: 'OpenAI API unavailable' }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function checkEmailServiceHealth() {
|
|
336
|
+
// Implement email service health check
|
|
337
|
+
return { status: 'healthy' }
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Production State Configuration
|
|
342
|
+
|
|
343
|
+
```yaml
|
|
344
|
+
# config.yml
|
|
345
|
+
state:
|
|
346
|
+
adapter: redis
|
|
347
|
+
host: ${REDIS_HOST:-localhost}
|
|
348
|
+
port: ${REDIS_PORT:-6379}
|
|
349
|
+
password: ${REDIS_PASSWORD}
|
|
350
|
+
db: ${REDIS_DB:-0}
|
|
351
|
+
ttl: ${STATE_TTL:-3600}
|
|
352
|
+
keyPrefix: ${APP_NAME:-motia}:state:
|
|
353
|
+
|
|
354
|
+
# Logging configuration
|
|
355
|
+
logging:
|
|
356
|
+
level: ${LOG_LEVEL:-info}
|
|
357
|
+
format: json
|
|
358
|
+
destination: stdout
|
|
359
|
+
|
|
360
|
+
# Security settings
|
|
361
|
+
security:
|
|
362
|
+
rateLimit:
|
|
363
|
+
windowMs: ${RATE_LIMIT_WINDOW_MS:-900000}
|
|
364
|
+
max: ${RATE_LIMIT_MAX_REQUESTS:-100}
|
|
365
|
+
cors:
|
|
366
|
+
origin: ${CORS_ORIGIN:-*}
|
|
367
|
+
credentials: true
|
|
368
|
+
helmet:
|
|
369
|
+
contentSecurityPolicy: true
|
|
370
|
+
hsts: true
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## CI/CD Pipeline
|
|
374
|
+
|
|
375
|
+
```yaml
|
|
376
|
+
# .github/workflows/deploy.yml
|
|
377
|
+
name: Deploy to Production
|
|
378
|
+
|
|
379
|
+
on:
|
|
380
|
+
push:
|
|
381
|
+
branches: [main]
|
|
382
|
+
pull_request:
|
|
383
|
+
branches: [main]
|
|
384
|
+
|
|
385
|
+
env:
|
|
386
|
+
NODE_VERSION: '18'
|
|
387
|
+
PYTHON_VERSION: '3.11'
|
|
388
|
+
|
|
389
|
+
jobs:
|
|
390
|
+
test:
|
|
391
|
+
runs-on: ubuntu-latest
|
|
392
|
+
|
|
393
|
+
services:
|
|
394
|
+
postgres:
|
|
395
|
+
image: postgres:15
|
|
396
|
+
env:
|
|
397
|
+
POSTGRES_PASSWORD: postgres
|
|
398
|
+
POSTGRES_DB: test_db
|
|
399
|
+
options: >-
|
|
400
|
+
--health-cmd pg_isready
|
|
401
|
+
--health-interval 10s
|
|
402
|
+
--health-timeout 5s
|
|
403
|
+
--health-retries 5
|
|
404
|
+
|
|
405
|
+
redis:
|
|
406
|
+
image: redis:7
|
|
407
|
+
options: >-
|
|
408
|
+
--health-cmd "redis-cli ping"
|
|
409
|
+
--health-interval 10s
|
|
410
|
+
--health-timeout 5s
|
|
411
|
+
--health-retries 5
|
|
412
|
+
|
|
413
|
+
steps:
|
|
414
|
+
- uses: actions/checkout@v4
|
|
415
|
+
|
|
416
|
+
- uses: actions/setup-node@v4
|
|
417
|
+
with:
|
|
418
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
419
|
+
cache: 'pnpm'
|
|
420
|
+
|
|
421
|
+
- uses: actions/setup-python@v4
|
|
422
|
+
with:
|
|
423
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
424
|
+
cache: 'pip'
|
|
425
|
+
|
|
426
|
+
- name: Install dependencies
|
|
427
|
+
run: |
|
|
428
|
+
npm install -g pnpm
|
|
429
|
+
pnpm install
|
|
430
|
+
pip install -r requirements.txt
|
|
431
|
+
|
|
432
|
+
- name: Run linting
|
|
433
|
+
run: |
|
|
434
|
+
pnpm lint
|
|
435
|
+
|
|
436
|
+
- name: Run tests
|
|
437
|
+
env:
|
|
438
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
|
|
439
|
+
REDIS_URL: redis://localhost:6379
|
|
440
|
+
JWT_SECRET: test-secret-key-for-testing-only
|
|
441
|
+
run: |
|
|
442
|
+
pnpm test
|
|
443
|
+
|
|
444
|
+
- name: Build application
|
|
445
|
+
run: |
|
|
446
|
+
pnpm build
|
|
447
|
+
|
|
448
|
+
deploy:
|
|
449
|
+
if: github.ref == 'refs/heads/main'
|
|
450
|
+
needs: test
|
|
451
|
+
runs-on: ubuntu-latest
|
|
452
|
+
|
|
453
|
+
steps:
|
|
454
|
+
- uses: actions/checkout@v4
|
|
455
|
+
|
|
456
|
+
- name: Deploy to production
|
|
457
|
+
env:
|
|
458
|
+
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
|
459
|
+
PRODUCTION_HOST: ${{ secrets.PRODUCTION_HOST }}
|
|
460
|
+
run: |
|
|
461
|
+
# Deploy using your preferred method
|
|
462
|
+
# This could be Docker deployment, Kubernetes, or cloud services
|
|
463
|
+
echo "Deploying to production..."
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Performance Optimization
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// steps/middleware/performance.ts
|
|
470
|
+
import { ApiMiddleware } from 'motia'
|
|
471
|
+
import compression from 'compression'
|
|
472
|
+
import helmet from 'helmet'
|
|
473
|
+
|
|
474
|
+
// Compression middleware
|
|
475
|
+
export const compressionMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
476
|
+
// Add compression for responses > 1KB
|
|
477
|
+
const response = await next()
|
|
478
|
+
|
|
479
|
+
if (response.body && typeof response.body === 'object') {
|
|
480
|
+
const bodySize = JSON.stringify(response.body).length
|
|
481
|
+
if (bodySize > 1024) {
|
|
482
|
+
response.headers = {
|
|
483
|
+
...response.headers,
|
|
484
|
+
'Content-Encoding': 'gzip'
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return response
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Security headers middleware
|
|
493
|
+
export const securityMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
494
|
+
const response = await next()
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
...response,
|
|
498
|
+
headers: {
|
|
499
|
+
...response.headers,
|
|
500
|
+
'X-Content-Type-Options': 'nosniff',
|
|
501
|
+
'X-Frame-Options': 'DENY',
|
|
502
|
+
'X-XSS-Protection': '1; mode=block',
|
|
503
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
504
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin'
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Request timing middleware
|
|
510
|
+
export const timingMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
511
|
+
const start = Date.now()
|
|
512
|
+
|
|
513
|
+
const response = await next()
|
|
514
|
+
|
|
515
|
+
const duration = Date.now() - start
|
|
516
|
+
|
|
517
|
+
ctx.logger.info('Request completed', {
|
|
518
|
+
method: req.method || 'unknown',
|
|
519
|
+
path: req.path,
|
|
520
|
+
duration,
|
|
521
|
+
status: response.status
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
...response,
|
|
526
|
+
headers: {
|
|
527
|
+
...response.headers,
|
|
528
|
+
'X-Response-Time': `${duration}ms`
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Error Handling & Logging
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// steps/middleware/error-handling.ts
|
|
538
|
+
import { ApiMiddleware } from 'motia'
|
|
539
|
+
import { env } from '../config/environment'
|
|
540
|
+
|
|
541
|
+
export const errorHandlingMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
542
|
+
try {
|
|
543
|
+
return await next()
|
|
544
|
+
} catch (error) {
|
|
545
|
+
ctx.logger.error('Unhandled error in API route', {
|
|
546
|
+
error: error.message,
|
|
547
|
+
stack: error.stack,
|
|
548
|
+
method: req.method,
|
|
549
|
+
path: req.path,
|
|
550
|
+
userId: req.user?.userId
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
// Don't expose internal errors in production
|
|
554
|
+
const message = env.NODE_ENV === 'production'
|
|
555
|
+
? 'Internal server error'
|
|
556
|
+
: error.message
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
status: 500,
|
|
560
|
+
body: { error: message }
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Monitoring & Alerting
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// steps/cron/system-monitoring.step.ts
|
|
570
|
+
import { CronConfig, Handlers } from 'motia'
|
|
571
|
+
|
|
572
|
+
export const config: CronConfig = {
|
|
573
|
+
type: 'cron',
|
|
574
|
+
name: 'SystemMonitoring',
|
|
575
|
+
description: 'Monitor system metrics and send alerts',
|
|
576
|
+
cron: '*/5 * * * *', // Every 5 minutes
|
|
577
|
+
emits: ['alert.system', 'metrics.collected'],
|
|
578
|
+
flows: ['monitoring']
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export const handler: Handlers['SystemMonitoring'] = async ({ emit, logger, state }) => {
|
|
582
|
+
try {
|
|
583
|
+
const metrics = await collectSystemMetrics()
|
|
584
|
+
|
|
585
|
+
// Store metrics
|
|
586
|
+
await state.set('metrics', new Date().toISOString(), metrics)
|
|
587
|
+
|
|
588
|
+
// Check thresholds and send alerts
|
|
589
|
+
const alerts = checkAlertThresholds(metrics)
|
|
590
|
+
|
|
591
|
+
for (const alert of alerts) {
|
|
592
|
+
await emit({
|
|
593
|
+
topic: 'alert.system',
|
|
594
|
+
data: {
|
|
595
|
+
severity: alert.severity,
|
|
596
|
+
metric: alert.metric,
|
|
597
|
+
value: alert.value,
|
|
598
|
+
threshold: alert.threshold,
|
|
599
|
+
timestamp: new Date().toISOString()
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
await emit({
|
|
605
|
+
topic: 'metrics.collected',
|
|
606
|
+
data: metrics
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
logger.info('System metrics collected', {
|
|
610
|
+
alertCount: alerts.length,
|
|
611
|
+
metrics: Object.keys(metrics)
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
} catch (error) {
|
|
615
|
+
logger.error('System monitoring failed', { error: error.message })
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function collectSystemMetrics() {
|
|
620
|
+
return {
|
|
621
|
+
memory: {
|
|
622
|
+
used: process.memoryUsage().heapUsed,
|
|
623
|
+
total: process.memoryUsage().heapTotal,
|
|
624
|
+
percentage: (process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100
|
|
625
|
+
},
|
|
626
|
+
cpu: {
|
|
627
|
+
usage: process.cpuUsage()
|
|
628
|
+
},
|
|
629
|
+
uptime: process.uptime(),
|
|
630
|
+
activeConnections: await getActiveConnectionCount(),
|
|
631
|
+
responseTime: await measureAverageResponseTime()
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function checkAlertThresholds(metrics: any) {
|
|
636
|
+
const alerts = []
|
|
637
|
+
|
|
638
|
+
if (metrics.memory.percentage > 85) {
|
|
639
|
+
alerts.push({
|
|
640
|
+
severity: 'warning',
|
|
641
|
+
metric: 'memory_usage',
|
|
642
|
+
value: metrics.memory.percentage,
|
|
643
|
+
threshold: 85
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (metrics.memory.percentage > 95) {
|
|
648
|
+
alerts.push({
|
|
649
|
+
severity: 'critical',
|
|
650
|
+
metric: 'memory_usage',
|
|
651
|
+
value: metrics.memory.percentage,
|
|
652
|
+
threshold: 95
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return alerts
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
This production deployment setup provides:
|
|
661
|
+
- Comprehensive environment configuration
|
|
662
|
+
- Docker containerization
|
|
663
|
+
- Health checks and monitoring
|
|
664
|
+
- CI/CD pipeline
|
|
665
|
+
- Performance optimization
|
|
666
|
+
- Security best practices
|
|
667
|
+
- Error handling and logging
|
|
668
|
+
- System monitoring and alerting
|