@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 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.
@@ -0,0 +1,2 @@
1
+ export { RoutedApi, type RoutedApiProps, type RoutedApiCors, type RoutedApiDomain, type RouteDefinition, type RoutesFile, type DeploymentMode, type TableAccess, type HttpMethod, } from './routed-api';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,EACL,OAAO,EAKR,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AASrD,MAAM,MAAM,UAAU,GAClB,KAAK,GACL,MAAM,GACN,KAAK,GACL,OAAO,GACP,QAAQ,GACR,MAAM,GACN,SAAS,GACT,KAAK,CAAC;AAEV,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,cAAc,GAAG,SAAS,GAAG,MAAM,CAAC;IACvF,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gDAAgD;IAChD,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,cAAc,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,MAAM,CAAC,EAAE,UAAU,GAAG,eAAe,EAAE,CAAC;IAExC,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IAErB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEzC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,cAAc,GAAG,SAAS,GAAG,MAAM,CAAC;IAEvF,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE1B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACzB,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEnD,sDAAsD;IACtD,IAAI,CAAC,EAAE,KAAK,GAAG,aAAa,CAAC;IAE7B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAMD,qBAAa,SAAU,SAAQ,SAAS;IACtC,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAa;IAC5D;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc;IA8E/D,OAAO,CAAC,oBAAoB;IAiC5B,OAAO,CAAC,kBAAkB;IAwE1B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAqD3B,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,YAAY;IAgDpB,OAAO,CAAC,YAAY;CAiCrB"}
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAA8B;AAC9B,4CAA8B;AAC9B,gDAAkC;AAClC,6CAAuC;AACvC,2CAAuC;AACvC,+DAAiD;AACjD,qEAAoF;AACpF,mEAMsC;AACtC,6FAAkF;AAElF,wEAA0D;AAC1D,iEAAmD;AACnD,4EAA8D;AA+J9D,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAa,SAAU,SAAQ,sBAAS;IAgBtC,YAAY,KAAgB,EAAE,EAAU,EAAE,KAAqB;QAC7D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAdnB;;;WAGG;QACM,cAAS,GAAgC,IAAI,GAAG,EAAE,CAAC;QAY1D,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,2CAA2C,YAAY,EAAE,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,eAAe,GAAG,UAAU,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,IAAI,GAAG,CAAC;QACnF,MAAM,qBAAqB,GACzB,UAAU,CAAC,qBAAqB,IAAI,KAAK,CAAC,qBAAqB,IAAI,EAAE,CAAC;QACxE,MAAM,WAAW,GACf,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC1E,MAAM,kBAAkB,GAA2B;YACjD,YAAY,EAAE,sBAAsB;YACpC,GAAG,WAAW;YACd,GAAG,CAAC,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC;YACnC,GAAG,CAAC,UAAU,CAAC,kBAAkB,IAAI,EAAE,CAAC;SACzC,CAAC;QACF,MAAM,kBAAkB,GACtB,UAAU,CAAC,kBAAkB,IAAI,KAAK,CAAC,kBAAkB,IAAI,cAAc,CAAC;QAE9E,2EAA2E;QAC3E,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO,GAAG,IAAI,0BAAO,CAAC,IAAI,EAAE,SAAS,EAAE;YAC1C,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;YAC5B,aAAa;SACd,CAAC,CAAC;QAEH,2EAA2E;QAC3E,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,KAAK,CAAC,cAAc,IAAI,kBAAkB,CAAC;QAExD,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;QAC9C,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;QAE5C,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;YAC7B,IAAI,CAAC,kBAAkB,CAAC;gBACtB,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,YAAY;gBACZ,OAAO;gBACP,eAAe;gBACf,qBAAqB;gBACrB,kBAAkB;gBAClB,kBAAkB;gBAClB,MAAM;gBACN,YAAY;gBACZ,cAAc;gBACd,UAAU,EAAE,KAAK,CAAC,mBAAmB;aACtC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,oBAAoB,CAAC;gBACxB,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,YAAY;gBACZ,OAAO;gBACP,eAAe;gBACf,qBAAqB;gBACrB,kBAAkB;gBAClB,kBAAkB;gBAClB,MAAM;gBACN,YAAY;gBACZ,cAAc;gBACd,UAAU,EAAE,KAAK,CAAC,mBAAmB;aACtC,CAAC,CAAC;QACL,CAAC;QAED,0EAA0E;QAC1E,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAC3C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC;IACzB,CAAC;IAED,8EAA8E;IAEtE,oBAAoB,CAAC,IAAgB;QAC3C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;gBAC3B,KAAK;gBACL,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,eAAe,EAAE,IAAI,CAAC,eAAe;gBACrC,qBAAqB,EAAE,IAAI,CAAC,qBAAqB;gBACjD,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;gBAC3C,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,UAAU,EAAE,IAAI,CAAC,UAAU;aAC5B,CAAC,CAAC;YAEH,gBAAgB,CAAC;gBACf,EAAE;gBACF,KAAK;gBACL,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;aAC5C,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACtC,WAAW,EAAE,IAAI,qDAAqB,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;aACrE,CAAC,CAAC;YAEH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,6EAA6E;IAErE,kBAAkB,CAAC,IAAgB;QACzC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;QAE/C,iEAAiE;QACjE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,2CAA2C,KAAK,GAAG;oBACjD,UAAU,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,CAC1C,CAAC;YACJ,CAAC;QACH,CAAC;QAED,kEAAkE;QAClE,MAAM,SAAS,GAA2B,EAAE,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACzE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,sDAAsD;QACtD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CACzB,IAAI,CAAC,eAAe,EACpB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CACtC,CAAC;QACF,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAC7B,IAAI,CAAC,qBAAqB,EAC1B,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,CAC5C,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAEnE,MAAM,EAAE,GAAG,IAAI,kCAAc,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,YAAY,UAAU,EAAE;YAClE,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,cAAc,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,KAAK,EAAE,WAAW;YAClB,OAAO,EAAE,SAAS;YAClB,OAAO;YACP,UAAU;YACV,OAAO,EAAE,sBAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;YACzC,WAAW,EAAE,SAAS;YACtB,QAAQ,EAAE;gBACR,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,QAAQ;gBAChB,eAAe,EAAE,CAAC,YAAY,CAAC;gBAC/B,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC;aACrC;YACD,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;SAC3B,CAAC,CAAC;QAEH,0DAA0D;QAC1D,qBAAqB,CAAC;YACpB,EAAE;YACF,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;SAC5C,CAAC,CAAC;QAEH,uEAAuE;QACvE,MAAM,WAAW,GAAG,IAAI,qDAAqB,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACjE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACtC,WAAW;aACZ,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,CAAC;QAEA,IAA4C,CAAC,cAAc,GAAG,EAAE,CAAC;IACpE,CAAC;IAED;;;;OAIG;IACK,mBAAmB,CAAC,MAAyB,EAAE,YAAoB;QACzE,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAa,EAAE,CAAC;QAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;YAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YACtB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7D,qEAAqE;YACrE,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YAErD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,UAAU,UAAU,IAAI,CAAC,CAAC;YACxD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,YAAY,UAAU,OAAO,KAAK,YAAY,UAAU,IAAI,CAAC,CAAC;YAC7E,CAAC;YAED,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACjD,UAAU,CAAC,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,MAAM,GAAG;YACb,6DAA6D;YAC7D,EAAE;YACF,GAAG,OAAO;YACV,EAAE;YACF,8DAA8D;YAC9D,GAAG,UAAU;YACb,IAAI;YACJ,EAAE;YACF,8EAA8E;YAC9E,wCAAwC;YACxC,cAAc;YACd,cAAc;YACd,wBAAwB;YACxB,wDAAwD;YACxD,iFAAiF;YACjF,QAAQ;YACR,KAAK;YACL,+CAA+C;YAC/C,IAAI;YACJ,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAClD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,6EAA6E;IAErE,cAAc,CAAC,KAAqB;QAC1C,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;YACjE,OAAO,KAAK,CAAC,MAAM,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAe,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,iCAAiC,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,YAAY,CAAC,IAUpB;QACC,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC;QAEtF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,2CAA2C,KAAK,GAAG;gBACjD,UAAU,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,CAC1C,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,MAAM,WAAW,GAAG;YAClB,GAAG,IAAI,CAAC,kBAAkB;YAC1B,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC;SAC7B,CAAC;QAEF,OAAO,IAAI,kCAAc,CAAC,IAAI,EAAE,GAAG,WAAW,IAAI,EAAE;YAClD,GAAG,CAAC,IAAI,CAAC,cAAc;gBACrB,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,cAAc,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE;gBAC9D,CAAC,CAAC,EAAE,CAAC;YACP,KAAK;YACL,OAAO,EAAE,KAAK,CAAC,MAAM,IAAI,SAAS;YAClC,OAAO;YACP,UAAU,EAAE,KAAK,CAAC,QAAQ,IAAI,eAAe;YAC7C,OAAO,EAAE,sBAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,IAAI,qBAAqB,CAAC;YACxE,WAAW;YACX,QAAQ,EAAE;gBACR,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,QAAQ;gBAChB,eAAe,EAAE,CAAC,YAAY,CAAC;gBAC/B,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC;aACrC;YACD,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,MAAuB;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;QAE3F,MAAM,EAAE,GAAG,IAAI,6BAAU,CAAC,IAAI,EAAE,YAAY,EAAE;YAC5C,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QAEH,IAAI,6BAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE;YACvC,GAAG,EAAE,IAAI,CAAC,OAAO;YACjB,UAAU,EAAE,EAAE;SACf,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY;YAC9B,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,wBAAwB,CAAC,IAAI,EAAE,YAAY,EAAE;gBAC9D,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,QAAQ,EAAE,MAAM,CAAC,cAAc;aAChC,CAAC;YACJ,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;QAE7F,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,mBAAmB,EAAE;YAC7C,IAAI;YACJ,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAAS,CACpC,IAAI,UAAU,CAAC,4BAA4B,CACzC,EAAE,CAAC,kBAAkB,EACrB,EAAE,CAAC,oBAAoB,CACxB,CACF;SACF,CAAC,CAAC;QAEH,OAAO,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC;IACxC,CAAC;CACF;AAxWD,8BAwWC;AAoBD,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,SAAS,CAAC,IAAoB;IACrC,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;IACrB,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAC7F,CAAC,CAAC,EAAE,EAAE,CAAC,iCAAc,CAAC,CAAgC,CAAC,IAAI,iCAAc,CAAC,GAAG,CAC9E,CAAC;IACF,MAAM,WAAW,GAAG;QAClB,cAAc;QACd,eAAe;QACf,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;KACnB,CAAC;IACF,OAAO;QACL,YAAY,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC;QAChC,YAAY,EAAE,OAAO;QACrB,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzE,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;QACpC,MAAM,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,sBAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS;KACxE,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAKzB;IACC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEnC,wDAAwD;IACxD,MAAM,MAAM,GAAgC,EAAE,CAAC;IAE/C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC;QAC9D,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACpC,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC;QACrE,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QAChE,CAAC;aAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YAC1B,YAAY;QACd,CAAC;aAAM,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,oBAAoB,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,iBAAiB,IAAI,KAAK;gBACtE,6CAA6C,CAChD,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;aAC1C,IAAI,MAAM,KAAK,OAAO;YAAE,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;;YACjD,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QAElC,oDAAoD;QACpD,gDAAgD;QAChD,EAAE,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,IAK9B;IACC,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACpC,MAAM,MAAM,GAAgC,EAAE,CAAC;IAE/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAgC,EAAE,CAAC;QAE/C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC;YACpC,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;gBAC3B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC;YACrE,CAAC;iBAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBAC7B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;YAChE,CAAC;iBAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;gBAC1B,YAAY;YACd,CAAC;iBAAM,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,KAAK;gBAC1C,6CAA6C,CAChD,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;aAC1C,IAAI,MAAM,KAAK,OAAO;YAAE,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;;YACjD,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QAElC,EAAE,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,QAAiC,EACjC,QAAqB;IAErB,IAAI,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/B,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IAC7E,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC;IAC9C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,CAAa;IAClC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,KAAK;YACR,OAAO,6BAAe,CAAC,GAAG,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,6BAAe,CAAC,IAAI,CAAC;QAC9B,KAAK,KAAK;YACR,OAAO,6BAAe,CAAC,GAAG,CAAC;QAC7B,KAAK,OAAO;YACV,OAAO,6BAAe,CAAC,KAAK,CAAC;QAC/B,KAAK,QAAQ;YACX,OAAO,6BAAe,CAAC,MAAM,CAAC;QAChC,KAAK,MAAM;YACT,OAAO,6BAAe,CAAC,IAAI,CAAC;QAC9B,KAAK,SAAS;YACZ,OAAO,6BAAe,CAAC,OAAO,CAAC;QACjC,KAAK,KAAK,CAAC;QACX;YACE,OAAO,6BAAe,CAAC,GAAG,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,KAAsB;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;SACpB,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACxD,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5E,OAAO,GAAG,MAAM,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC;AACtC,CAAC;AAED,oEAAoE;AACpE,SAAS,OAAO,CAAC,KAAsB;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;SACpB,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI;SACR,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,WAAW,EAAE,CAAC;AACnB,CAAC"}
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
+ }