Skip to content

feat(event-handler): AppSync Events resolver #3858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
410 changes: 410 additions & 0 deletions docs/features/event-handler/appsync-events.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions docs/features/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ description: Features of Powertools for AWS Lambda

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

- __Event Handler - AppSync Events__

---

Event Handler for AWS AppSync real-time events

[:octicons-arrow-right-24: Read more](./event-handler/appsync-events.md)

- __Parameters__

---
Expand Down
23 changes: 12 additions & 11 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,18 @@ You can use Powertools for AWS Lambda in both TypeScript and JavaScript code bas

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.

| Utility | Description |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- ------------------------------- |
| [Tracer](./features/tracer.md) | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
| [Logger](./features/logger.md) | Structured logging made easier, and a middleware to enrich structured logging with key Lambda context details |
| [Metrics](./features/metrics.md) | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
| [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 |
| [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. |
| [Batch Processing](./features/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
| [JMESPath Functions](./features/jmespath.md) | Built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions. |
| [Parser](./features/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
| [Validation](./features/validation.md) | JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. |
| Utility | Description |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Tracer](./features/tracer.md) | Decorators and utilities to trace Lambda function handlers, and both synchronous and asynchronous functions |
| [Logger](./features/logger.md) | Structured logging made easier, and a middleware to enrich structured logging with key Lambda context details |
| [Metrics](./features/metrics.md) | Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) |
| [Event Handler - AppSync Events](./features/event-handler/appsync-events.md) | Event Handler for AWS AppSync real-time events |
| [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 |
| [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. |
| [Batch Processing](./features/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
| [JMESPath Functions](./features/jmespath.md) | Built-in JMESPath functions to easily deserialize common encoded JSON payloads in Lambda functions. |
| [Parser](./features/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
| [Validation](./features/validation.md) | JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. |

## Examples

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { Context } from 'aws-lambda';

const app = new AppSyncEventsResolver();

app.onPublish('/*', (payload, event, context) => {
const { headers } = event.request; // (1)!
const { awsRequestId } = context;

// your business logic here

return payload;
});

export const handler = async (event: unknown, context: Context) =>
await app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import {
BatchWriteItemCommand,
DynamoDBClient,
type WriteRequest,
} from '@aws-sdk/client-dynamodb';
import { marshall } from '@aws-sdk/util-dynamodb';
import type { Context } from 'aws-lambda';

const ddbClient = new DynamoDBClient();
const app = new AppSyncEventsResolver();

app.onPublish(
'/default/foo/*',
async (payloads) => {
const writeOperations: WriteRequest[] = [];
for (const payload of payloads) {
writeOperations.push({
PutRequest: {
Item: marshall(payload),
},
});
}
await ddbClient.send(
new BatchWriteItemCommand({
RequestItems: {
'your-table-name': writeOperations,
},
})
);

return payloads;
},
{ aggregate: true }
);

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { OnPublishAggregateOutput } from '@aws-lambda-powertools/event-handler/types';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger({
serviceName: 'serverlessAirline',
logLevel: 'INFO',
});
const app = new AppSyncEventsResolver();

app.onPublish(
'/default/foo/*',
async (payloads) => {
const returnValues: OnPublishAggregateOutput<{
processed: boolean;
original_payload: unknown;
}> = [];
try {
for (const payload of payloads) {
returnValues.push({
id: payload.id,
payload: { processed: true, original_payload: payload },
});
}
} catch (error) {
logger.error('Error processing payloads', { error });
throw error;
}

return returnValues;
},
{ aggregate: true }
);

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
16 changes: 16 additions & 0 deletions examples/snippets/event-handler/appsync-events/debugLogging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger({
serviceName: 'serverlessAirline',
logLevel: 'DEBUG',
});
const app = new AppSyncEventsResolver({ logger });

app.onPublish('/default/foo', (payload) => {
return payload;
});

export const handler = async (event: unknown, context: Context) =>
await app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { OnPublishAggregateOutput } from '@aws-lambda-powertools/event-handler/types';
import type { Context } from 'aws-lambda';

const app = new AppSyncEventsResolver();

app.onPublish(
'/default/foo/*',
async (payloads) => {
const returnValues: OnPublishAggregateOutput<{
processed: boolean;
original_payload: unknown;
}> = [];
for (const payload of payloads) {
try {
returnValues.push({
id: payload.id,
payload: { processed: true, original_payload: payload },
});
} catch (error) {
returnValues.push({
id: payload.id,
error: `${error.name} - ${error.message}`,
});
}
}

return returnValues;
},
{ aggregate: true }
);

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger({
serviceName: 'appsync-events',
logLevel: 'DEBUG',
});
const app = new AppSyncEventsResolver();

app.onPublish('/default/foo', (payload) => {
try {
return payload;
} catch (error) {
logger.error('Error processing event', { error });
throw error;
}
});

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { Context } from 'aws-lambda';

const app = new AppSyncEventsResolver();

app.onPublish('/default/foo', (payload) => {
return {
processed: true,
original_payload: payload,
};
});

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { AppSyncEventsPublishEvent } from '@aws-lambda-powertools/event-handler/types';
import type { Context } from 'aws-lambda';

const app = new AppSyncEventsResolver();

class Lambda {
@app.onPublish('/default/foo')
async fooHandler(payload: AppSyncEventsPublishEvent) {
return {
processed: true,
original_payload: payload,
};
}

async handler(event: unknown, context: Context) {
return app.resolve(event, context);
}
}

const lambda = new Lambda();
export const handler = lambda.handler.bind(lambda);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics';
import type { Context } from 'aws-lambda';

const metrics = new Metrics({
namespace: 'serverlessAirline',
serviceName: 'chat',
singleMetric: true,
});
const app = new AppSyncEventsResolver();

app.onSubscribe('/default/foo', (event) => {
metrics.addDimension('channel', event.info.channel.path);
metrics.addMetric('connections', MetricUnit.Count, 1);
});

export const handler = async (event: unknown, context: Context) =>
app.resolve(event, context);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AppSyncEventsResolver } from '@aws-lambda-powertools/event-handler/appsync-events';
import type { AppSyncEventsSubscribeEvent } from '@aws-lambda-powertools/event-handler/types';
import { MetricUnit, Metrics } from '@aws-lambda-powertools/metrics';
import type { Context } from 'aws-lambda';

const metrics = new Metrics({
namespace: 'serverlessAirline',
serviceName: 'chat',
singleMetric: true,
});
const app = new AppSyncEventsResolver();

class Lambda {
@app.onSubscribe('/default/foo')
async fooHandler(event: AppSyncEventsSubscribeEvent) {
metrics.addDimension('channel', event.info.channel.path);
metrics.addMetric('connections', MetricUnit.Count, 1);
}

async handler(event: unknown, context: Context) {
return app.resolve(event, context);
}
}

const lambda = new Lambda();
export const handler = lambda.handler.bind(lambda);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"error": "Error - An unexpected error occurred"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{
"level": "DEBUG",
"message": "Registering onPublish route handler for path '/default/foo' with aggregate 'false'",
"timestamp": "2025-04-22T13:24:34.762Z",
"service": "serverlessAirline",
"sampling_rate": 0
},
{
"level": "DEBUG",
"message": "Resolving handler for path '/default/foo'",
"timestamp": "2025-04-22T13:24:34.775Z",
"service": "serverlessAirline",
"sampling_rate": 0,
"xray_trace_id": "1-68079892-6a1723770bc0b1f348d9a7ad"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"events": [
{
"error": "SyntaxError - Invalid item",
"id": "1"
},
{
"payload": {
"processed": true,
"original_payload": {
"event_2": "data_2"
}
},
"id": "2"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"events": [
{
"error": "Error message",
"id": "1"
},
{
"payload": {
"data": "data_2"
},
"id": "2"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"identity": "None",
"result": "None",
"request": {
"headers": {
"x-forwarded-for": "1.1.1.1, 2.2.2.2",
"cloudfront-viewer-country": "US"
},
"domainName": "None"
},
"info": {
"channel": {
"path": "/default/channel",
"segments": [
"default",
"channel"
]
},
"channelNamespace": {
"name": "default"
},
"operation": "PUBLISH"
},
"error": "None",
"prev": "None",
"stash": {},
"outErrors": [],
"events": [
{
"payload": {
"data": "data_1"
},
"id": "1"
},
{
"payload": {
"data": "data_2"
},
"id": "2"
}
]
}
Loading