Skip to content

Commit 01f8a68

Browse files
authored
feat(event-handler): AppSync Events resolver (#3858)
1 parent 68fa1eb commit 01f8a68

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2885
-70
lines changed

docs/features/event-handler/appsync-events.md

+410
Large diffs are not rendered by default.

docs/features/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ description: Features of Powertools for AWS Lambda
3131

3232
[:octicons-arrow-right-24: Read more](./metrics.md)
3333

34+
- __Event Handler - AppSync Events__
35+
36+
---
37+
38+
Event Handler for AWS AppSync real-time events
39+
40+
[:octicons-arrow-right-24: Read more](./event-handler/appsync-events.md)
41+
3442
- __Parameters__
3543

3644
---

docs/index.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,18 @@ You can use Powertools for AWS Lambda in both TypeScript and JavaScript code bas
4242

4343
Powertools for AWS Lambda (TypeScript) is built as a modular toolkit, so you can pick and choose the utilities you want to use. The following table lists the available utilities, and links to their documentation.
4444

45-
| Utility | Description |
46-
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- ------------------------------- |
47-
| [Tracer](./features/tracer.md) | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
48-
| [Logger](./features/logger.md) | Structured logging made easier, and a middleware to enrich structured logging with key Lambda context details |
49-
| [Metrics](./features/metrics.md) | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
50-
| [Parameters](./features/parameters.md) | High-level functions to retrieve one or more parameters from AWS SSM Parameter Store, AWS Secrets Manager, AWS AppConfig, and Amazon DynamoDB |
51-
| [Idempotency](./features/idempotency.md) | Class method decorator, Middy middleware, and function wrapper to make your Lambda functions idempotent and prevent duplicate execution based on payload content. |
52-
| [Batch Processing](./features/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
53-
| [JMESPath Functions](./features/jmespath.md) | Built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions. |
54-
| [Parser](./features/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
55-
| [Validation](./features/validation.md) | JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. |
45+
| Utility | Description |
46+
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47+
| [Tracer](./features/tracer.md) | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
48+
| [Logger](./features/logger.md) | Structured logging made easier, and a middleware to enrich structured logging with key Lambda context details |
49+
| [Metrics](./features/metrics.md) | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
50+
| [Event Handler - AppSync Events](./features/event-handler/appsync-events.md) | Event Handler for AWS AppSync real-time events |
51+
| [Parameters](./features/parameters.md) | High-level functions to retrieve one or more parameters from AWS SSM Parameter Store, AWS Secrets Manager, AWS AppConfig, and Amazon DynamoDB |
52+
| [Idempotency](./features/idempotency.md) | Class method decorator, Middy middleware, and function wrapper to make your Lambda functions idempotent and prevent duplicate execution based on payload content. |
53+
| [Batch Processing](./features/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
54+
| [JMESPath Functions](./features/jmespath.md) | Built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions. |
55+
| [Parser](./features/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
56+
| [Validation](./features/validation.md) | JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. |
5657

5758
## Examples
5859

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { Context } from 'aws-lambda';
3+
4+
const app = new AppSyncEventsResolver();
5+
6+
app.onPublish('/*', (payload, event, context) => {
7+
const { headers } = event.request; // (1)!
8+
const { awsRequestId } = context;
9+
10+
// your business logic here
11+
12+
return payload;
13+
});
14+
15+
export const handler = async (event: unknown, context: Context) =>
16+
await app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import {
3+
BatchWriteItemCommand,
4+
DynamoDBClient,
5+
type WriteRequest,
6+
} from '@aws-sdk/client-dynamodb';
7+
import { marshall } from '@aws-sdk/util-dynamodb';
8+
import type { Context } from 'aws-lambda';
9+
10+
const ddbClient = new DynamoDBClient();
11+
const app = new AppSyncEventsResolver();
12+
13+
app.onPublish(
14+
'/default/foo/*',
15+
async (payloads) => {
16+
const writeOperations: WriteRequest[] = [];
17+
for (const payload of payloads) {
18+
writeOperations.push({
19+
PutRequest: {
20+
Item: marshall(payload),
21+
},
22+
});
23+
}
24+
await ddbClient.send(
25+
new BatchWriteItemCommand({
26+
RequestItems: {
27+
'your-table-name': writeOperations,
28+
},
29+
})
30+
);
31+
32+
return payloads;
33+
},
34+
{ aggregate: true }
35+
);
36+
37+
export const handler = async (event: unknown, context: Context) =>
38+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { OnPublishAggregateOutput } from '@aws-lambda-powertools/event-handler/types';
3+
import { Logger } from '@aws-lambda-powertools/logger';
4+
import type { Context } from 'aws-lambda';
5+
6+
const logger = new Logger({
7+
serviceName: 'serverlessAirline',
8+
logLevel: 'INFO',
9+
});
10+
const app = new AppSyncEventsResolver();
11+
12+
app.onPublish(
13+
'/default/foo/*',
14+
async (payloads) => {
15+
const returnValues: OnPublishAggregateOutput<{
16+
processed: boolean;
17+
original_payload: unknown;
18+
}> = [];
19+
try {
20+
for (const payload of payloads) {
21+
returnValues.push({
22+
id: payload.id,
23+
payload: { processed: true, original_payload: payload },
24+
});
25+
}
26+
} catch (error) {
27+
logger.error('Error processing payloads', { error });
28+
throw error;
29+
}
30+
31+
return returnValues;
32+
},
33+
{ aggregate: true }
34+
);
35+
36+
export const handler = async (event: unknown, context: Context) =>
37+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import { Logger } from '@aws-lambda-powertools/logger';
3+
import type { Context } from 'aws-lambda';
4+
5+
const logger = new Logger({
6+
serviceName: 'serverlessAirline',
7+
logLevel: 'DEBUG',
8+
});
9+
const app = new AppSyncEventsResolver({ logger });
10+
11+
app.onPublish('/default/foo', (payload) => {
12+
return payload;
13+
});
14+
15+
export const handler = async (event: unknown, context: Context) =>
16+
await app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { OnPublishAggregateOutput } from '@aws-lambda-powertools/event-handler/types';
3+
import type { Context } from 'aws-lambda';
4+
5+
const app = new AppSyncEventsResolver();
6+
7+
app.onPublish(
8+
'/default/foo/*',
9+
async (payloads) => {
10+
const returnValues: OnPublishAggregateOutput<{
11+
processed: boolean;
12+
original_payload: unknown;
13+
}> = [];
14+
for (const payload of payloads) {
15+
try {
16+
returnValues.push({
17+
id: payload.id,
18+
payload: { processed: true, original_payload: payload },
19+
});
20+
} catch (error) {
21+
returnValues.push({
22+
id: payload.id,
23+
error: `${error.name} - ${error.message}`,
24+
});
25+
}
26+
}
27+
28+
return returnValues;
29+
},
30+
{ aggregate: true }
31+
);
32+
33+
export const handler = async (event: unknown, context: Context) =>
34+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import { Logger } from '@aws-lambda-powertools/logger';
3+
import type { Context } from 'aws-lambda';
4+
5+
const logger = new Logger({
6+
serviceName: 'appsync-events',
7+
logLevel: 'DEBUG',
8+
});
9+
const app = new AppSyncEventsResolver();
10+
11+
app.onPublish('/default/foo', (payload) => {
12+
try {
13+
return payload;
14+
} catch (error) {
15+
logger.error('Error processing event', { error });
16+
throw error;
17+
}
18+
});
19+
20+
export const handler = async (event: unknown, context: Context) =>
21+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { Context } from 'aws-lambda';
3+
4+
const app = new AppSyncEventsResolver();
5+
6+
app.onPublish('/default/foo', (payload) => {
7+
return {
8+
processed: true,
9+
original_payload: payload,
10+
};
11+
});
12+
13+
export const handler = async (event: unknown, context: Context) =>
14+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { AppSyncEventsPublishEvent } from '@aws-lambda-powertools/event-handler/types';
3+
import type { Context } from 'aws-lambda';
4+
5+
const app = new AppSyncEventsResolver();
6+
7+
class Lambda {
8+
@app.onPublish('/default/foo')
9+
async fooHandler(payload: AppSyncEventsPublishEvent) {
10+
return {
11+
processed: true,
12+
original_payload: payload,
13+
};
14+
}
15+
16+
async handler(event: unknown, context: Context) {
17+
return app.resolve(event, context);
18+
}
19+
}
20+
21+
const lambda = new Lambda();
22+
export const handler = lambda.handler.bind(lambda);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics';
3+
import type { Context } from 'aws-lambda';
4+
5+
const metrics = new Metrics({
6+
namespace: 'serverlessAirline',
7+
serviceName: 'chat',
8+
singleMetric: true,
9+
});
10+
const app = new AppSyncEventsResolver();
11+
12+
app.onSubscribe('/default/foo', (event) => {
13+
metrics.addDimension('channel', event.info.channel.path);
14+
metrics.addMetric('connections', MetricUnit.Count, 1);
15+
});
16+
17+
export const handler = async (event: unknown, context: Context) =>
18+
app.resolve(event, context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
2+
import type { AppSyncEventsSubscribeEvent } from '@aws-lambda-powertools/event-handler/types';
3+
import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics';
4+
import type { Context } from 'aws-lambda';
5+
6+
const metrics = new Metrics({
7+
namespace: 'serverlessAirline',
8+
serviceName: 'chat',
9+
singleMetric: true,
10+
});
11+
const app = new AppSyncEventsResolver();
12+
13+
class Lambda {
14+
@app.onSubscribe('/default/foo')
15+
async fooHandler(event: AppSyncEventsSubscribeEvent) {
16+
metrics.addDimension('channel', event.info.channel.path);
17+
metrics.addMetric('connections', MetricUnit.Count, 1);
18+
}
19+
20+
async handler(event: unknown, context: Context) {
21+
return app.resolve(event, context);
22+
}
23+
}
24+
25+
const lambda = new Lambda();
26+
export const handler = lambda.handler.bind(lambda);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"error": "Error - An unexpected error occurred"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"level": "DEBUG",
4+
"message": "Registering onPublish route handler for path '/default/foo' with aggregate 'false'",
5+
"timestamp": "2025-04-22T13:24:34.762Z",
6+
"service": "serverlessAirline",
7+
"sampling_rate": 0
8+
},
9+
{
10+
"level": "DEBUG",
11+
"message": "Resolving handler for path '/default/foo'",
12+
"timestamp": "2025-04-22T13:24:34.775Z",
13+
"service": "serverlessAirline",
14+
"sampling_rate": 0,
15+
"xray_trace_id": "1-68079892-6a1723770bc0b1f348d9a7ad"
16+
}
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"events": [
3+
{
4+
"error": "SyntaxError - Invalid item",
5+
"id": "1"
6+
},
7+
{
8+
"payload": {
9+
"processed": true,
10+
"original_payload": {
11+
"event_2": "data_2"
12+
}
13+
},
14+
"id": "2"
15+
}
16+
]
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"events": [
3+
{
4+
"error": "Error message",
5+
"id": "1"
6+
},
7+
{
8+
"payload": {
9+
"data": "data_2"
10+
},
11+
"id": "2"
12+
}
13+
]
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"identity": "None",
3+
"result": "None",
4+
"request": {
5+
"headers": {
6+
"x-forwarded-for": "1.1.1.1, 2.2.2.2",
7+
"cloudfront-viewer-country": "US"
8+
},
9+
"domainName": "None"
10+
},
11+
"info": {
12+
"channel": {
13+
"path": "/default/channel",
14+
"segments": [
15+
"default",
16+
"channel"
17+
]
18+
},
19+
"channelNamespace": {
20+
"name": "default"
21+
},
22+
"operation": "PUBLISH"
23+
},
24+
"error": "None",
25+
"prev": "None",
26+
"stash": {},
27+
"outErrors": [],
28+
"events": [
29+
{
30+
"payload": {
31+
"data": "data_1"
32+
},
33+
"id": "1"
34+
},
35+
{
36+
"payload": {
37+
"data": "data_2"
38+
},
39+
"id": "2"
40+
}
41+
]
42+
}

0 commit comments

Comments
 (0)