@jaypie/mcp 0.1.9 → 0.2.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/dist/datadog.d.ts +212 -0
- package/dist/index.js +1461 -6
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/prompts/Development_Process.md +57 -35
- package/prompts/Jaypie_CDK_Constructs_and_Patterns.md +143 -19
- package/prompts/Jaypie_Express_Package.md +408 -0
- package/prompts/Jaypie_Init_Express_on_Lambda.md +66 -38
- package/prompts/Jaypie_Init_Lambda_Package.md +202 -83
- package/prompts/Jaypie_Init_Project_Subpackage.md +21 -26
- package/prompts/Jaypie_Legacy_Patterns.md +4 -0
- package/prompts/Templates_CDK_Subpackage.md +113 -0
- package/prompts/Templates_Express_Subpackage.md +183 -0
- package/prompts/Templates_Project_Monorepo.md +326 -0
- package/prompts/Templates_Project_Subpackage.md +93 -0
- package/LICENSE.txt +0 -21
- package/prompts/Jaypie_Mongoose_Models_Package.md +0 -231
- package/prompts/Jaypie_Mongoose_with_Express_CRUD.md +0 -1000
- package/prompts/templates/cdk-subpackage/bin/cdk.ts +0 -11
- package/prompts/templates/cdk-subpackage/cdk.json +0 -19
- package/prompts/templates/cdk-subpackage/lib/cdk-app.ts +0 -41
- package/prompts/templates/cdk-subpackage/lib/cdk-infrastructure.ts +0 -15
- package/prompts/templates/express-subpackage/index.ts +0 -8
- package/prompts/templates/express-subpackage/src/app.ts +0 -18
- package/prompts/templates/express-subpackage/src/handler.config.ts +0 -44
- package/prompts/templates/express-subpackage/src/routes/resource/__tests__/resourceGet.route.spec.ts +0 -29
- package/prompts/templates/express-subpackage/src/routes/resource/resourceGet.route.ts +0 -22
- package/prompts/templates/express-subpackage/src/routes/resource.router.ts +0 -11
- package/prompts/templates/express-subpackage/src/types/express.ts +0 -9
- package/prompts/templates/project-monorepo/.vscode/settings.json +0 -72
- package/prompts/templates/project-monorepo/eslint.config.mjs +0 -1
- package/prompts/templates/project-monorepo/gitignore +0 -11
- package/prompts/templates/project-monorepo/package.json +0 -20
- package/prompts/templates/project-monorepo/tsconfig.base.json +0 -18
- package/prompts/templates/project-monorepo/tsconfig.json +0 -6
- package/prompts/templates/project-monorepo/vitest.workspace.js +0 -3
- package/prompts/templates/project-subpackage/package.json +0 -16
- package/prompts/templates/project-subpackage/tsconfig.json +0 -11
- package/prompts/templates/project-subpackage/vite.config.ts +0 -21
- package/prompts/templates/project-subpackage/vitest.config.ts +0 -7
- package/prompts/templates/project-subpackage/vitest.setup.ts +0 -6
|
@@ -33,10 +33,13 @@ new JaypieLambda(this, "MyFunction", {
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Features:
|
|
36
|
-
- Automatic Datadog integration when `DATADOG_API_KEY_ARN` exists
|
|
36
|
+
- Automatic Datadog integration when `DATADOG_API_KEY_ARN` or `CDK_ENV_DATADOG_API_KEY_ARN` exists
|
|
37
37
|
- Default environment variables from `PROJECT_*` settings
|
|
38
|
-
- Provisioned concurrency support
|
|
39
|
-
- Secrets management via `JaypieEnvSecret
|
|
38
|
+
- Provisioned concurrency support via `provisionedConcurrentExecutions`
|
|
39
|
+
- Secrets management via `secrets` array (JaypieEnvSecret[])
|
|
40
|
+
- Direct secret integration via `envSecrets` object
|
|
41
|
+
- Parameter Store/Secrets Manager layer via `paramsAndSecrets`
|
|
42
|
+
- VPC, security groups, filesystem, and all standard Lambda configuration options
|
|
40
43
|
|
|
41
44
|
### Queue-Lambda Patterns
|
|
42
45
|
|
|
@@ -45,18 +48,33 @@ Use `JaypieQueuedLambda` for SQS-triggered Lambdas:
|
|
|
45
48
|
new JaypieQueuedLambda(this, "Worker", {
|
|
46
49
|
code: "dist",
|
|
47
50
|
fifo: true,
|
|
48
|
-
batchSize: 10
|
|
51
|
+
batchSize: 10,
|
|
52
|
+
visibilityTimeout: Duration.seconds(900)
|
|
49
53
|
});
|
|
50
54
|
```
|
|
51
55
|
|
|
56
|
+
Features:
|
|
57
|
+
- Auto-creates SQS queue and connects to Lambda
|
|
58
|
+
- Implements both `lambda.IFunction` and `sqs.IQueue` interfaces
|
|
59
|
+
- Auto-injects `CDK_ENV_QUEUE_URL` environment variable
|
|
60
|
+
- Grants consume and send permissions to Lambda
|
|
61
|
+
|
|
52
62
|
Use `JaypieBucketQueuedLambda` for S3-triggered processing:
|
|
53
63
|
```typescript
|
|
54
64
|
new JaypieBucketQueuedLambda(this, "Processor", {
|
|
55
65
|
code: "dist",
|
|
56
|
-
bucketName: "my-bucket"
|
|
66
|
+
bucketName: "my-bucket",
|
|
67
|
+
bucketOptions: { versioned: true } // Optional S3 configuration
|
|
57
68
|
});
|
|
58
69
|
```
|
|
59
70
|
|
|
71
|
+
Features:
|
|
72
|
+
- Extends `JaypieQueuedLambda` with S3 bucket and event notifications
|
|
73
|
+
- Forces non-FIFO queue (S3 limitation)
|
|
74
|
+
- Auto-injects `CDK_ENV_BUCKET_NAME` environment variable
|
|
75
|
+
- Grants read/write permissions to Lambda
|
|
76
|
+
- Implements `s3.IBucket` interface
|
|
77
|
+
|
|
60
78
|
### API Gateway
|
|
61
79
|
|
|
62
80
|
Use `JaypieApiGateway` for REST APIs with custom domains:
|
|
@@ -101,22 +119,37 @@ new JaypieEnvSecret(this, "API_KEY");
|
|
|
101
119
|
// Explicit configuration
|
|
102
120
|
new JaypieEnvSecret(this, "ApiKey", {
|
|
103
121
|
envKey: "API_KEY",
|
|
104
|
-
provider: true, // Exports for other stacks
|
|
105
|
-
consumer: false
|
|
122
|
+
provider: true, // Exports for other stacks (default: PROJECT_ENV=sandbox)
|
|
123
|
+
consumer: false, // Imports from provider stack (default: PROJECT_ENV=personal)
|
|
124
|
+
value: "secret-value", // Direct value (alternative to envKey)
|
|
125
|
+
generateSecretString: {}, // Auto-generate secret
|
|
106
126
|
});
|
|
107
127
|
```
|
|
108
128
|
|
|
129
|
+
Provider/consumer pattern:
|
|
130
|
+
- Provider stacks (sandbox) create and export secrets
|
|
131
|
+
- Consumer stacks (personal/ephemeral) import secrets by name
|
|
132
|
+
- Export name format: `env-${PROJECT_ENV}-${PROJECT_KEY}-${id}`
|
|
133
|
+
|
|
109
134
|
### Web Hosting
|
|
110
135
|
|
|
111
136
|
Use `JaypieWebDeploymentBucket` for static sites:
|
|
112
137
|
```typescript
|
|
113
138
|
new JaypieWebDeploymentBucket(this, "WebSite", {
|
|
114
139
|
host: "www.example.com",
|
|
115
|
-
zone: "example.com"
|
|
140
|
+
zone: "example.com",
|
|
141
|
+
certificate: true // Creates new cert; can pass ICertificate or false
|
|
116
142
|
});
|
|
117
143
|
```
|
|
118
144
|
|
|
119
|
-
|
|
145
|
+
Features:
|
|
146
|
+
- Creates S3 bucket with website hosting enabled
|
|
147
|
+
- Creates CloudFront distribution with SSL certificate
|
|
148
|
+
- Creates Route53 DNS records
|
|
149
|
+
- Auto-creates GitHub deployment role when `CDK_ENV_REPO` is set
|
|
150
|
+
- Production environments get optimized caching (index.html excluded)
|
|
151
|
+
- Implements `s3.IBucket` interface
|
|
152
|
+
- Can use `CDK_ENV_WEB_HOST` or `CDK_ENV_WEB_SUBDOMAIN` + hosted zone
|
|
120
153
|
|
|
121
154
|
### Hosted Zones
|
|
122
155
|
|
|
@@ -130,32 +163,123 @@ new JaypieHostedZone(this, "Zone", {
|
|
|
130
163
|
## Environment Variables
|
|
131
164
|
|
|
132
165
|
Configure constructs via environment:
|
|
133
|
-
|
|
166
|
+
|
|
167
|
+
### API Configuration
|
|
168
|
+
- `CDK_ENV_API_HOST_NAME`: Full API domain name
|
|
169
|
+
- `CDK_ENV_API_SUBDOMAIN`: API subdomain (combined with CDK_ENV_API_HOSTED_ZONE)
|
|
134
170
|
- `CDK_ENV_API_HOSTED_ZONE`: API hosted zone
|
|
135
|
-
|
|
171
|
+
|
|
172
|
+
### Web Configuration
|
|
173
|
+
- `CDK_ENV_WEB_HOST`: Full web domain name
|
|
174
|
+
- `CDK_ENV_WEB_SUBDOMAIN`: Web subdomain (combined with CDK_ENV_WEB_HOSTED_ZONE)
|
|
136
175
|
- `CDK_ENV_WEB_HOSTED_ZONE`: Web hosted zone
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `
|
|
176
|
+
|
|
177
|
+
### General DNS
|
|
178
|
+
- `CDK_ENV_HOSTED_ZONE`: Fallback hosted zone (used by API and Web if specific zones not set)
|
|
179
|
+
|
|
180
|
+
### Deployment
|
|
181
|
+
- `CDK_ENV_REPO`: GitHub repository for deployment roles (format: owner/repo)
|
|
182
|
+
|
|
183
|
+
### Datadog Integration
|
|
184
|
+
- `DATADOG_API_KEY_ARN`: Datadog API key secret ARN (primary)
|
|
185
|
+
- `CDK_ENV_DATADOG_API_KEY_ARN`: Datadog API key secret ARN (fallback)
|
|
186
|
+
- `CDK_ENV_DATADOG_ROLE_ARN`: Datadog IAM role ARN for extended permissions
|
|
187
|
+
|
|
188
|
+
### Project Configuration
|
|
189
|
+
- `PROJECT_ENV`: Environment name (sandbox, production, personal, etc.)
|
|
140
190
|
- `PROJECT_KEY`: Project identifier
|
|
141
191
|
- `PROJECT_SERVICE`: Service name
|
|
142
192
|
- `PROJECT_SPONSOR`: Cost allocation tag
|
|
193
|
+
- `PROJECT_NONCE`: Unique identifier for ephemeral builds
|
|
194
|
+
|
|
195
|
+
### Infrastructure Tracking
|
|
196
|
+
- `CDK_ENV_INFRASTRUCTURE_STACK_SHA`: Git SHA for infrastructure stack tagging
|
|
197
|
+
|
|
198
|
+
### Auto-Injected (Set by Constructs)
|
|
199
|
+
- `CDK_ENV_BUCKET_NAME`: S3 bucket name (set by JaypieBucketQueuedLambda)
|
|
200
|
+
- `CDK_ENV_QUEUE_URL`: SQS queue URL (set by JaypieQueuedLambda)
|
|
143
201
|
|
|
144
202
|
## Helper Functions
|
|
145
203
|
|
|
146
204
|
Use helper utilities:
|
|
147
205
|
```typescript
|
|
148
|
-
import { constructStackName, constructEnvName, isEnv } from "@jaypie/constructs
|
|
206
|
+
import { constructStackName, constructEnvName, isEnv } from "@jaypie/constructs";
|
|
149
207
|
|
|
150
208
|
const stackName = constructStackName("app"); // project-env-app
|
|
151
209
|
const resourceName = constructEnvName("bucket"); // project-env-bucket
|
|
152
210
|
const isProduction = isEnv("production");
|
|
153
211
|
```
|
|
154
212
|
|
|
213
|
+
## Additional Helper Functions
|
|
214
|
+
|
|
215
|
+
Available from `@jaypie/constructs`:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import {
|
|
219
|
+
isProductionEnv,
|
|
220
|
+
isSandboxEnv,
|
|
221
|
+
jaypieLambdaEnv,
|
|
222
|
+
constructTagger,
|
|
223
|
+
resolveHostedZone,
|
|
224
|
+
resolveDatadogLayers,
|
|
225
|
+
resolveDatadogForwarderFunction,
|
|
226
|
+
resolveDatadogLoggingDestination,
|
|
227
|
+
resolveParamsAndSecrets,
|
|
228
|
+
extendDatadogRole,
|
|
229
|
+
envHostname,
|
|
230
|
+
isValidHostname,
|
|
231
|
+
isValidSubdomain,
|
|
232
|
+
mergeDomain,
|
|
233
|
+
} from "@jaypie/constructs";
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Common usage:
|
|
237
|
+
- `isProductionEnv()`: Returns true if PROJECT_ENV is production
|
|
238
|
+
- `isSandboxEnv()`: Returns true if PROJECT_ENV is sandbox
|
|
239
|
+
- `jaypieLambdaEnv({ initialEnvironment })`: Merges PROJECT_* vars into Lambda env
|
|
240
|
+
- `resolveHostedZone(scope, { zone })`: Gets IHostedZone from string or object
|
|
241
|
+
- `extendDatadogRole(lambda)`: Adds Datadog IAM permissions when CDK_ENV_DATADOG_ROLE_ARN set
|
|
242
|
+
|
|
243
|
+
## Additional Constructs
|
|
244
|
+
|
|
245
|
+
Other constructs available but not commonly used:
|
|
246
|
+
|
|
247
|
+
### Base and Infrastructure
|
|
248
|
+
- `JaypieStack`: Base stack with standard tagging and configuration
|
|
249
|
+
|
|
250
|
+
### Specialized Secrets
|
|
251
|
+
- `JaypieDatadogSecret`: Datadog API key secret management
|
|
252
|
+
- `JaypieMongoDbSecret`: MongoDB connection string secret
|
|
253
|
+
- `JaypieOpenAiSecret`: OpenAI API key secret
|
|
254
|
+
- `JaypieTraceSigningKeySecret`: Trace signing key secret
|
|
255
|
+
|
|
256
|
+
### DNS and Networking
|
|
257
|
+
- `JaypieDnsRecord`: Create individual DNS records in hosted zones
|
|
258
|
+
|
|
259
|
+
### Deployment and CI/CD
|
|
260
|
+
- `JaypieGitHubDeployRole`: GitHub Actions OIDC deployment role
|
|
261
|
+
|
|
262
|
+
### Event-Driven
|
|
263
|
+
- `JaypieEventsRule`: EventBridge rules with standard configuration
|
|
264
|
+
|
|
265
|
+
### Advanced Features
|
|
266
|
+
- `JaypieNextJs`: Next.js application deployment (uses cdk-nextjs-standalone)
|
|
267
|
+
- `JaypieDatadogForwarder`: Datadog log forwarder Lambda setup
|
|
268
|
+
- `JaypieOrganizationTrail`: CloudTrail organization-wide trail
|
|
269
|
+
- `JaypieSsoPermissions`: AWS IAM Identity Center permission sets
|
|
270
|
+
- `JaypieSsoSyncApplication`: SSO sync application for Google Workspace
|
|
271
|
+
- `JaypieAccountLoggingBucket`: Account-level centralized logging bucket
|
|
272
|
+
- `JaypieDatadogBucket`: Datadog-specific S3 bucket
|
|
273
|
+
- `JaypieStaticWebBucket`: Static web bucket (simpler than JaypieWebDeploymentBucket)
|
|
274
|
+
- `JaypieDistribution`: CloudFront distribution construct
|
|
275
|
+
|
|
155
276
|
## Tagging Strategy
|
|
156
277
|
|
|
157
278
|
Constructs apply standard tags:
|
|
158
|
-
- `role`: Resource role (api, processing, networking, hosting)
|
|
159
|
-
- `vendor`: External service provider
|
|
160
|
-
- `service`: Service category
|
|
161
|
-
- `sponsor`: Cost allocation
|
|
279
|
+
- `role`: Resource role (api, processing, networking, hosting, deploy, monitoring, security, storage, stack)
|
|
280
|
+
- `vendor`: External service provider (auth0, datadog, mongodb, openai, knowtrace)
|
|
281
|
+
- `service`: Service category (datadog, infrastructure, libraries, sso, trace)
|
|
282
|
+
- `sponsor`: Cost allocation
|
|
283
|
+
- `project`: Project identifier (from PROJECT_KEY)
|
|
284
|
+
- `env`: Environment name (from PROJECT_ENV)
|
|
285
|
+
- `stackSha`: Git SHA for infrastructure stacks (from CDK_ENV_INFRASTRUCTURE_STACK_SHA)
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Complete guide to using Jaypie Express features including expressHandler, CORS, lifecycle hooks, and pre-built routes
|
|
3
|
+
globs: packages/express/**
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jaypie Express Package
|
|
7
|
+
|
|
8
|
+
Jaypie provides Express utilities through `@jaypie/express` (also available via `jaypie`). The main export is `expressHandler`, a function that wraps Express route handlers to add error handling, logging, lifecycle hooks, and automatic response formatting.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install jaypie
|
|
14
|
+
# or
|
|
15
|
+
npm install @jaypie/express
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## expressHandler
|
|
19
|
+
|
|
20
|
+
Wraps Express route handlers with error handling, logging, and lifecycle management.
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { expressHandler } from "jaypie";
|
|
26
|
+
import type { Request, Response } from "express";
|
|
27
|
+
|
|
28
|
+
const myRoute = expressHandler(async (req: Request, res: Response) => {
|
|
29
|
+
return { message: "Hello, World!" };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Use in Express
|
|
33
|
+
app.get("/hello", myRoute);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Return Value Handling
|
|
37
|
+
|
|
38
|
+
expressHandler automatically formats responses based on return values:
|
|
39
|
+
|
|
40
|
+
| Return Value | HTTP Status | Response |
|
|
41
|
+
|--------------|-------------|----------|
|
|
42
|
+
| Object | 200 | JSON body |
|
|
43
|
+
| Array | 200 | JSON body |
|
|
44
|
+
| String (JSON) | 200 | Parsed JSON |
|
|
45
|
+
| String (other) | 200 | Text body |
|
|
46
|
+
| Number | 200 | Sent via `res.send()` |
|
|
47
|
+
| `true` | 201 Created | Empty |
|
|
48
|
+
| `null`, `undefined`, `false` | 204 No Content | Empty |
|
|
49
|
+
| Object with `.json()` method | 200 | Result of `.json()` |
|
|
50
|
+
|
|
51
|
+
**Note:** If you call `res.json()`, `res.send()`, or `res.end()` directly in your handler, expressHandler will log a warning but respect your call. Prefer using return values instead.
|
|
52
|
+
|
|
53
|
+
### Options
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { expressHandler } from "jaypie";
|
|
57
|
+
import type { ExpressHandlerOptions } from "jaypie";
|
|
58
|
+
|
|
59
|
+
const options: ExpressHandlerOptions = {
|
|
60
|
+
name: "myHandler", // Handler name for logging
|
|
61
|
+
chaos: "low", // Chaos testing level
|
|
62
|
+
unavailable: false, // Return 503 if true
|
|
63
|
+
setup: [], // Setup function(s)
|
|
64
|
+
teardown: [], // Teardown function(s)
|
|
65
|
+
validate: [], // Validation function(s)
|
|
66
|
+
locals: {}, // Values to set on req.locals
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handler = expressHandler(async (req, res) => {
|
|
70
|
+
return { success: true };
|
|
71
|
+
}, options);
|
|
72
|
+
|
|
73
|
+
// Alternative: options first
|
|
74
|
+
const handler2 = expressHandler(options, async (req, res) => {
|
|
75
|
+
return { success: true };
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Lifecycle Hooks
|
|
80
|
+
|
|
81
|
+
Lifecycle hooks execute in this order:
|
|
82
|
+
1. Setup functions (in array order)
|
|
83
|
+
2. Locals functions (in object key order, after all setup)
|
|
84
|
+
3. Validate functions (in array order)
|
|
85
|
+
4. Main handler
|
|
86
|
+
5. Teardown functions (always run, even on error)
|
|
87
|
+
|
|
88
|
+
### Setup Functions
|
|
89
|
+
|
|
90
|
+
Run before validation and the main handler. Use for initialization, authentication checks, or setting up request context.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { expressHandler } from "jaypie";
|
|
94
|
+
import type { JaypieHandlerSetup } from "jaypie";
|
|
95
|
+
|
|
96
|
+
const authenticateUser: JaypieHandlerSetup = async (req, res) => {
|
|
97
|
+
const token = req.headers.authorization;
|
|
98
|
+
// Validate token, throw UnauthorizedError if invalid
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const loadTenant: JaypieHandlerSetup = async (req, res) => {
|
|
102
|
+
req.locals.tenant = await getTenant(req.params.tenantId);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handler = expressHandler(
|
|
106
|
+
async (req, res) => {
|
|
107
|
+
// req.locals.tenant is available here
|
|
108
|
+
return { tenant: req.locals.tenant };
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
setup: [authenticateUser, loadTenant],
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Teardown Functions
|
|
117
|
+
|
|
118
|
+
Run after the main handler completes. Teardown functions execute regardless of success or error, making them suitable for cleanup operations.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import type { JaypieHandlerTeardown } from "jaypie";
|
|
122
|
+
|
|
123
|
+
const closeConnection: JaypieHandlerTeardown = async (req, res) => {
|
|
124
|
+
await req.locals.dbConnection?.close();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handler = expressHandler(
|
|
128
|
+
async (req, res) => {
|
|
129
|
+
req.locals.dbConnection = await openConnection();
|
|
130
|
+
return await doWork(req.locals.dbConnection);
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
teardown: closeConnection,
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Validation Functions
|
|
139
|
+
|
|
140
|
+
Run before the main handler. Return `true` to continue or `false`/throw to reject.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { ForbiddenError } from "jaypie";
|
|
144
|
+
import type { JaypieHandlerValidate } from "jaypie";
|
|
145
|
+
|
|
146
|
+
const requireAdmin: JaypieHandlerValidate = (req, res) => {
|
|
147
|
+
if (!req.locals.user?.isAdmin) {
|
|
148
|
+
throw new ForbiddenError();
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const validateBody: JaypieHandlerValidate = (req, res) => {
|
|
154
|
+
return req.body?.email && req.body?.name;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handler = expressHandler(
|
|
158
|
+
async (req, res) => {
|
|
159
|
+
// Only runs if user is admin and body is valid
|
|
160
|
+
return { success: true };
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
validate: [requireAdmin, validateBody],
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Locals
|
|
169
|
+
|
|
170
|
+
Set values on `req.locals`. Values can be static or functions that receive `(req, res)`. Locals functions are called AFTER setup functions.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import type { ExpressHandlerLocals } from "jaypie";
|
|
174
|
+
|
|
175
|
+
const getUser: ExpressHandlerLocals = async (req, res) => {
|
|
176
|
+
return await User.findById(req.params.userId);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handler = expressHandler(
|
|
180
|
+
async (req, res) => {
|
|
181
|
+
// Access via req.locals
|
|
182
|
+
console.log(req.locals.apiVersion); // "v1"
|
|
183
|
+
console.log(req.locals.user); // User object
|
|
184
|
+
return req.locals.user;
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
locals: {
|
|
188
|
+
apiVersion: "v1", // Static value
|
|
189
|
+
user: getUser, // Function called after setup
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## CORS Helper
|
|
196
|
+
|
|
197
|
+
Configures CORS middleware using the `cors` npm package with automatic origin validation.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { cors } from "jaypie";
|
|
201
|
+
import type { CorsConfig } from "jaypie";
|
|
202
|
+
|
|
203
|
+
// Default: uses BASE_URL or PROJECT_BASE_URL env vars
|
|
204
|
+
app.use(cors());
|
|
205
|
+
|
|
206
|
+
// Wildcard origin
|
|
207
|
+
app.use(cors({ origin: "*" }));
|
|
208
|
+
|
|
209
|
+
// Specific origin
|
|
210
|
+
app.use(cors({ origin: "https://example.com" }));
|
|
211
|
+
|
|
212
|
+
// Multiple origins
|
|
213
|
+
app.use(cors({ origin: ["https://example.com", "https://app.example.com"] }));
|
|
214
|
+
|
|
215
|
+
// Custom configuration
|
|
216
|
+
const corsConfig: CorsConfig = {
|
|
217
|
+
origin: "https://api.example.com",
|
|
218
|
+
overrides: {
|
|
219
|
+
// Additional options passed to the cors package
|
|
220
|
+
credentials: true,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
app.use(cors(corsConfig));
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Environment variables:
|
|
227
|
+
- `BASE_URL` or `PROJECT_BASE_URL`: Default allowed origins
|
|
228
|
+
- `PROJECT_ENV=sandbox` or `PROJECT_SANDBOX_MODE=true`: Allows localhost origins (including ports)
|
|
229
|
+
|
|
230
|
+
## Pre-built Routes
|
|
231
|
+
|
|
232
|
+
Ready-to-use route handlers for common responses.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import {
|
|
236
|
+
badRequestRoute, // 400 Bad Request
|
|
237
|
+
echoRoute, // 200 with request echo
|
|
238
|
+
forbiddenRoute, // 403 Forbidden
|
|
239
|
+
goneRoute, // 410 Gone
|
|
240
|
+
methodNotAllowedRoute, // 405 Method Not Allowed
|
|
241
|
+
noContentRoute, // 204 No Content
|
|
242
|
+
notFoundRoute, // 404 Not Found
|
|
243
|
+
notImplementedRoute, // 501 Not Implemented
|
|
244
|
+
} from "jaypie";
|
|
245
|
+
|
|
246
|
+
// Use as catch-all or placeholder routes
|
|
247
|
+
app.all("/deprecated/*", goneRoute);
|
|
248
|
+
app.use("*", notFoundRoute);
|
|
249
|
+
|
|
250
|
+
// Echo route for debugging
|
|
251
|
+
app.get("/debug/echo", echoRoute);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## HTTP Code Handler
|
|
255
|
+
|
|
256
|
+
Create custom HTTP status code handlers.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { expressHttpCodeHandler, HTTP } from "jaypie";
|
|
260
|
+
|
|
261
|
+
// Returns 200 OK with empty body
|
|
262
|
+
const okRoute = expressHttpCodeHandler(HTTP.CODE.OK);
|
|
263
|
+
|
|
264
|
+
// Returns 202 Accepted
|
|
265
|
+
const acceptedRoute = expressHttpCodeHandler(202, { name: "accepted" });
|
|
266
|
+
|
|
267
|
+
app.post("/jobs", acceptedRoute);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Error Handling
|
|
271
|
+
|
|
272
|
+
Throw Jaypie errors for proper HTTP responses.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import {
|
|
276
|
+
expressHandler,
|
|
277
|
+
BadRequestError,
|
|
278
|
+
NotFoundError,
|
|
279
|
+
UnauthorizedError,
|
|
280
|
+
ForbiddenError,
|
|
281
|
+
InternalError,
|
|
282
|
+
log,
|
|
283
|
+
} from "jaypie";
|
|
284
|
+
|
|
285
|
+
const handler = expressHandler(async (req, res) => {
|
|
286
|
+
const item = await findItem(req.params.id);
|
|
287
|
+
|
|
288
|
+
if (!item) {
|
|
289
|
+
log.warn("Item not found");
|
|
290
|
+
throw new NotFoundError();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!canAccess(req.user, item)) {
|
|
294
|
+
throw new ForbiddenError();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return item;
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Errors return JSON:API compliant error responses:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"errors": [{
|
|
306
|
+
"status": 404,
|
|
307
|
+
"title": "Not Found",
|
|
308
|
+
"detail": "The requested resource was not found"
|
|
309
|
+
}]
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## TypeScript Types
|
|
314
|
+
|
|
315
|
+
All lifecycle function types are exported for type safety:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import type {
|
|
319
|
+
ExpressHandlerOptions,
|
|
320
|
+
ExpressHandlerLocals,
|
|
321
|
+
JaypieHandlerSetup,
|
|
322
|
+
JaypieHandlerTeardown,
|
|
323
|
+
JaypieHandlerValidate,
|
|
324
|
+
CorsConfig,
|
|
325
|
+
} from "jaypie";
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Complete Example
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import express from "express";
|
|
332
|
+
import {
|
|
333
|
+
cors,
|
|
334
|
+
expressHandler,
|
|
335
|
+
notFoundRoute,
|
|
336
|
+
NotFoundError,
|
|
337
|
+
ForbiddenError,
|
|
338
|
+
UnauthorizedError,
|
|
339
|
+
log,
|
|
340
|
+
} from "jaypie";
|
|
341
|
+
import type {
|
|
342
|
+
JaypieHandlerSetup,
|
|
343
|
+
JaypieHandlerValidate,
|
|
344
|
+
ExpressHandlerLocals,
|
|
345
|
+
} from "jaypie";
|
|
346
|
+
|
|
347
|
+
const app = express();
|
|
348
|
+
app.use(express.json());
|
|
349
|
+
app.use(cors());
|
|
350
|
+
|
|
351
|
+
// Lifecycle functions
|
|
352
|
+
const authenticate: JaypieHandlerSetup = async (req, res) => {
|
|
353
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
354
|
+
if (!token) throw new UnauthorizedError();
|
|
355
|
+
req.locals.user = await verifyToken(token);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const requireOwner: JaypieHandlerValidate = (req, res) => {
|
|
359
|
+
return req.locals.resource?.ownerId === req.locals.user?.id;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const loadResource: ExpressHandlerLocals = async (req, res) => {
|
|
363
|
+
const resource = await Resource.findById(req.params.id);
|
|
364
|
+
if (!resource) throw new NotFoundError();
|
|
365
|
+
return resource;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Route handler
|
|
369
|
+
const updateResource = expressHandler(
|
|
370
|
+
async (req, res) => {
|
|
371
|
+
const { resource, user } = req.locals;
|
|
372
|
+
|
|
373
|
+
resource.name = req.body.name;
|
|
374
|
+
resource.updatedBy = user.id;
|
|
375
|
+
await resource.save();
|
|
376
|
+
|
|
377
|
+
log.trace("Resource updated");
|
|
378
|
+
return resource;
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "updateResource",
|
|
382
|
+
setup: authenticate,
|
|
383
|
+
validate: requireOwner,
|
|
384
|
+
locals: {
|
|
385
|
+
resource: loadResource,
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
app.put("/resources/:id", updateResource);
|
|
391
|
+
app.use("*", notFoundRoute);
|
|
392
|
+
|
|
393
|
+
export default app;
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Response Headers
|
|
397
|
+
|
|
398
|
+
expressHandler automatically sets these headers:
|
|
399
|
+
- `X-Powered-By: @jaypie/express` (always set, overrides Express default)
|
|
400
|
+
- `X-Project-Handler: {name}` (when name option is provided)
|
|
401
|
+
- `X-Project-Invocation: {uuid}` (request tracking ID, always set)
|
|
402
|
+
- `X-Project-Environment: {env}` (when PROJECT_ENV is set)
|
|
403
|
+
- `X-Project-Key: {key}` (when PROJECT_KEY is set)
|
|
404
|
+
- `X-Project-Version: {version}` (when PROJECT_VERSION is set or version option provided)
|
|
405
|
+
|
|
406
|
+
## Datadog Integration
|
|
407
|
+
|
|
408
|
+
When Datadog environment variables are configured, expressHandler automatically submits metrics for each request including status code and path.
|