@ajentify/routed-api 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/README.md +197 -0
- package/SKILL.md +163 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/routed-api.d.ts +162 -0
- package/dist/routed-api.d.ts.map +1 -0
- package/dist/routed-api.js +518 -0
- package/dist/routed-api.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# @ajentify/routed-api
|
|
2
|
+
|
|
3
|
+
A reusable CDK construct that takes a `routes.json` + a folder of TypeScript
|
|
4
|
+
handler files and gives you:
|
|
5
|
+
|
|
6
|
+
- Lambdas per route **or** a single-lambda router (configurable via `deploymentMode`)
|
|
7
|
+
- DynamoDB permissions wired up declaratively
|
|
8
|
+
- An HTTP API (API Gateway v2) with CORS
|
|
9
|
+
- An optional Route53 + ACM custom domain
|
|
10
|
+
- Everything else in your CDK stack stays exactly as you write it
|
|
11
|
+
|
|
12
|
+
No build step, no codegen, no separate deploy tool. Drop it into any CDK stack.
|
|
13
|
+
|
|
14
|
+
## Why HTTP API v2?
|
|
15
|
+
|
|
16
|
+
HTTP APIs are ~70% cheaper than REST APIs, have native CORS, and are perfectly
|
|
17
|
+
adequate for typical app backends. The construct hides API-Gateway-isms behind a
|
|
18
|
+
single `routes.json`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @ajentify/routed-api
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Peer dependencies (your CDK app must already have these):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install aws-cdk-lib constructs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Minimum usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import * as cdk from 'aws-cdk-lib';
|
|
36
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
37
|
+
import * as path from 'node:path';
|
|
38
|
+
import { RoutedApi } from '@ajentify/routed-api';
|
|
39
|
+
|
|
40
|
+
export class MyStack extends cdk.Stack {
|
|
41
|
+
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
42
|
+
super(scope, id, props);
|
|
43
|
+
|
|
44
|
+
const todosTable = new dynamodb.Table(this, 'TodosTable', {
|
|
45
|
+
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
|
|
46
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const api = new RoutedApi(this, 'TodoApi', {
|
|
50
|
+
routesFile: path.join(__dirname, 'routes.json'),
|
|
51
|
+
lambdaSrcDir: path.join(__dirname, 'lambda'),
|
|
52
|
+
tables: { todos: todosTable },
|
|
53
|
+
cors: { origins: ['http://localhost:3000'] },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
new cdk.CfnOutput(this, 'ApiUrl', { value: api.url });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## routes.json
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"defaultMemoryMb": 256,
|
|
66
|
+
"defaultTimeoutSeconds": 15,
|
|
67
|
+
"defaultEnvironment": { "LOG_LEVEL": "info" },
|
|
68
|
+
"defaultTableAccess": { "todos": "readWrite" },
|
|
69
|
+
|
|
70
|
+
"routes": [
|
|
71
|
+
{
|
|
72
|
+
"path": "/todos",
|
|
73
|
+
"method": "GET",
|
|
74
|
+
"handler": "todos/list.ts",
|
|
75
|
+
"tables": { "todos": "read" }
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"path": "/todos",
|
|
79
|
+
"method": "POST",
|
|
80
|
+
"handler": "todos/create.ts"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "/todos/{id}",
|
|
84
|
+
"method": "DELETE",
|
|
85
|
+
"handler": "todos/delete.ts"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Route fields
|
|
92
|
+
|
|
93
|
+
| Field | Type | Default |
|
|
94
|
+
| ---------------- | ---------------------------------------------------------------- | ---------------------------------------- |
|
|
95
|
+
| `path` | string (e.g. `"/todos"`, `"/todos/{id}"`) | required |
|
|
96
|
+
| `method` | `"GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE" \| ... \| "ANY"` | required |
|
|
97
|
+
| `handler` | path to `.ts` file, relative to `lambdaSrcDir` | required |
|
|
98
|
+
| `export` | exported function name in that file | `"handler"` |
|
|
99
|
+
| `memoryMb` | number | `defaultMemoryMb` |
|
|
100
|
+
| `timeoutSeconds` | number | `defaultTimeoutSeconds` |
|
|
101
|
+
| `environment` | `Record<string,string>` | `{}` |
|
|
102
|
+
| `tables` | `string[]` or `Record<string, "read"\|"write"\|"readWrite">` | inherits `defaultTableAccess` from props |
|
|
103
|
+
|
|
104
|
+
### Default table access
|
|
105
|
+
|
|
106
|
+
Pass one of:
|
|
107
|
+
|
|
108
|
+
- `"allReadWrite"` — every route can read & write every table you pass in **(default)**
|
|
109
|
+
- `"allRead"` — every route gets read-only on every table
|
|
110
|
+
- `"none"` — no table access unless the route asks
|
|
111
|
+
- `Record<string, "read"|"write"|"readWrite">` — explicit per-table defaults
|
|
112
|
+
|
|
113
|
+
For every grant, the lambda also receives `${NAME}_TABLE_NAME` as an env var
|
|
114
|
+
(e.g. `TODOS_TABLE_NAME`).
|
|
115
|
+
|
|
116
|
+
### CORS
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
cors: {
|
|
120
|
+
origins: ['http://localhost:3000', 'https://app.example.com'],
|
|
121
|
+
// methods, headers, allowCredentials, maxAgeSeconds are all optional
|
|
122
|
+
}
|
|
123
|
+
// or pass false to disable preflight entirely
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Sensible defaults are applied — `Content-Type`, `Authorization`, `X-Api-Key`,
|
|
127
|
+
`X-Amz-Date`, `X-Amz-Security-Token`, `X-Requested-With`.
|
|
128
|
+
|
|
129
|
+
### Custom domain (Route53 + ACM)
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
domain: {
|
|
133
|
+
recordName: 'api.example.com',
|
|
134
|
+
hostedZoneName: 'example.com',
|
|
135
|
+
certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abcd',
|
|
136
|
+
// hostedZoneId optional — if omitted the zone is looked up by name
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> ACM cert must be in the **same region** as the API (HTTP API uses regional
|
|
141
|
+
> endpoints, not edge-optimized). If you need an edge cert, you'd typically
|
|
142
|
+
> front the API with CloudFront instead.
|
|
143
|
+
|
|
144
|
+
## Handlers
|
|
145
|
+
|
|
146
|
+
Each handler is a normal `APIGatewayProxyHandlerV2`. The construct uses CDK's
|
|
147
|
+
`NodejsFunction`, so esbuild compiles your TypeScript automatically — no build
|
|
148
|
+
step needed.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// lambda/todos/list.ts
|
|
152
|
+
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
153
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
154
|
+
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';
|
|
155
|
+
|
|
156
|
+
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
157
|
+
|
|
158
|
+
export const handler: APIGatewayProxyHandlerV2 = async () => {
|
|
159
|
+
const out = await ddb.send(new ScanCommand({ TableName: process.env.TODOS_TABLE_NAME! }));
|
|
160
|
+
return {
|
|
161
|
+
statusCode: 200,
|
|
162
|
+
headers: { 'content-type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({ items: out.Items ?? [] }),
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Your CDK app should have `@aws-sdk/*` packages and `aws-lambda` types installed:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install --save-dev @types/aws-lambda
|
|
172
|
+
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
(`@aws-sdk/*` is `external` in the bundle — Lambda's Node 20 runtime already
|
|
176
|
+
includes it.)
|
|
177
|
+
|
|
178
|
+
## What about non-API stuff?
|
|
179
|
+
|
|
180
|
+
Just keep writing CDK normally. `RoutedApi` is one block in your stack — add
|
|
181
|
+
SES identities, processing lambdas, EventBridge rules, S3 buckets, whatever,
|
|
182
|
+
before or after it.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
new RoutedApi(this, 'Api', { ... });
|
|
186
|
+
|
|
187
|
+
new lambda.Function(this, 'BackgroundProcessor', { ... }); // totally fine
|
|
188
|
+
new ses.EmailIdentity(this, 'EmailIdentity', { ... }); // also fine
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Worked example
|
|
192
|
+
|
|
193
|
+
See `example/` in this folder:
|
|
194
|
+
|
|
195
|
+
- `example/routes.json` — the routes file
|
|
196
|
+
- `example/lambda/todos/{list,create,delete}.ts` — handler files
|
|
197
|
+
- `example/example-stack.ts` — the CDK stack
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @ajentify/routed-api
|
|
2
|
+
|
|
3
|
+
AWS CDK construct that wires HTTP API Gateway routes to Lambda handlers from a single declaration. Define your routes in JSON or inline, point at a directory of handler files, and the construct creates the API, Lambdas, CORS, DynamoDB grants, and optional Route53 custom domain.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ajentify/routed-api
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies: `aws-cdk-lib` >= 2.150.0, `constructs` >= 10.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { RoutedApi } from '@ajentify/routed-api';
|
|
17
|
+
|
|
18
|
+
const api = new RoutedApi(this, 'MyApi', {
|
|
19
|
+
routesFile: path.join(__dirname, 'routes.json'),
|
|
20
|
+
lambdaSrcDir: path.join(__dirname, 'lambda'),
|
|
21
|
+
tables: { todos: todosTable },
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Deployment modes
|
|
26
|
+
|
|
27
|
+
Set `deploymentMode` on the construct props.
|
|
28
|
+
|
|
29
|
+
- `"lambda-per-route"` (default) -- one Lambda per route. Each handler is bundled and deployed independently. Routes scale and are permissioned individually.
|
|
30
|
+
- `"single-lambda"` -- one Lambda for the entire API. A router is auto-generated at synth time that dispatches to the correct handler based on the API Gateway route key. Environment variables and DynamoDB grants are merged across all routes.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
new RoutedApi(this, 'MyApi', {
|
|
34
|
+
deploymentMode: 'single-lambda',
|
|
35
|
+
// ...same props as above
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## routes.json format
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"defaultMemoryMb": 256,
|
|
44
|
+
"defaultTimeoutSeconds": 15,
|
|
45
|
+
"defaultEnvironment": { "LOG_LEVEL": "info" },
|
|
46
|
+
"defaultTableAccess": { "todos": "readWrite" },
|
|
47
|
+
"routes": [
|
|
48
|
+
{
|
|
49
|
+
"path": "/todos",
|
|
50
|
+
"method": "GET",
|
|
51
|
+
"handler": "todos/list.ts",
|
|
52
|
+
"tables": { "todos": "read" }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "/todos",
|
|
56
|
+
"method": "POST",
|
|
57
|
+
"handler": "todos/create.ts"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"path": "/todos/{id}",
|
|
61
|
+
"method": "DELETE",
|
|
62
|
+
"handler": "todos/delete.ts"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Routes can also be passed inline via the `routes` prop instead of `routesFile`.
|
|
69
|
+
|
|
70
|
+
## Handler file convention
|
|
71
|
+
|
|
72
|
+
Each handler file must export a function with the `APIGatewayProxyHandlerV2` signature from `aws-lambda`. The default export name is `handler` (override per route with the `export` field).
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
|
|
76
|
+
|
|
77
|
+
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
|
|
78
|
+
return {
|
|
79
|
+
statusCode: 200,
|
|
80
|
+
headers: { 'content-type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({ message: 'ok' }),
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Handler files work identically in both deployment modes -- no changes needed when switching.
|
|
87
|
+
|
|
88
|
+
## DynamoDB table access
|
|
89
|
+
|
|
90
|
+
Pass tables to the construct via the `tables` prop keyed by a short name:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
tables: { todos: todosTable, audit: auditTable }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The construct automatically:
|
|
97
|
+
- Grants IAM permissions (read, write, or readWrite) based on the route's `tables` field or the `defaultTableAccess` setting.
|
|
98
|
+
- Sets an environment variable `<TABLE_NAME>_TABLE_NAME` on each Lambda (e.g. `TODOS_TABLE_NAME`), so handlers read `process.env.TODOS_TABLE_NAME`.
|
|
99
|
+
|
|
100
|
+
### Table access options
|
|
101
|
+
|
|
102
|
+
Per-route `tables` field (overrides defaults for that route):
|
|
103
|
+
- Array: `["todos"]` grants readWrite to listed tables.
|
|
104
|
+
- Object: `{ "todos": "read", "audit": "write" }` for mixed access.
|
|
105
|
+
|
|
106
|
+
`defaultTableAccess` (applies to routes that don't specify their own):
|
|
107
|
+
- `"allReadWrite"` (default) -- every table, full access.
|
|
108
|
+
- `"allRead"` -- every table, read only.
|
|
109
|
+
- `"none"` -- no grants unless the route opts in.
|
|
110
|
+
- Object: `{ "todos": "readWrite" }` -- explicit per-table defaults.
|
|
111
|
+
|
|
112
|
+
## CORS
|
|
113
|
+
|
|
114
|
+
Enabled by default with sensible headers. Customise or disable:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
cors: {
|
|
118
|
+
origins: ['https://app.example.com'],
|
|
119
|
+
allowCredentials: true,
|
|
120
|
+
},
|
|
121
|
+
// or disable:
|
|
122
|
+
cors: false,
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Custom domain
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
domain: {
|
|
129
|
+
recordName: 'api.example.com',
|
|
130
|
+
hostedZoneName: 'example.com',
|
|
131
|
+
certificateArn: 'arn:aws:acm:...',
|
|
132
|
+
},
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Requires an ACM certificate in the same region. Creates the Route53 A record automatically.
|
|
136
|
+
|
|
137
|
+
## Lambda naming
|
|
138
|
+
|
|
139
|
+
Set `lambdaPrefix` to control the CloudFormation construct ID prefix for all Lambdas:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
new RoutedApi(this, 'MyApi', {
|
|
143
|
+
lambdaPrefix: 'MyApi',
|
|
144
|
+
// ...
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
In `lambda-per-route` mode this produces IDs like `MyApiGetTodosFn`, `MyApiPostTodosFn`, etc. In `single-lambda` mode the function is named `MyApiRouterFn`. Defaults to `""` (no prefix).
|
|
149
|
+
|
|
150
|
+
## Exposed properties
|
|
151
|
+
|
|
152
|
+
- `api.httpApi` -- the underlying `HttpApi` construct.
|
|
153
|
+
- `api.functions` -- `Map<string, NodejsFunction>` keyed by `"METHOD /path"`.
|
|
154
|
+
- `api.routerFunction` -- the single Lambda (only in `single-lambda` mode).
|
|
155
|
+
- `api.url` -- the base URL (custom domain or API Gateway endpoint).
|
|
156
|
+
|
|
157
|
+
## Defaults
|
|
158
|
+
|
|
159
|
+
- Runtime: Node.js 20.x
|
|
160
|
+
- Memory: 256 MB
|
|
161
|
+
- Timeout: 30 seconds
|
|
162
|
+
- `NODE_OPTIONS: '--enable-source-maps'` is set on all Lambdas automatically for readable stack traces.
|
|
163
|
+
- Bundling: minified with source maps, `@aws-sdk/*` externalised.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoutedApi = void 0;
|
|
4
|
+
var routed_api_1 = require("./routed-api");
|
|
5
|
+
Object.defineProperty(exports, "RoutedApi", { enumerable: true, get: function () { return routed_api_1.RoutedApi; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAUsB;AATpB,uGAAA,SAAS,OAAA"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
3
|
+
import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
4
|
+
import { HttpApi } from 'aws-cdk-lib/aws-apigatewayv2';
|
|
5
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
6
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'ANY';
|
|
7
|
+
export type DeploymentMode = 'lambda-per-route' | 'single-lambda';
|
|
8
|
+
export type TableAccess = 'read' | 'write' | 'readWrite';
|
|
9
|
+
export interface RouteDefinition {
|
|
10
|
+
/** URL path (e.g. "/todos" or "/todos/{id}"). */
|
|
11
|
+
path: string;
|
|
12
|
+
/** HTTP method. */
|
|
13
|
+
method: HttpMethod;
|
|
14
|
+
/**
|
|
15
|
+
* Path to the handler TypeScript file, relative to `lambdaSrcDir`.
|
|
16
|
+
* Example: "todos/list.ts"
|
|
17
|
+
*/
|
|
18
|
+
handler: string;
|
|
19
|
+
/** Exported function name in the handler file. Defaults to "handler". */
|
|
20
|
+
export?: string;
|
|
21
|
+
/** Override the default memory size (MB). */
|
|
22
|
+
memoryMb?: number;
|
|
23
|
+
/** Override the default timeout (seconds). */
|
|
24
|
+
timeoutSeconds?: number;
|
|
25
|
+
/** Extra environment variables for this lambda only. */
|
|
26
|
+
environment?: Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Tables this lambda should have access to. Keys must exist in the
|
|
29
|
+
* `tables` map passed to the construct. When provided this REPLACES the
|
|
30
|
+
* default table grants for this route.
|
|
31
|
+
*
|
|
32
|
+
* Examples:
|
|
33
|
+
* ["todos"] -> readWrite to "todos"
|
|
34
|
+
* { todos: "read", audit: "write" } -> mixed access
|
|
35
|
+
*/
|
|
36
|
+
tables?: Record<string, TableAccess> | string[];
|
|
37
|
+
}
|
|
38
|
+
export interface RoutesFile {
|
|
39
|
+
defaultMemoryMb?: number;
|
|
40
|
+
defaultTimeoutSeconds?: number;
|
|
41
|
+
defaultEnvironment?: Record<string, string>;
|
|
42
|
+
/** Table grants applied to every route that doesn't specify its own. */
|
|
43
|
+
defaultTableAccess?: Record<string, TableAccess> | 'allReadWrite' | 'allRead' | 'none';
|
|
44
|
+
routes: RouteDefinition[];
|
|
45
|
+
}
|
|
46
|
+
export interface RoutedApiCors {
|
|
47
|
+
/** Defaults to ["*"]. */
|
|
48
|
+
origins?: string[];
|
|
49
|
+
/** Defaults to all common methods + OPTIONS. */
|
|
50
|
+
methods?: HttpMethod[];
|
|
51
|
+
/** Extra allowed request headers. Sensible defaults are always included. */
|
|
52
|
+
headers?: string[];
|
|
53
|
+
allowCredentials?: boolean;
|
|
54
|
+
/** Max age for cached preflight, in seconds. */
|
|
55
|
+
maxAgeSeconds?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface RoutedApiDomain {
|
|
58
|
+
/** Full record (e.g. "api.example.com"). */
|
|
59
|
+
recordName: string;
|
|
60
|
+
/** Hosted zone name (e.g. "example.com"). */
|
|
61
|
+
hostedZoneName: string;
|
|
62
|
+
/** Hosted zone id. If omitted, the zone is looked up by name (requires env account/region). */
|
|
63
|
+
hostedZoneId?: string;
|
|
64
|
+
/** ACM certificate ARN for `recordName`. Must be in the same region as the API. */
|
|
65
|
+
certificateArn: string;
|
|
66
|
+
}
|
|
67
|
+
export interface RoutedApiProps {
|
|
68
|
+
/**
|
|
69
|
+
* How lambdas are deployed.
|
|
70
|
+
*
|
|
71
|
+
* - `"lambda-per-route"` (default) — one Lambda per route.
|
|
72
|
+
* - `"single-lambda"` — a single Lambda with an auto-generated router that
|
|
73
|
+
* dispatches to the correct handler based on the API Gateway route key.
|
|
74
|
+
* All route environment variables and table grants are merged.
|
|
75
|
+
*/
|
|
76
|
+
deploymentMode?: DeploymentMode;
|
|
77
|
+
/** Path to the routes JSON file (relative to CWD or absolute). */
|
|
78
|
+
routesFile?: string;
|
|
79
|
+
/** Inline routes — alternative to `routesFile`. */
|
|
80
|
+
routes?: RoutesFile | RouteDefinition[];
|
|
81
|
+
/** Directory containing handler .ts files. Relative to CWD or absolute. */
|
|
82
|
+
lambdaSrcDir: string;
|
|
83
|
+
/** Tables exposed to routes, keyed by short name. */
|
|
84
|
+
tables?: Record<string, dynamodb.ITable>;
|
|
85
|
+
/**
|
|
86
|
+
* Default table grants for every route. Overridden by `routes.json`'s
|
|
87
|
+
* `defaultTableAccess` or by each route's `tables`.
|
|
88
|
+
* Defaults to "allReadWrite".
|
|
89
|
+
*/
|
|
90
|
+
defaultTableAccess?: Record<string, TableAccess> | 'allReadWrite' | 'allRead' | 'none';
|
|
91
|
+
/** Environment vars added to every lambda. */
|
|
92
|
+
defaultEnvironment?: Record<string, string>;
|
|
93
|
+
/** Default memory (MB). Defaults to 256. */
|
|
94
|
+
defaultMemoryMb?: number;
|
|
95
|
+
/** Default timeout (seconds). Defaults to 30. */
|
|
96
|
+
defaultTimeoutSeconds?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Log level injected as the `LOG_LEVEL` environment variable on every
|
|
99
|
+
* Lambda. Defaults to `"info"`. Set to `false` to omit.
|
|
100
|
+
*/
|
|
101
|
+
logLevel?: string | false;
|
|
102
|
+
/** API name. Defaults to the construct id. */
|
|
103
|
+
apiName?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Prefix for Lambda construct IDs (and therefore CloudFormation logical IDs).
|
|
106
|
+
* For example, `"MyApi"` produces Lambdas named `MyApiGetTodosFn`,
|
|
107
|
+
* `MyApiPostTodosFn`, etc. In `single-lambda` mode the function is named
|
|
108
|
+
* `<prefix>RouterFn`. Defaults to `""` (no prefix).
|
|
109
|
+
*/
|
|
110
|
+
lambdaPrefix?: string;
|
|
111
|
+
/**
|
|
112
|
+
* When set, Lambda functions receive explicit AWS physical names instead of
|
|
113
|
+
* CDK-generated ones with hash suffixes. This produces clean, readable
|
|
114
|
+
* names in the AWS console.
|
|
115
|
+
*
|
|
116
|
+
* - `single-lambda` mode: `<resourcePrefix>-router`
|
|
117
|
+
* - `lambda-per-route` mode: `<resourcePrefix>-<method>-<path-slug>`
|
|
118
|
+
* e.g. `"my-api"` -> `my-api-get-todos`, `my-api-post-todos-id`
|
|
119
|
+
*
|
|
120
|
+
* **Note:** Setting explicit function names prevents CloudFormation from
|
|
121
|
+
* performing seamless replacements. Use this when you want predictable
|
|
122
|
+
* naming and are comfortable with the trade-off.
|
|
123
|
+
*/
|
|
124
|
+
resourcePrefix?: string;
|
|
125
|
+
/** Node runtime. Defaults to NODEJS_20_X. */
|
|
126
|
+
runtime?: lambda.Runtime;
|
|
127
|
+
/** Pass-through props for NodejsFunction (bundling options, layers, etc.). */
|
|
128
|
+
nodejsFunctionProps?: Partial<NodejsFunctionProps>;
|
|
129
|
+
/** CORS config. Pass `false` to disable preflight. */
|
|
130
|
+
cors?: false | RoutedApiCors;
|
|
131
|
+
/** Optional Route53 + ACM custom domain. */
|
|
132
|
+
domain?: RoutedApiDomain;
|
|
133
|
+
}
|
|
134
|
+
export declare class RoutedApi extends Construct {
|
|
135
|
+
/** The underlying HTTP API. */
|
|
136
|
+
readonly httpApi: HttpApi;
|
|
137
|
+
/**
|
|
138
|
+
* Created Lambdas keyed by `"METHOD path"` (e.g. `"GET /todos"`).
|
|
139
|
+
* In `single-lambda` mode every key points to the same function.
|
|
140
|
+
*/
|
|
141
|
+
readonly functions: Map<string, NodejsFunction>;
|
|
142
|
+
/**
|
|
143
|
+
* The single router Lambda (only set in `single-lambda` mode).
|
|
144
|
+
* In `lambda-per-route` mode this is `undefined`.
|
|
145
|
+
*/
|
|
146
|
+
readonly routerFunction?: NodejsFunction;
|
|
147
|
+
/** Base URL: custom domain if configured, otherwise the API Gateway URL. */
|
|
148
|
+
readonly url: string;
|
|
149
|
+
constructor(scope: Construct, id: string, props: RoutedApiProps);
|
|
150
|
+
private deployLambdaPerRoute;
|
|
151
|
+
private deploySingleLambda;
|
|
152
|
+
/**
|
|
153
|
+
* Writes a temporary TypeScript router file that imports every handler and
|
|
154
|
+
* dispatches based on the API Gateway `routeKey` (`"METHOD /path"`).
|
|
155
|
+
* Returns the absolute path to the generated file.
|
|
156
|
+
*/
|
|
157
|
+
private generateRouterEntry;
|
|
158
|
+
private loadRoutesFile;
|
|
159
|
+
private createLambda;
|
|
160
|
+
private attachDomain;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=routed-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routed-api.d.ts","sourceRoot":"","sources":["../src/routed-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RoutedApi = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
41
|
+
const constructs_1 = require("constructs");
|
|
42
|
+
const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
|
|
43
|
+
const aws_lambda_nodejs_1 = require("aws-cdk-lib/aws-lambda-nodejs");
|
|
44
|
+
const aws_apigatewayv2_1 = require("aws-cdk-lib/aws-apigatewayv2");
|
|
45
|
+
const aws_apigatewayv2_integrations_1 = require("aws-cdk-lib/aws-apigatewayv2-integrations");
|
|
46
|
+
const acm = __importStar(require("aws-cdk-lib/aws-certificatemanager"));
|
|
47
|
+
const route53 = __importStar(require("aws-cdk-lib/aws-route53"));
|
|
48
|
+
const r53targets = __importStar(require("aws-cdk-lib/aws-route53-targets"));
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// The construct
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
class RoutedApi extends constructs_1.Construct {
|
|
53
|
+
constructor(scope, id, props) {
|
|
54
|
+
super(scope, id);
|
|
55
|
+
/**
|
|
56
|
+
* Created Lambdas keyed by `"METHOD path"` (e.g. `"GET /todos"`).
|
|
57
|
+
* In `single-lambda` mode every key points to the same function.
|
|
58
|
+
*/
|
|
59
|
+
this.functions = new Map();
|
|
60
|
+
const routesFile = this.loadRoutesFile(props);
|
|
61
|
+
const lambdaSrcDir = path.resolve(props.lambdaSrcDir);
|
|
62
|
+
if (!fs.existsSync(lambdaSrcDir)) {
|
|
63
|
+
throw new Error(`RoutedApi: lambdaSrcDir does not exist: ${lambdaSrcDir}`);
|
|
64
|
+
}
|
|
65
|
+
const defaultMemoryMb = routesFile.defaultMemoryMb ?? props.defaultMemoryMb ?? 256;
|
|
66
|
+
const defaultTimeoutSeconds = routesFile.defaultTimeoutSeconds ?? props.defaultTimeoutSeconds ?? 30;
|
|
67
|
+
const logLevelEnv = props.logLevel === false ? {} : { LOG_LEVEL: props.logLevel ?? 'info' };
|
|
68
|
+
const defaultEnvironment = {
|
|
69
|
+
NODE_OPTIONS: '--enable-source-maps',
|
|
70
|
+
...logLevelEnv,
|
|
71
|
+
...(props.defaultEnvironment ?? {}),
|
|
72
|
+
...(routesFile.defaultEnvironment ?? {}),
|
|
73
|
+
};
|
|
74
|
+
const defaultTableAccess = routesFile.defaultTableAccess ?? props.defaultTableAccess ?? 'allReadWrite';
|
|
75
|
+
// ---- HTTP API + CORS ---------------------------------------------------
|
|
76
|
+
const corsPreflight = props.cors === false ? undefined : buildCors(props.cors);
|
|
77
|
+
this.httpApi = new aws_apigatewayv2_1.HttpApi(this, 'HttpApi', {
|
|
78
|
+
apiName: props.apiName ?? id,
|
|
79
|
+
corsPreflight,
|
|
80
|
+
});
|
|
81
|
+
// ---- Lambdas + routes --------------------------------------------------
|
|
82
|
+
const runtime = props.runtime ?? lambda.Runtime.NODEJS_20_X;
|
|
83
|
+
const tables = props.tables ?? {};
|
|
84
|
+
const mode = props.deploymentMode ?? 'lambda-per-route';
|
|
85
|
+
const lambdaPrefix = props.lambdaPrefix ?? '';
|
|
86
|
+
const resourcePrefix = props.resourcePrefix;
|
|
87
|
+
if (mode === 'single-lambda') {
|
|
88
|
+
this.deploySingleLambda({
|
|
89
|
+
routes: routesFile.routes,
|
|
90
|
+
lambdaSrcDir,
|
|
91
|
+
runtime,
|
|
92
|
+
defaultMemoryMb,
|
|
93
|
+
defaultTimeoutSeconds,
|
|
94
|
+
defaultEnvironment,
|
|
95
|
+
defaultTableAccess,
|
|
96
|
+
tables,
|
|
97
|
+
lambdaPrefix,
|
|
98
|
+
resourcePrefix,
|
|
99
|
+
extraProps: props.nodejsFunctionProps,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.deployLambdaPerRoute({
|
|
104
|
+
routes: routesFile.routes,
|
|
105
|
+
lambdaSrcDir,
|
|
106
|
+
runtime,
|
|
107
|
+
defaultMemoryMb,
|
|
108
|
+
defaultTimeoutSeconds,
|
|
109
|
+
defaultEnvironment,
|
|
110
|
+
defaultTableAccess,
|
|
111
|
+
tables,
|
|
112
|
+
lambdaPrefix,
|
|
113
|
+
resourcePrefix,
|
|
114
|
+
extraProps: props.nodejsFunctionProps,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// ---- Optional custom domain -------------------------------------------
|
|
118
|
+
let resolvedUrl = this.httpApi.apiEndpoint;
|
|
119
|
+
if (props.domain) {
|
|
120
|
+
resolvedUrl = this.attachDomain(props.domain);
|
|
121
|
+
}
|
|
122
|
+
this.url = resolvedUrl;
|
|
123
|
+
}
|
|
124
|
+
// ---- lambda-per-route mode (original behaviour) ---------------------------
|
|
125
|
+
deployLambdaPerRoute(args) {
|
|
126
|
+
for (const route of args.routes) {
|
|
127
|
+
const fn = this.createLambda({
|
|
128
|
+
route,
|
|
129
|
+
lambdaSrcDir: args.lambdaSrcDir,
|
|
130
|
+
runtime: args.runtime,
|
|
131
|
+
defaultMemoryMb: args.defaultMemoryMb,
|
|
132
|
+
defaultTimeoutSeconds: args.defaultTimeoutSeconds,
|
|
133
|
+
defaultEnvironment: args.defaultEnvironment,
|
|
134
|
+
lambdaPrefix: args.lambdaPrefix,
|
|
135
|
+
resourcePrefix: args.resourcePrefix,
|
|
136
|
+
extraProps: args.extraProps,
|
|
137
|
+
});
|
|
138
|
+
grantTableAccess({
|
|
139
|
+
fn,
|
|
140
|
+
route,
|
|
141
|
+
tables: args.tables,
|
|
142
|
+
defaultTableAccess: args.defaultTableAccess,
|
|
143
|
+
});
|
|
144
|
+
this.httpApi.addRoutes({
|
|
145
|
+
path: route.path,
|
|
146
|
+
methods: [toApiGwMethod(route.method)],
|
|
147
|
+
integration: new aws_apigatewayv2_integrations_1.HttpLambdaIntegration(`${cleanId(route)}Integ`, fn),
|
|
148
|
+
});
|
|
149
|
+
this.functions.set(`${route.method} ${route.path}`, fn);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// ---- single-lambda mode --------------------------------------------------
|
|
153
|
+
deploySingleLambda(args) {
|
|
154
|
+
const { routes, lambdaSrcDir, runtime } = args;
|
|
155
|
+
// Validate all handler files exist before generating the router.
|
|
156
|
+
for (const route of routes) {
|
|
157
|
+
const entry = path.resolve(lambdaSrcDir, route.handler);
|
|
158
|
+
if (!fs.existsSync(entry)) {
|
|
159
|
+
throw new Error(`RoutedApi: handler file does not exist: ${entry} ` +
|
|
160
|
+
`(route ${route.method} ${route.path})`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Merge environment variables from every route into one superset.
|
|
164
|
+
const mergedEnv = { ...args.defaultEnvironment };
|
|
165
|
+
for (const route of routes) {
|
|
166
|
+
Object.assign(mergedEnv, route.environment ?? {});
|
|
167
|
+
}
|
|
168
|
+
// Use the highest memory / timeout across all routes.
|
|
169
|
+
const memorySize = Math.max(args.defaultMemoryMb, ...routes.map((r) => r.memoryMb ?? 0));
|
|
170
|
+
const timeoutSeconds = Math.max(args.defaultTimeoutSeconds, ...routes.map((r) => r.timeoutSeconds ?? 0));
|
|
171
|
+
const routerEntry = this.generateRouterEntry(routes, lambdaSrcDir);
|
|
172
|
+
const fn = new aws_lambda_nodejs_1.NodejsFunction(this, `${args.lambdaPrefix}RouterFn`, {
|
|
173
|
+
...(args.resourcePrefix ? { functionName: `${args.resourcePrefix}-router` } : {}),
|
|
174
|
+
entry: routerEntry,
|
|
175
|
+
handler: 'handler',
|
|
176
|
+
runtime,
|
|
177
|
+
memorySize,
|
|
178
|
+
timeout: aws_cdk_lib_1.Duration.seconds(timeoutSeconds),
|
|
179
|
+
environment: mergedEnv,
|
|
180
|
+
bundling: {
|
|
181
|
+
minify: true,
|
|
182
|
+
sourceMap: true,
|
|
183
|
+
target: 'node20',
|
|
184
|
+
externalModules: ['@aws-sdk/*'],
|
|
185
|
+
...(args.extraProps?.bundling ?? {}),
|
|
186
|
+
},
|
|
187
|
+
...(args.extraProps ?? {}),
|
|
188
|
+
});
|
|
189
|
+
// Grant the union of all table access across every route.
|
|
190
|
+
grantTableAccessUnion({
|
|
191
|
+
fn,
|
|
192
|
+
routes,
|
|
193
|
+
tables: args.tables,
|
|
194
|
+
defaultTableAccess: args.defaultTableAccess,
|
|
195
|
+
});
|
|
196
|
+
// Register each route in API Gateway, all pointing to the same Lambda.
|
|
197
|
+
const integration = new aws_apigatewayv2_integrations_1.HttpLambdaIntegration('RouterInteg', fn);
|
|
198
|
+
for (const route of routes) {
|
|
199
|
+
this.httpApi.addRoutes({
|
|
200
|
+
path: route.path,
|
|
201
|
+
methods: [toApiGwMethod(route.method)],
|
|
202
|
+
integration,
|
|
203
|
+
});
|
|
204
|
+
this.functions.set(`${route.method} ${route.path}`, fn);
|
|
205
|
+
}
|
|
206
|
+
this.routerFunction = fn;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Writes a temporary TypeScript router file that imports every handler and
|
|
210
|
+
* dispatches based on the API Gateway `routeKey` (`"METHOD /path"`).
|
|
211
|
+
* Returns the absolute path to the generated file.
|
|
212
|
+
*/
|
|
213
|
+
generateRouterEntry(routes, lambdaSrcDir) {
|
|
214
|
+
const imports = [];
|
|
215
|
+
const mapEntries = [];
|
|
216
|
+
for (let i = 0; i < routes.length; i++) {
|
|
217
|
+
const route = routes[i];
|
|
218
|
+
const exportName = route.export ?? 'handler';
|
|
219
|
+
const alias = `h${i}`;
|
|
220
|
+
const handlerAbs = path.resolve(lambdaSrcDir, route.handler);
|
|
221
|
+
// Strip the .ts extension so the import works as a module specifier.
|
|
222
|
+
const importPath = handlerAbs.replace(/\.tsx?$/, '');
|
|
223
|
+
if (exportName === 'default') {
|
|
224
|
+
imports.push(`import ${alias} from '${importPath}';`);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
imports.push(`import { ${exportName} as ${alias} } from '${importPath}';`);
|
|
228
|
+
}
|
|
229
|
+
const routeKey = `${route.method} ${route.path}`;
|
|
230
|
+
mapEntries.push(` '${routeKey}': ${alias},`);
|
|
231
|
+
}
|
|
232
|
+
const source = [
|
|
233
|
+
`import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';`,
|
|
234
|
+
'',
|
|
235
|
+
...imports,
|
|
236
|
+
'',
|
|
237
|
+
`const routeMap: Record<string, APIGatewayProxyHandlerV2> = {`,
|
|
238
|
+
...mapEntries,
|
|
239
|
+
`};`,
|
|
240
|
+
'',
|
|
241
|
+
`export const handler: APIGatewayProxyHandlerV2 = async (event, context) => {`,
|
|
242
|
+
` const fn = routeMap[event.routeKey];`,
|
|
243
|
+
` if (!fn) {`,
|
|
244
|
+
` return {`,
|
|
245
|
+
` statusCode: 404,`,
|
|
246
|
+
` headers: { 'content-type': 'application/json' },`,
|
|
247
|
+
` body: JSON.stringify({ message: 'Not Found', routeKey: event.routeKey }),`,
|
|
248
|
+
` };`,
|
|
249
|
+
` }`,
|
|
250
|
+
` return fn(event, context, () => {}) as any;`,
|
|
251
|
+
`};`,
|
|
252
|
+
'',
|
|
253
|
+
].join('\n');
|
|
254
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routed-api-'));
|
|
255
|
+
const routerPath = path.join(tmpDir, 'router.ts');
|
|
256
|
+
fs.writeFileSync(routerPath, source, 'utf8');
|
|
257
|
+
return routerPath;
|
|
258
|
+
}
|
|
259
|
+
// ---- shared helpers ------------------------------------------------------
|
|
260
|
+
loadRoutesFile(props) {
|
|
261
|
+
if (props.routesFile && props.routes) {
|
|
262
|
+
throw new Error('RoutedApi: pass either `routesFile` OR `routes`, not both.');
|
|
263
|
+
}
|
|
264
|
+
if (props.routes) {
|
|
265
|
+
if (Array.isArray(props.routes))
|
|
266
|
+
return { routes: props.routes };
|
|
267
|
+
return props.routes;
|
|
268
|
+
}
|
|
269
|
+
if (!props.routesFile) {
|
|
270
|
+
throw new Error('RoutedApi: provide either `routesFile` or `routes`.');
|
|
271
|
+
}
|
|
272
|
+
const abs = path.resolve(props.routesFile);
|
|
273
|
+
if (!fs.existsSync(abs)) {
|
|
274
|
+
throw new Error(`RoutedApi: routesFile does not exist: ${abs}`);
|
|
275
|
+
}
|
|
276
|
+
const parsed = JSON.parse(fs.readFileSync(abs, 'utf8'));
|
|
277
|
+
if (!parsed.routes || !Array.isArray(parsed.routes)) {
|
|
278
|
+
throw new Error(`RoutedApi: ${abs} must contain a "routes" array.`);
|
|
279
|
+
}
|
|
280
|
+
return parsed;
|
|
281
|
+
}
|
|
282
|
+
createLambda(args) {
|
|
283
|
+
const { route, lambdaSrcDir, runtime, defaultMemoryMb, defaultTimeoutSeconds } = args;
|
|
284
|
+
const entry = path.resolve(lambdaSrcDir, route.handler);
|
|
285
|
+
if (!fs.existsSync(entry)) {
|
|
286
|
+
throw new Error(`RoutedApi: handler file does not exist: ${entry} ` +
|
|
287
|
+
`(route ${route.method} ${route.path})`);
|
|
288
|
+
}
|
|
289
|
+
const constructId = `${args.lambdaPrefix}${cleanId(route)}`;
|
|
290
|
+
const environment = {
|
|
291
|
+
...args.defaultEnvironment,
|
|
292
|
+
...(route.environment ?? {}),
|
|
293
|
+
};
|
|
294
|
+
return new aws_lambda_nodejs_1.NodejsFunction(this, `${constructId}Fn`, {
|
|
295
|
+
...(args.resourcePrefix
|
|
296
|
+
? { functionName: `${args.resourcePrefix}-${kebabId(route)}` }
|
|
297
|
+
: {}),
|
|
298
|
+
entry,
|
|
299
|
+
handler: route.export ?? 'handler',
|
|
300
|
+
runtime,
|
|
301
|
+
memorySize: route.memoryMb ?? defaultMemoryMb,
|
|
302
|
+
timeout: aws_cdk_lib_1.Duration.seconds(route.timeoutSeconds ?? defaultTimeoutSeconds),
|
|
303
|
+
environment,
|
|
304
|
+
bundling: {
|
|
305
|
+
minify: true,
|
|
306
|
+
sourceMap: true,
|
|
307
|
+
target: 'node20',
|
|
308
|
+
externalModules: ['@aws-sdk/*'],
|
|
309
|
+
...(args.extraProps?.bundling ?? {}),
|
|
310
|
+
},
|
|
311
|
+
...(args.extraProps ?? {}),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
attachDomain(domain) {
|
|
315
|
+
const cert = acm.Certificate.fromCertificateArn(this, 'DomainCert', domain.certificateArn);
|
|
316
|
+
const dn = new aws_apigatewayv2_1.DomainName(this, 'DomainName', {
|
|
317
|
+
domainName: domain.recordName,
|
|
318
|
+
certificate: cert,
|
|
319
|
+
});
|
|
320
|
+
new aws_apigatewayv2_1.ApiMapping(this, 'DomainApiMapping', {
|
|
321
|
+
api: this.httpApi,
|
|
322
|
+
domainName: dn,
|
|
323
|
+
});
|
|
324
|
+
const zone = domain.hostedZoneId
|
|
325
|
+
? route53.HostedZone.fromHostedZoneAttributes(this, 'DomainZone', {
|
|
326
|
+
hostedZoneId: domain.hostedZoneId,
|
|
327
|
+
zoneName: domain.hostedZoneName,
|
|
328
|
+
})
|
|
329
|
+
: route53.HostedZone.fromLookup(this, 'DomainZone', { domainName: domain.hostedZoneName });
|
|
330
|
+
new route53.ARecord(this, 'DomainAliasRecord', {
|
|
331
|
+
zone,
|
|
332
|
+
recordName: domain.recordName,
|
|
333
|
+
target: route53.RecordTarget.fromAlias(new r53targets.ApiGatewayv2DomainProperties(dn.regionalDomainName, dn.regionalHostedZoneId)),
|
|
334
|
+
});
|
|
335
|
+
return `https://${domain.recordName}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
exports.RoutedApi = RoutedApi;
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// Helpers
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
function buildCors(cors) {
|
|
343
|
+
const c = cors ?? {};
|
|
344
|
+
const methods = (c.methods ?? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']).map((m) => aws_apigatewayv2_1.CorsHttpMethod[m] ?? aws_apigatewayv2_1.CorsHttpMethod.ANY);
|
|
345
|
+
const baseHeaders = [
|
|
346
|
+
'Content-Type',
|
|
347
|
+
'Authorization',
|
|
348
|
+
'X-Api-Key',
|
|
349
|
+
'X-Amz-Date',
|
|
350
|
+
'X-Amz-Security-Token',
|
|
351
|
+
'X-Requested-With',
|
|
352
|
+
];
|
|
353
|
+
return {
|
|
354
|
+
allowOrigins: c.origins ?? ['*'],
|
|
355
|
+
allowMethods: methods,
|
|
356
|
+
allowHeaders: Array.from(new Set([...baseHeaders, ...(c.headers ?? [])])),
|
|
357
|
+
allowCredentials: c.allowCredentials,
|
|
358
|
+
maxAge: c.maxAgeSeconds ? aws_cdk_lib_1.Duration.seconds(c.maxAgeSeconds) : undefined,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function grantTableAccess(args) {
|
|
362
|
+
const { fn, route, tables } = args;
|
|
363
|
+
// Resolve which tables get which access for this route.
|
|
364
|
+
const grants = {};
|
|
365
|
+
if (route.tables) {
|
|
366
|
+
if (Array.isArray(route.tables)) {
|
|
367
|
+
for (const name of route.tables)
|
|
368
|
+
grants[name] = 'readWrite';
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
Object.assign(grants, route.tables);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
const def = args.defaultTableAccess;
|
|
376
|
+
if (def === 'allReadWrite') {
|
|
377
|
+
for (const name of Object.keys(tables))
|
|
378
|
+
grants[name] = 'readWrite';
|
|
379
|
+
}
|
|
380
|
+
else if (def === 'allRead') {
|
|
381
|
+
for (const name of Object.keys(tables))
|
|
382
|
+
grants[name] = 'read';
|
|
383
|
+
}
|
|
384
|
+
else if (def === 'none') {
|
|
385
|
+
// no grants
|
|
386
|
+
}
|
|
387
|
+
else if (def && typeof def === 'object') {
|
|
388
|
+
Object.assign(grants, def);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
for (const [name, access] of Object.entries(grants)) {
|
|
392
|
+
const table = tables[name];
|
|
393
|
+
if (!table) {
|
|
394
|
+
throw new Error(`RoutedApi: route ${route.method} ${route.path} wants table "${name}", ` +
|
|
395
|
+
`but it was not passed in the "tables" prop.`);
|
|
396
|
+
}
|
|
397
|
+
if (access === 'read')
|
|
398
|
+
table.grantReadData(fn);
|
|
399
|
+
else if (access === 'write')
|
|
400
|
+
table.grantWriteData(fn);
|
|
401
|
+
else
|
|
402
|
+
table.grantReadWriteData(fn);
|
|
403
|
+
// Expose the table name to the lambda as an env var
|
|
404
|
+
// (idempotent: setting the same value is fine).
|
|
405
|
+
fn.addEnvironment(`${envCase(name)}_TABLE_NAME`, table.tableName);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Grants the union of all table access across every route to a single Lambda.
|
|
410
|
+
* If any route requests `readWrite` for a table, that wins over `read` or `write`.
|
|
411
|
+
* `read` + `write` on the same table is escalated to `readWrite`.
|
|
412
|
+
*/
|
|
413
|
+
function grantTableAccessUnion(args) {
|
|
414
|
+
const { fn, routes, tables } = args;
|
|
415
|
+
const merged = {};
|
|
416
|
+
for (const route of routes) {
|
|
417
|
+
const grants = {};
|
|
418
|
+
if (route.tables) {
|
|
419
|
+
if (Array.isArray(route.tables)) {
|
|
420
|
+
for (const name of route.tables)
|
|
421
|
+
grants[name] = 'readWrite';
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
Object.assign(grants, route.tables);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const def = args.defaultTableAccess;
|
|
429
|
+
if (def === 'allReadWrite') {
|
|
430
|
+
for (const name of Object.keys(tables))
|
|
431
|
+
grants[name] = 'readWrite';
|
|
432
|
+
}
|
|
433
|
+
else if (def === 'allRead') {
|
|
434
|
+
for (const name of Object.keys(tables))
|
|
435
|
+
grants[name] = 'read';
|
|
436
|
+
}
|
|
437
|
+
else if (def === 'none') {
|
|
438
|
+
// no grants
|
|
439
|
+
}
|
|
440
|
+
else if (def && typeof def === 'object') {
|
|
441
|
+
Object.assign(grants, def);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const [name, access] of Object.entries(grants)) {
|
|
445
|
+
merged[name] = escalateAccess(merged[name], access);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const [name, access] of Object.entries(merged)) {
|
|
449
|
+
const table = tables[name];
|
|
450
|
+
if (!table) {
|
|
451
|
+
throw new Error(`RoutedApi: a route wants table "${name}", ` +
|
|
452
|
+
`but it was not passed in the "tables" prop.`);
|
|
453
|
+
}
|
|
454
|
+
if (access === 'read')
|
|
455
|
+
table.grantReadData(fn);
|
|
456
|
+
else if (access === 'write')
|
|
457
|
+
table.grantWriteData(fn);
|
|
458
|
+
else
|
|
459
|
+
table.grantReadWriteData(fn);
|
|
460
|
+
fn.addEnvironment(`${envCase(name)}_TABLE_NAME`, table.tableName);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function escalateAccess(existing, incoming) {
|
|
464
|
+
if (!existing)
|
|
465
|
+
return incoming;
|
|
466
|
+
if (existing === 'readWrite' || incoming === 'readWrite')
|
|
467
|
+
return 'readWrite';
|
|
468
|
+
if (existing !== incoming)
|
|
469
|
+
return 'readWrite';
|
|
470
|
+
return existing;
|
|
471
|
+
}
|
|
472
|
+
function toApiGwMethod(m) {
|
|
473
|
+
switch (m) {
|
|
474
|
+
case 'GET':
|
|
475
|
+
return aws_apigatewayv2_1.HttpMethod.GET;
|
|
476
|
+
case 'POST':
|
|
477
|
+
return aws_apigatewayv2_1.HttpMethod.POST;
|
|
478
|
+
case 'PUT':
|
|
479
|
+
return aws_apigatewayv2_1.HttpMethod.PUT;
|
|
480
|
+
case 'PATCH':
|
|
481
|
+
return aws_apigatewayv2_1.HttpMethod.PATCH;
|
|
482
|
+
case 'DELETE':
|
|
483
|
+
return aws_apigatewayv2_1.HttpMethod.DELETE;
|
|
484
|
+
case 'HEAD':
|
|
485
|
+
return aws_apigatewayv2_1.HttpMethod.HEAD;
|
|
486
|
+
case 'OPTIONS':
|
|
487
|
+
return aws_apigatewayv2_1.HttpMethod.OPTIONS;
|
|
488
|
+
case 'ANY':
|
|
489
|
+
default:
|
|
490
|
+
return aws_apigatewayv2_1.HttpMethod.ANY;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function cleanId(route) {
|
|
494
|
+
const slug = route.path
|
|
495
|
+
.replace(/[{}]/g, '')
|
|
496
|
+
.split('/')
|
|
497
|
+
.filter(Boolean)
|
|
498
|
+
.map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1))
|
|
499
|
+
.join('');
|
|
500
|
+
const method = route.method.charAt(0) + route.method.slice(1).toLowerCase();
|
|
501
|
+
return `${method}${slug || 'Root'}`;
|
|
502
|
+
}
|
|
503
|
+
/** Kebab-case physical name like "get-todos" or "post-todos-id". */
|
|
504
|
+
function kebabId(route) {
|
|
505
|
+
const slug = route.path
|
|
506
|
+
.replace(/[{}]/g, '')
|
|
507
|
+
.split('/')
|
|
508
|
+
.filter(Boolean)
|
|
509
|
+
.join('-');
|
|
510
|
+
return `${route.method.toLowerCase()}-${slug || 'root'}`;
|
|
511
|
+
}
|
|
512
|
+
function envCase(name) {
|
|
513
|
+
return name
|
|
514
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
515
|
+
.replace(/[-\s]+/g, '_')
|
|
516
|
+
.toUpperCase();
|
|
517
|
+
}
|
|
518
|
+
//# sourceMappingURL=routed-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routed-api.js","sourceRoot":"","sources":["../src/routed-api.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ajentify/routed-api",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Reusable RoutedApi CDK construct: routes.json -> Lambdas + HTTP API + CORS + (optional) Route53 domain. Supports per-lambda and single-lambda deployment modes.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"SKILL.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.build.json",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"aws",
|
|
24
|
+
"cdk",
|
|
25
|
+
"lambda",
|
|
26
|
+
"api-gateway",
|
|
27
|
+
"serverless",
|
|
28
|
+
"construct",
|
|
29
|
+
"routing"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"aws-cdk-lib": "^2.150.0",
|
|
33
|
+
"constructs": "^10.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/aws-lambda": "^8.10.161",
|
|
37
|
+
"@types/node": "^20.11.0",
|
|
38
|
+
"aws-cdk-lib": "^2.150.0",
|
|
39
|
+
"constructs": "^10.0.0",
|
|
40
|
+
"typescript": "^5.4.0"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/ajentify/routed-api"
|
|
46
|
+
}
|
|
47
|
+
}
|