@ashishpandeyone/cdk-lambda-web-function 0.1.0 → 0.1.2

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 CHANGED
@@ -59,7 +59,7 @@ Requests carry `lambda-web-request-id` and `lambda-web-endpoint-name` headers.
59
59
 
60
60
  ## Regions and endpoints
61
61
 
62
- Each `WebFunction` has one endpoint, `fn.endpoint`:
62
+ Each `WebFunction` has a default endpoint, `fn.endpoint`, configured with the `endpoint` property. [Add more](#more-endpoints) with `fn.addEndpoint()`.
63
63
 
64
64
  | `endpointType` | URL | Routing |
65
65
  |---|---|---|
@@ -84,6 +84,34 @@ new CfnOutput(this, 'UsEast1Url', { value: perRegion.endpoint.regionalUrl('us-ea
84
84
 
85
85
  - `MULTI_REGION` and `PER_REGION` need at least one region besides the home region; `HOME_REGION` takes none.
86
86
  - A MultiRegion endpoint takes about a minute to create.
87
+ - Lambda routes a `MULTI_REGION` endpoint by latency, not by health: it doesn't fail over when a region has problems.
88
+
89
+ ### More endpoints
90
+
91
+ A function can have up to 10 endpoints, each with its own URL, authentication, regions and limits. For example, a public endpoint next to an IAM-only global one:
92
+
93
+ ```ts
94
+ const api = new WebFunction(this, 'Api', {
95
+ runtime: lambda.Runtime.NODEJS_24_X,
96
+ code: lambda.Code.fromAsset('dist'),
97
+ authType: WebFunctionAuthType.NONE, // the default endpoint is public
98
+ endpoint: { rateLimit: 1000 }, // requests per second
99
+ });
100
+
101
+ const internal = api.addEndpoint('Internal', {
102
+ authType: WebFunctionAuthType.AWS_IAM,
103
+ endpointType: WebFunctionEndpointType.MULTI_REGION,
104
+ regions: ['us-east-1', 'eu-west-1'],
105
+ description: 'Service-to-service traffic',
106
+ maxEnvironments: 50,
107
+ });
108
+ internal.grantInvoke(consumerRole);
109
+ new CfnOutput(this, 'InternalUrl', { value: internal.url });
110
+ ```
111
+
112
+ - `rateLimit` takes 0 (blocks all traffic), 100–1,000 in steps of 100, or 2,000–10,000 in steps of 1,000; excess requests get HTTP 429. `maxEnvironments` takes 2–10,000.
113
+ - **Both limits apply separately in each region** an endpoint serves from: a 3-region endpoint with `rateLimit: 1000` accepts up to 1,000 requests per second in each region.
114
+ - Endpoints can serve the same regions. The construct owns one `.local` log group per region, whichever endpoints serve it.
87
115
 
88
116
  ### Changing regions or the endpoint type
89
117
 
@@ -91,7 +119,7 @@ An endpoint's type and regions can't change in place: a change creates a **new e
91
119
 
92
120
  | `endpointName` | Physical name | Changing `regions` or `endpointType` |
93
121
  |---|---|---|
94
- | omitted (recommended) | `default-<hash of type and regions>` | works: new endpoint, new URL |
122
+ | omitted (recommended) | `default-<hash>` for `fn.endpoint`, `<id>-<hash>` for added endpoints; the hash covers the type and regions | works: new endpoint, new URL |
95
123
  | set, e.g. `'live'` | `live` | also change the name in the same deployment, or it fails with *The endpoint live already exists* (the stack rolls back and the old endpoint keeps serving) |
96
124
 
97
125
  The construct warns at synth time when `endpointName` is set.
@@ -107,7 +135,7 @@ The construct warns at synth time when `endpointName` is set.
107
135
  fn.grants.invoke(consumerRole); // or fn.grantInvoke(consumerRole), as on lambda.Function
108
136
  ```
109
137
 
110
- The grant is on the endpoint's home-region ARN, which IAM evaluates in every region the endpoint serves from, including the global URL.
138
+ The grant is on the endpoint's home-region ARN, which IAM evaluates in every region the endpoint serves from, including the global URL. `fn.grants` covers the default endpoint only; grant other endpoints with their own `grantInvoke()` or `grants.invoke()`.
111
139
 
112
140
  | Endpoint | How to sign |
113
141
  |---|---|
@@ -151,15 +179,114 @@ table.grantReadWriteData(api); // the default role trusts lambda.amazo
151
179
  - Environment variable names starting with `AWS_` are reserved.
152
180
  - **Renaming the function replaces it and changes its URL.** A generated name depends on the construct path, so moving the construct renames it too.
153
181
 
182
+ ### Bundling TypeScript with `NodejsWebFunction`
183
+
184
+ `NodejsWebFunction` is a `WebFunction` that bundles your server from a TypeScript or JavaScript entry file with esbuild, like `NodejsFunction` does for Lambda functions:
185
+
186
+ ```ts
187
+ new NodejsWebFunction(this, 'Api', {
188
+ entry: 'src/server.ts',
189
+ authType: WebFunctionAuthType.AWS_IAM,
190
+ bundling: {
191
+ minify: true, // default true
192
+ sourceMap: true, // default true
193
+ format: lambdaNodejs.OutputFormat.ESM, // default ESM (index.mjs); CJS gives index.js
194
+ externalModules: [], // default: bundle everything you import
195
+ },
196
+ });
197
+ ```
198
+
199
+ - **Defaults:** `runtime` is `NODEJS_24_X`, and the esbuild target follows the runtime (`node24`). The project root is the directory of the nearest lock file above `entry` (override it with `depsLockFilePath` or `projectRoot`).
200
+ - **It also sets what a `lambda.Code` can't:** the entry point that matches the bundle, and `NODE_OPTIONS=--enable-source-maps` when source maps are on, appended to any `NODE_OPTIONS` you set, so stack traces point at your `.ts` files (e.g. `/var/task/src/server.ts:22:12`). Source map paths are relative to the project root, so no build-machine paths are deployed.
201
+ - **ESM bundles include a `require` shim**, so CommonJS dependencies that load Node.js built-ins (Express, for example) work.
202
+ - **The AWS SDK is bundled like any other dependency**, since the runtime has none. Only the clients you import are included.
203
+ - **Bundling runs locally** when esbuild is installed in your project, and otherwise in Docker (an arm64 AWS SAM build image; set `forceDockerBundling` to always use Docker).
204
+ - **Not yet supported:** installing native modules next to the bundle (`nodeModules`) and command hooks. For native dependencies, use `WebFunction` with `Code.fromAsset(..., { bundling: { image, command } })`.
205
+
154
206
  ### Code
155
207
 
156
208
  `code` takes any `lambda.Code` that produces a zip in S3: `fromAsset`, `fromBucket`/`fromBucketV2`, `fromCustomCommand`, `fromDockerBuild`. Inline code and container images fail at synth time.
157
209
 
158
210
  Lambda Web Functions download code as the `lambda.amazonaws.com` service principal, from a **versioned** bucket, and the CDK bootstrap bucket allows neither. So each stack gets a small code bucket, and the code is copied into it on deploy.
159
211
 
160
- ### Updates
212
+ ## Revisions
213
+
214
+ Any change to code or configuration creates a new **revision** (`fn.currentRevision`), and endpoints switch to it once it's ready. A failed deployment rolls back to the previous revision. If nothing changed, nothing is deployed.
215
+
216
+ Earlier revisions are kept, so you can roll back or route traffic to them. After each deployment, the construct deletes the oldest beyond `revisionRetention` (default 20, up to 50). It never deletes a revision an endpoint routes to, or one the previous deployment used, which a rollback returns to.
217
+
218
+ ```ts
219
+ new WebFunction(this, 'Api', {
220
+ runtime: lambda.Runtime.NODEJS_24_X,
221
+ code: lambda.Code.fromAsset('dist'),
222
+ authType: WebFunctionAuthType.AWS_IAM,
223
+ revisionRetention: 5,
224
+ });
225
+ ```
226
+
227
+ ### Routing to earlier revisions
228
+
229
+ Reference an earlier revision by its ID (for example from a `CfnOutput` of `fn.currentRevision.revisionId`, or `aws lambda-web list-web-function-revisions`). Then pin an endpoint to it, or split traffic between two revisions:
230
+
231
+ ```ts
232
+ const previous = WebFunctionRevision.fromRevisionAttributes(this, 'Previous', {
233
+ webFunction: fn,
234
+ revisionId: 'rev-a1b2c',
235
+ });
236
+
237
+ // Keep the default endpoint on the previous revision, and send 10% to the new one:
238
+ fn.endpoint.routeTraffic(previous, [{ revision: fn.currentRevision, weight: 0.1 }]);
239
+
240
+ // Or a second endpoint that stays on the previous revision while fn.endpoint moves on:
241
+ fn.addEndpoint('Stable', { authType: WebFunctionAuthType.AWS_IAM, revision: previous });
242
+ ```
243
+
244
+ - An endpoint routes to at most two revisions. The additional revision's `weight` is a whole percentage, from 0.01 to 0.99.
245
+ - To promote, remove `routeTraffic` (or `revision`) and deploy: the endpoint serves the current revision again.
246
+ - Lambda Web Function metrics have no revision dimension. To compare a split's revisions, log a version you control (for example an environment variable) from your application.
247
+
248
+ ### Canary and linear deployments
249
+
250
+ By default an endpoint moves all its traffic to a new revision at once. With `deployment`, it moves in steps instead, watching CloudWatch alarms: if one goes into ALARM, the endpoint goes back to the revision it served before and the deployment fails, so CloudFormation rolls the stack back.
251
+
252
+ ```ts
253
+ // errorRate: a cloudwatch.IAlarm, for example on the share of 5xx responses
254
+ const api = new WebFunction(this, 'Api', {
255
+ runtime: lambda.Runtime.NODEJS_24_X,
256
+ code: lambda.Code.fromAsset('dist'),
257
+ authType: WebFunctionAuthType.AWS_IAM,
258
+ endpoint: {
259
+ // 10% of the traffic for 10 minutes, then all of it.
260
+ deployment: WebFunctionDeployment.canary({ percent: 10, bake: Duration.minutes(10), alarms: [errorRate] }),
261
+ },
262
+ });
263
+
264
+ // 25% more every 5 minutes. Each endpoint has its own deployment.
265
+ api.addEndpoint('Public', {
266
+ authType: WebFunctionAuthType.NONE,
267
+ deployment: WebFunctionDeployment.linear({ percent: 25, interval: Duration.minutes(5), alarms: [errorRate] }),
268
+ });
269
+ ```
270
+
271
+ | | Steps | Limits |
272
+ |---|---|---|
273
+ | `allAtOnce()` (default) | 100% | |
274
+ | `canary({ percent, bake })` | `percent`, then 100% after `bake` | `percent` 1–99, `bake` 1–45 minutes |
275
+ | `linear({ percent, interval })` | `percent` more every `interval` | `percent` 5–50, `interval` from 1 minute, at most 45 minutes in all |
276
+
277
+ How it works:
278
+
279
+ - Each deployment that creates a revision (code or configuration) moves the traffic from the revision the endpoint serves at that moment, which is also what it goes back to. If someone switched the endpoint outside CloudFormation, the deployment starts from there.
280
+ - Each step waits until every region of a `MULTI_REGION` or `PER_REGION` endpoint has applied it (usually about 15 seconds), and if a region fails to update, the endpoint goes back as for an alarm. The bake or interval counts from the start of the step.
281
+ - Alarms are checked before the first step (an alarm already in ALARM stops the deployment before any traffic moves), throughout every step, and once the new revision has all the traffic. They can be in any region, and composite alarms work. `INSUFFICIENT_DATA` doesn't stop a deployment.
282
+ - A rollback of the stack, for any reason, moves the traffic back at once, without waiting or checking alarms.
283
+ - A stack's first deployment, and a new endpoint (including one renamed by a region change), serve the revision at once: there's nothing to move from.
284
+ - A deployment can take up to an hour: CloudFormation waits for it before it updates the endpoint. Deployments of different endpoints run in parallel.
285
+ - A canary or linear endpoint always moves to the current revision, so it can't also use `revision`, `additionalRevisions` or `routeTraffic()`. Use `allAtOnce()` to deploy a fix immediately, for example while an alarm is in ALARM.
286
+
287
+ Lambda Web Function metrics have no revision dimension, so an alarm sees the whole endpoint: at 10%, a new revision's errors are a tenth of the endpoint's. Alarm on a rate, with a threshold low enough to notice that, or publish metrics from your application that carry its version. A bake should be longer than an alarm needs to fire (its period times its evaluation periods).
161
288
 
162
- Any change to code or configuration creates a new **revision**. The endpoint switches to it once it's ready, and the old revision is deleted after the deployment succeeds; a failed deployment rolls back to it. If nothing changed, nothing is deployed.
289
+ The deployment runs in a few resources per stack, created on first use: two Lambda functions, a Step Functions state machine that calls them every 15 seconds, and a log group.
163
290
 
164
291
  ## Logs in every region
165
292
 
@@ -195,10 +322,9 @@ new WebFunction(this, 'Api', {
195
322
 
196
323
  ## Limitations
197
324
 
198
- - One endpoint per function; no traffic splitting between revisions.
199
325
  - Not yet supported: importing existing functions, VPC access (network connectors), resource-policy permissions, KMS encryption, metrics helpers, tracing, and a CloudFront origin.
200
326
  - `cdk deploy --hotswap` and `cdk watch` don't support Web Functions.
201
- - Quotas per account and region: 50 revisions and 10 endpoints per function, 200 Arm vCPUs, 10,000 requests per second.
327
+ - Quotas: 10 endpoints and about 50 revisions per function (Lambda cleans up older revisions automatically), and 10,000 requests per second per endpoint. Per account and region: 100,000 requests per second and 2,000 Arm64 vCPUs (each execution environment uses 2). The account quotas are adjustable, and new accounts start lower. See [Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html).
202
328
  - The stack's custom resource provider Lambda keeps its own log group after the stack is deleted, as with other CDK custom resources.
203
329
 
204
330
  ## Development