diff --git a/docs/features/parser.md b/docs/features/parser.md
index b60feed4da..59f961258f 100644
--- a/docs/features/parser.md
+++ b/docs/features/parser.md
@@ -1,63 +1,57 @@
---
-title: Parser (Zod)
+title: Parser (Standard Schema)
descrition: Utility
---
-This utility provides data validation and parsing using [Zod](https://zod.dev){target="_blank"}, a TypeScript-first schema declaration and validation library.
+This utility provides data validation and parsing for [Standard Schema](https://github.com/standard-schema/standard-schema){target="_blank"}, together with a collection of built-in [Zod](https://zod.dev){target="_blank"} schemas and envelopes to parse and unwrap popular AWS event sources payloads.
## Key features
-* Define data schema as Zod schema, then parse, validate and extract only what you want
-* Built-in envelopes to unwrap and validate popular AWS event sources payloads
-* Extend and customize envelopes to fit your needs
-* Safe parsing option to avoid throwing errors and custom error handling
-* Available for Middy.js middleware and TypeScript method decorators
+* Accept a [Standard Schema](https://github.com/standard-schema/standard-schema) and parse incoming payloads
+* Built-in Zod schemas and envelopes to unwrap and validate popular AWS event sources payloads
+* Extend and customize built-in Zod schemas to fit your needs
+* Safe parsing option to avoid throwing errors and allow custom error handling
+* Available as Middy.js middleware and TypeScript class method decorator
## Getting started
```bash
-npm install @aws-lambda-powertools/parser zod@~3
+npm install @aws-lambda-powertools/parser zod
```
-!!! warning "Zod version"
- The package is compatible with Zod v3 only.
- We're considering Zod v4 support and we'd love to hear your feedback. Please [leave a comment here](https://github.com/aws-powertools/powertools-lambda-typescript/issues/3951) to let us know your thoughts.
-
-## Define schema
-
-You can define your schema using Zod:
-
-```typescript title="schema.ts"
---8<-- "examples/snippets/parser/schema.ts"
-```
-
-This is a schema for `Order` object using Zod.
-You can create complex schemas by using nested objects, arrays, unions, and other types, see [Zod documentation](https://zod.dev) for more details.
-
## Parse events
You can parse inbound events using `parser` decorator, Middy.js middleware, or [manually](#manual-parsing) using built-in envelopes and schemas.
-Both are also able to parse either an object or JSON string as an input.
-???+ warning
- The decorator and middleware will replace the event object with the parsed schema if successful.
- Be cautious when using multiple decorators that expect event to have a specific structure, the order of evaluation for decorators is from bottom to top.
+When using the decorator or middleware, you can specify a schema to parse the event, this can be a [built-in Zod schema](#built-in-schemas) or a custom schema you defined. Custom schemas can be defined using Zod or any other [Standard Schema compatible library](https://standardschema.dev/#what-schema-libraries-implement-the-spec){target="_blank"}.
-=== "Middy middleware"
+=== "Middy.js middleware with Zod schema"
```typescript hl_lines="22"
--8<-- "examples/snippets/parser/middy.ts"
```
+=== "Middy.js middleware with Valibot schema"
+ ```typescript hl_lines="30"
+ --8<-- "examples/snippets/parser/middyValibot.ts"
+ ```
+
=== "Decorator"
+ !!! warning
+ The decorator and middleware will replace the event object with the parsed schema if successful.
+ Be cautious when using multiple decorators that expect an event to have a specific structure, the order of evaluation for decorators is from the inner to the outermost decorator.
+
```typescript hl_lines="25"
--8<-- "examples/snippets/parser/decorator.ts"
```
## Built-in schemas
-**Parser** comes with the following built-in schemas:
+**Parser** comes with the following built-in Zod schemas:
+
+!!! note "Looking for other libraries?"
+ The built-in schemas are defined using Zod, if you would like us to support other libraries like [valibot](https://valibot.dev){target="_blank"} please [open an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new?template=feature_request.yml){target="_blank"} and we will consider it based on the community's feedback.
| Model name | Description |
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
@@ -198,7 +192,7 @@ This can become difficult quite quickly. Parser simplifies the development throu
Envelopes can be used via envelope parameter available in middy and decorator.
Here's an example of parsing a custom schema in an event coming from EventBridge, where all you want is what's inside the detail key.
-=== "Middy middleware"
+=== "Middy.js middleware"
```typescript hl_lines="23"
--8<-- "examples/snippets/parser/envelopeMiddy.ts"
```
@@ -221,24 +215,27 @@ We have also complex envelopes that parse the payload from a string, decode base
### Built-in envelopes
-Parser comes with the following built-in envelopes:
+Parser comes with the following built-in Zod envelopes:
+
+!!! note "Looking for other libraries?"
+ The built-in schemas are defined using Zod, if you would like us to support other libraries like [valibot](https://valibot.dev){target="_blank"} please [open an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new?template=feature_request.yml){target="_blank"} and we will consider it based on the community's feedback.
| Envelope name | Behaviour |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **apiGatewayEnvelope** | 1. Parses data using `APIGatewayProxyEventSchema`.
2. Parses `body` key using your schema and returns it. |
-| **apiGatewayV2Envelope** | 1. Parses data using `APIGatewayProxyEventV2Schema`.
2. Parses `body` key using your schema and returns it. |
-| **cloudWatchEnvelope** | 1. Parses data using `CloudwatchLogsSchema` which will base64 decode and decompress it.
2. Parses records in `message` key using your schema and return them in a list. |
-| **dynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamSchema`.
2. Parses records in `NewImage` and `OldImage` keys using your schema.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys |
-| **eventBridgeEnvelope** | 1. Parses data using `EventBridgeSchema`.
2. Parses `detail` key using your schema and returns it. |
-| **kafkaEnvelope** | 1. Parses data using `KafkaRecordSchema`.
2. Parses `value` key using your schema and returns it. |
-| **kinesisEnvelope** | 1. Parses data using `KinesisDataStreamSchema` which will base64 decode it.
2. Parses records in `Records` key using your schema and returns them in a list. |
-| **kinesisFirehoseEnvelope** | 1. Parses data using `KinesisFirehoseSchema` which will base64 decode it.
2. Parses records in `Records` key using your schema and returns them in a list. |
-| **lambdaFunctionUrlEnvelope** | 1. Parses data using `LambdaFunctionUrlSchema`.
2. Parses `body` key using your schema and returns it. |
-| **snsEnvelope** | 1. Parses data using `SnsSchema`.
2. Parses records in `body` key using your schema and return them in a list. |
-| **snsSqsEnvelope** | 1. Parses data using `SqsSchema`.
2. Parses SNS records in `body` key using `SnsNotificationSchema`.
3. Parses data in `Message` key using your schema and return them in a list. |
-| **sqsEnvelope** | 1. Parses data using `SqsSchema`.
2. Parses records in `body` key using your schema and return them in a list. |
-| **vpcLatticeEnvelope** | 1. Parses data using `VpcLatticeSchema`.
2. Parses `value` key using your schema and returns it. |
-| **vpcLatticeV2Envelope** | 1. Parses data using `VpcLatticeSchema`.
2. Parses `value` key using your schema and returns it. |
+| **ApiGatewayEnvelope** | 1. Parses data using `APIGatewayProxyEventSchema`.
2. Parses `body` key using your schema and returns it. |
+| **ApiGatewayV2Envelope** | 1. Parses data using `APIGatewayProxyEventV2Schema`.
2. Parses `body` key using your schema and returns it. |
+| **CloudWatchEnvelope** | 1. Parses data using `CloudwatchLogsSchema` which will base64 decode and decompress it.
2. Parses records in `message` key using your schema and return them in a list. |
+| **DynamoDBStreamEnvelope** | 1. Parses data using `DynamoDBStreamSchema`.
2. Parses records in `NewImage` and `OldImage` keys using your schema.
3. Returns a list with a dictionary containing `NewImage` and `OldImage` keys |
+| **EventBridgeEnvelope** | 1. Parses data using `EventBridgeSchema`.
2. Parses `detail` key using your schema and returns it. |
+| **KafkaEnvelope** | 1. Parses data using `KafkaRecordSchema`.
2. Parses `value` key using your schema and returns it. |
+| **KinesisEnvelope** | 1. Parses data using `KinesisDataStreamSchema` which will base64 decode it.
2. Parses records in `Records` key using your schema and returns them in a list. |
+| **KinesisFirehoseEnvelope** | 1. Parses data using `KinesisFirehoseSchema` which will base64 decode it.
2. Parses records in `Records` key using your schema and returns them in a list. |
+| **LambdaFunctionUrlEnvelope** | 1. Parses data using `LambdaFunctionUrlSchema`.
2. Parses `body` key using your schema and returns it. |
+| **SnsEnvelope** | 1. Parses data using `SnsSchema`.
2. Parses records in `body` key using your schema and return them in a list. |
+| **SnsSqsEnvelope** | 1. Parses data using `SqsSchema`.
2. Parses SNS records in `body` key using `SnsNotificationSchema`.
3. Parses data in `Message` key using your schema and return them in a list. |
+| **SnsEnvelope** | 1. Parses data using `SqsSchema`.
2. Parses records in `body` key using your schema and return them in a list. |
+| **VpcLatticeEnvelope** | 1. Parses data using `VpcLatticeSchema`.
2. Parses `value` key using your schema and returns it. |
+| **VpcLatticeV2Envelope** | 1. Parses data using `VpcLatticeSchema`.
2. Parses `value` key using your schema and returns it. |
## Safe parsing
@@ -248,7 +245,7 @@ The handler `event` object will be replaced with `ParsedResult
The `ParsedResult` object will have `success`, `data`, or `error` and `originalEvent` fields, depending on the outcome.
If the parsing is successful, the `data` field will contain the parsed event, otherwise you can access the `error` field and the `originalEvent` to handle the error and recover the original event.
-=== "Middy middleware"
+=== "Middy.js middleware"
```typescript hl_lines="23 28 32-33"
--8<-- "examples/snippets/parser/safeParseMiddy.ts"
```
@@ -320,10 +317,11 @@ Use `z.infer` to extract the type of the schema, so you can use types during dev
### Compatibility with `@types/aws-lambda`
-The package `@types/aws-lambda` is a popular project that contains type definitions for many AWS service event invocations.
-Powertools parser utility also bring AWS Lambda event types based on the built-in schema definitions.
+The package `@types/aws-lambda` is a popular project that contains type definitions for many AWS service event invocations, support for these types is provided on a best effort basis.
+
+We recommend using the types provided by the Parser utility under `@aws-powertools/parser/types` when using the built-in schemas and envelopes, as they are inferred directly from the Zod schemas and are more accurate.
-We recommend to use the types provided by the parser utility. If you encounter any issues or have any feedback, please [submit an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose).
+If you encounter any type compatibility issues with `@types/aws-lambda`, please [submit an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose).
## Testing your code
diff --git a/examples/snippets/package.json b/examples/snippets/package.json
index 468af38cd5..5d9814256b 100644
--- a/examples/snippets/package.json
+++ b/examples/snippets/package.json
@@ -43,7 +43,7 @@
"@valkey/valkey-glide": "^2.0.1",
"aws-sdk": "^2.1692.0",
"aws-sdk-client-mock": "^4.1.0",
- "zod": "^3.25.76"
+ "zod": "^4.0.5"
},
"dependencies": {
"arktype": "^2.1.20",
diff --git a/examples/snippets/parser/extendAlbSchema.ts b/examples/snippets/parser/extendAlbSchema.ts
index 7de794973e..d38f802022 100644
--- a/examples/snippets/parser/extendAlbSchema.ts
+++ b/examples/snippets/parser/extendAlbSchema.ts
@@ -8,7 +8,7 @@ const customSchema = z.object({
});
const extendedSchema = AlbSchema.extend({
- body: JSONStringified(customSchema),
+ body: JSONStringified(customSchema), // (1)!
});
type _ExtendedAlbEvent = z.infer;
diff --git a/examples/snippets/parser/extendSqsSchema.ts b/examples/snippets/parser/extendSqsSchema.ts
index f5e4ed43c0..a01f365785 100644
--- a/examples/snippets/parser/extendSqsSchema.ts
+++ b/examples/snippets/parser/extendSqsSchema.ts
@@ -13,7 +13,7 @@ const customSchema = z.object({
const extendedSchema = SqsSchema.extend({
Records: z.array(
SqsRecordSchema.extend({
- body: JSONStringified(customSchema), // (1)!
+ body: JSONStringified(customSchema),
})
),
});
diff --git a/examples/snippets/parser/middy.ts b/examples/snippets/parser/middy.ts
index e8c19fb3cf..396341483d 100644
--- a/examples/snippets/parser/middy.ts
+++ b/examples/snippets/parser/middy.ts
@@ -11,7 +11,7 @@ const orderSchema = z.object({
items: z.array(
z.object({
id: z.number().positive(),
- quantity: z.number(),
+ quantity: z.number().positive(),
description: z.string(),
})
),
diff --git a/examples/snippets/parser/middyValibot.ts b/examples/snippets/parser/middyValibot.ts
new file mode 100644
index 0000000000..5a37de5daa
--- /dev/null
+++ b/examples/snippets/parser/middyValibot.ts
@@ -0,0 +1,36 @@
+import { Logger } from '@aws-lambda-powertools/logger';
+import { parser } from '@aws-lambda-powertools/parser/middleware';
+import middy from '@middy/core';
+import {
+ array,
+ number,
+ object,
+ optional,
+ pipe,
+ string,
+ toMinValue,
+} from 'valibot';
+
+const logger = new Logger();
+
+const orderSchema = object({
+ id: pipe(number(), toMinValue(0)),
+ description: string(),
+ items: array(
+ object({
+ id: pipe(number(), toMinValue(0)),
+ quantity: pipe(number(), toMinValue(1)),
+ description: string(),
+ })
+ ),
+ optionalField: optional(string()),
+});
+
+export const handler = middy()
+ .use(parser({ schema: orderSchema }))
+ .handler(async (event): Promise => {
+ for (const item of event.items) {
+ // item is parsed as OrderItem
+ logger.info('Processing item', { item });
+ }
+ });
diff --git a/package-lock.json b/package-lock.json
index c64cb98a3d..4cc7f5ff52 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -107,7 +107,7 @@
"@valkey/valkey-glide": "^2.0.1",
"aws-sdk": "^2.1692.0",
"aws-sdk-client-mock": "^4.1.0",
- "zod": "^3.25.76"
+ "zod": "^4.0.5"
}
},
"examples/snippets/node_modules/@valkey/valkey-glide": {
@@ -14926,9 +14926,9 @@
"dev": true
},
"node_modules/agent-base": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
- "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -24287,9 +24287,16 @@
"license": "MIT"
},
"node_modules/strnum": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
- "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT"
},
"node_modules/strong-log-transformer": {
"version": "2.1.0",
@@ -25721,10 +25728,10 @@
}
},
"node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
- "devOptional": true,
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz",
+ "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
@@ -25812,12 +25819,12 @@
"devDependencies": {
"avro-js": "^1.12.0",
"protobufjs": "^7.5.3",
- "zod": "^3.25.76"
+ "zod": "^4.0.5"
},
"peerDependencies": {
"arktype": ">=2.0.0",
"valibot": ">=1.0.0",
- "zod": ">=3.24.0"
+ "zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"arktype": {
@@ -25929,11 +25936,15 @@
"version": "2.23.0",
"license": "MIT-0",
"dependencies": {
- "@aws-lambda-powertools/commons": "2.23.0"
+ "@aws-lambda-powertools/commons": "2.23.0",
+ "@standard-schema/spec": "^1.0.0"
+ },
+ "devDependencies": {
+ "zod": "^4.0.5"
},
"peerDependencies": {
"@middy/core": "4.x || 5.x || 6.x",
- "zod": ">=3.x"
+ "zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"@middy/core": {
diff --git a/packages/kafka/package.json b/packages/kafka/package.json
index 3f3f3f56a1..6cac43dfa1 100644
--- a/packages/kafka/package.json
+++ b/packages/kafka/package.json
@@ -53,7 +53,7 @@
"peerDependencies": {
"arktype": ">=2.0.0",
"valibot": ">=1.0.0",
- "zod": ">=3.24.0"
+ "zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
@@ -117,6 +117,6 @@
"devDependencies": {
"avro-js": "^1.12.0",
"protobufjs": "^7.5.3",
- "zod": "^3.25.76"
+ "zod": "^4.0.5"
}
}
diff --git a/packages/parser/README.md b/packages/parser/README.md
index 2b79ea02f4..091e48fabd 100644
--- a/packages/parser/README.md
+++ b/packages/parser/README.md
@@ -7,7 +7,7 @@ You can use the package in both TypeScript and JavaScript code bases.
- [Intro](#intro)
- [Key features](#key-features)
- [Usage](#usage)
- - [Middleware](#middleware)
+ - [Middy.js Middleware](#middyjs-middleware)
- [Decorator](#decorator)
- [Manual parsing](#manual-parsing)
- [Safe parsing](#safe-parsing)
@@ -23,96 +23,112 @@ You can use the package in both TypeScript and JavaScript code bases.
## Intro
-The parser utility provides data validation and parsing using [Zod](https://zod.dev), a TypeScript-first schema declaration and validation library.
+This utility provides data validation and parsing for [Standard Schema](https://github.com/standard-schema/standard-schema), together with a collection of built-in [Zod](https://zod.dev) schemas and envelopes to parse and unwrap popular AWS event source payloads.
## Key features
-- Define data schema as Zod schema, then parse, validate and extract only what you want
-- Built-in envelopes to unwrap and validate popular AWS event sources payloads
-- Extend and customize envelopes to fit your needs
-- Safe parsing option to avoid throwing errors and custom error handling
-- Available for Middy.js middleware and TypeScript method decorators
+- Accept a [Standard Schema](https://github.com/standard-schema/standard-schema) and parse incoming payloads
+- Built-in Zod schemas and envelopes to unwrap and validate popular AWS event sources payloads
+- Extend and customize built-in Zod schemas to fit your needs
+- Safe parsing option to avoid throwing errors and allow custom error handling
+- Available as Middy.js middleware and TypeScript class method decorator
## Usage
To get started, install the library by running:
```sh
-npm install @aws-lambda-powertools/parser zod@~3
+npm install @aws-lambda-powertools/parser zod
```
-Then, define your schema using Zod:
+You can parse inbound events using the `parser` decorator, Middy.js middleware, or [manually](#manual-parsing) using built-in envelopes and schemas.
+
+When using the decorator or middleware, you can specify a schema to parse the event: this can be a [built-in Zod schema](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parser/#built-in-schemas) or a custom schema you defined. Custom schemas can be defined using Zod or any other [Standard Schema compatible library](https://standardschema.dev/#what-schema-libraries-implement-the-spec).
+
+### Middy.js Middleware
+
+Using Zod schemas:
```typescript
+import { Logger } from '@aws-lambda-powertools/logger';
+import { parser } from '@aws-lambda-powertools/parser/middleware';
+import middy from '@middy/core';
import { z } from 'zod';
+const logger = new Logger();
+
const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
- quantity: z.number(),
+ quantity: z.number().positive(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});
-export { orderSchema };
+export const handler = middy()
+ .use(parser({ schema: orderSchema }))
+ .handler(async (event): Promise => {
+ for (const item of event.items) {
+ // item is parsed as OrderItem
+ logger.info('Processing item', { item });
+ }
+ });
```
-Next, you can parse incoming events using the `parser` decorator or Middy.js middleware:
-
-### Middleware
+Using Valibot schemas:
```typescript
-import type { Context } from 'aws-lambda';
+import { Logger } from '@aws-lambda-powertools/logger';
import { parser } from '@aws-lambda-powertools/parser/middleware';
-import { z } from 'zod';
import middy from '@middy/core';
-import { Logger } from '@aws-lambda-powertools/logger';
+import {
+ array,
+ number,
+ object,
+ optional,
+ pipe,
+ string,
+ toMinValue,
+} from 'valibot';
const logger = new Logger();
-const orderSchema = z.object({
- id: z.number().positive(),
- description: z.string(),
- items: z.array(
- z.object({
- id: z.number().positive(),
- quantity: z.number(),
- description: z.string(),
+const orderSchema = object({
+ id: pipe(number(), toMinValue(0)),
+ description: string(),
+ items: array(
+ object({
+ id: pipe(number(), toMinValue(0)),
+ quantity: pipe(number(), toMinValue(1)),
+ description: string(),
})
),
- optionalField: z.string().optional(),
+ optionalField: optional(string()),
});
-type Order = z.infer;
-
-const lambdaHandler = async (
- event: Order,
- _context: Context
-): Promise => {
- for (const item of event.items) {
- // item is parsed as OrderItem
- logger.info('Processing item', { item });
- }
-};
-
-export const handler = middy(lambdaHandler).use(
- parser({ schema: orderSchema })
-);
+export const handler = middy()
+ .use(parser({ schema: orderSchema }))
+ .handler(async (event): Promise => {
+ for (const item of event.items) {
+ // item is parsed as OrderItem
+ logger.info('Processing item', { item });
+ }
+ });
```
### Decorator
```typescript
-import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
+import { Logger } from '@aws-lambda-powertools/logger';
import { parser } from '@aws-lambda-powertools/parser';
+import type { Context } from 'aws-lambda';
import { z } from 'zod';
-import { Logger } from '@aws-lambda-powertools/logger';
const logger = new Logger();
@@ -147,7 +163,7 @@ export const handler = myFunction.handler.bind(myFunction);
### Manual parsing
-If you don't want to add an additional dependency, or you prefer the manual approach, you can `parse` the event directly by calling the `parse` method on schemas and envelopes:
+If you don't want to add an additional middleware dependency, or you prefer the manual approach, you can parse the event directly by calling the `parse` method on schemas and envelopes:
```typescript
import type { Context } from 'aws-lambda';
diff --git a/packages/parser/package.json b/packages/parser/package.json
index 6ae674fc65..88650bfa89 100644
--- a/packages/parser/package.json
+++ b/packages/parser/package.json
@@ -200,11 +200,12 @@
"nodejs"
],
"dependencies": {
- "@aws-lambda-powertools/commons": "2.23.0"
+ "@aws-lambda-powertools/commons": "2.23.0",
+ "@standard-schema/spec": "^1.0.0"
},
"peerDependencies": {
"@middy/core": "4.x || 5.x || 6.x",
- "zod": ">=3.x"
+ "zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
@@ -213,5 +214,8 @@
"@middy/core": {
"optional": true
}
+ },
+ "devDependencies": {
+ "zod": "^4.0.5"
}
}
diff --git a/packages/parser/src/envelopes/api-gateway.ts b/packages/parser/src/envelopes/api-gateway.ts
index 0abaecc6d8..b464ed561d 100644
--- a/packages/parser/src/envelopes/api-gateway.ts
+++ b/packages/parser/src/envelopes/api-gateway.ts
@@ -1,4 +1,4 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import { ParseError } from '../errors.js';
import { APIGatewayProxyEventSchema } from '../schemas/api-gateway.js';
import type { ParsedResult } from '../types/parser.js';
@@ -13,7 +13,7 @@ export const ApiGatewayEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
+ parse(data: unknown, schema: ZodType): T {
try {
return APIGatewayProxyEventSchema.extend({
body: schema,
@@ -25,10 +25,7 @@ export const ApiGatewayEnvelope = {
}
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const result = APIGatewayProxyEventSchema.extend({
body: schema,
}).safeParse(data);
diff --git a/packages/parser/src/envelopes/api-gatewayv2.ts b/packages/parser/src/envelopes/api-gatewayv2.ts
index 408bcde42c..828bfaab0c 100644
--- a/packages/parser/src/envelopes/api-gatewayv2.ts
+++ b/packages/parser/src/envelopes/api-gatewayv2.ts
@@ -1,4 +1,4 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import { ParseError } from '../errors.js';
import { APIGatewayProxyEventV2Schema } from '../schemas/api-gatewayv2.js';
import type { ParsedResult } from '../types/index.js';
@@ -13,7 +13,7 @@ export const ApiGatewayV2Envelope = {
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
+ parse(data: unknown, schema: ZodType): T {
try {
return APIGatewayProxyEventV2Schema.extend({
body: schema,
@@ -25,10 +25,7 @@ export const ApiGatewayV2Envelope = {
}
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const result = APIGatewayProxyEventV2Schema.extend({
body: schema,
}).safeParse(data);
diff --git a/packages/parser/src/envelopes/cloudwatch.ts b/packages/parser/src/envelopes/cloudwatch.ts
index 36a57ffaf3..55a6b81056 100644
--- a/packages/parser/src/envelopes/cloudwatch.ts
+++ b/packages/parser/src/envelopes/cloudwatch.ts
@@ -1,4 +1,4 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
import { CloudWatchLogsSchema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
@@ -13,7 +13,7 @@ export const CloudWatchEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
const parsedEnvelope = CloudWatchLogsSchema.parse(data);
return parsedEnvelope.awslogs.data.logEvents.map((record, index) => {
@@ -42,19 +42,8 @@ export const CloudWatchEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
- let parsedEnvelope: ParsedResult>;
- try {
- parsedEnvelope = CloudWatchLogsSchema.safeParse(data);
- } catch (error) {
- parsedEnvelope = {
- success: false,
- error: error as Error,
- };
- }
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
+ const parsedEnvelope = CloudWatchLogsSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -70,8 +59,8 @@ export const CloudWatchEnvelope = {
(
acc: {
success: boolean;
- messages: z.infer;
- errors: { [key: number]: { issues: ZodIssue[] } };
+ messages: T[];
+ errors: { [key: number]: { issues: z.core.$ZodIssue[] } };
},
record: { message: string },
index: number
@@ -114,7 +103,6 @@ export const CloudWatchEnvelope = {
? `Failed to parse CloudWatch Log messages at indexes ${Object.keys(result.errors).join(', ')}`
: `Failed to parse CloudWatch Log message at index ${Object.keys(result.errors)[0]}`;
const errorCause = new ZodError(
- // @ts-expect-error - issues are assigned because success is false
Object.values(result.errors).flatMap((error) => error.issues)
);
diff --git a/packages/parser/src/envelopes/dynamodb.ts b/packages/parser/src/envelopes/dynamodb.ts
index bbe0914303..bcf951d647 100644
--- a/packages/parser/src/envelopes/dynamodb.ts
+++ b/packages/parser/src/envelopes/dynamodb.ts
@@ -1,4 +1,4 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
import { DynamoDBStreamSchema } from '../schemas/index.js';
import type { DynamoDBStreamEnvelopeResponse } from '../types/envelope.js';
@@ -17,10 +17,10 @@ export const DynamoDBStreamEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(
+ parse(
data: unknown,
- schema: T
- ): DynamoDBStreamEnvelopeResponse>[] {
+ schema: ZodType
+ ): DynamoDBStreamEnvelopeResponse[] {
const parsedEnvelope = DynamoDBStreamSchema.parse(data);
const processImage = (
@@ -57,10 +57,10 @@ export const DynamoDBStreamEnvelope = {
}));
},
- safeParse(
+ safeParse(
data: unknown,
- schema: T
- ): ParsedResult>[]> {
+ schema: ZodType
+ ): ParsedResult[]> {
const parsedEnvelope = DynamoDBStreamSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -77,15 +77,15 @@ export const DynamoDBStreamEnvelope = {
const result = parsedEnvelope.data.Records.reduce<{
success: boolean;
- records: DynamoDBStreamEnvelopeResponse>[];
- errors: { index?: number; issues?: ZodIssue[] };
+ records: DynamoDBStreamEnvelopeResponse[];
+ errors: { [key: number]: { issues: z.core.$ZodIssue[] } };
}>(
(acc, record, index) => {
const newImage = processImage(record.dynamodb.NewImage);
const oldImage = processImage(record.dynamodb.OldImage);
if (newImage?.success === false || oldImage?.success === false) {
- const issues: ZodIssue[] = [];
+ const issues: z.core.$ZodIssue[] = [];
for (const key of ['NewImage', 'OldImage']) {
const image = key === 'NewImage' ? newImage : oldImage;
if (image?.success === false) {
@@ -98,7 +98,6 @@ export const DynamoDBStreamEnvelope = {
}
}
acc.success = false;
- // @ts-expect-error - index is assigned
acc.errors[index] = { issues };
return acc;
}
@@ -121,7 +120,6 @@ export const DynamoDBStreamEnvelope = {
? `Failed to parse records at indexes ${Object.keys(result.errors).join(', ')}`
: `Failed to parse record at index ${Object.keys(result.errors)[0]}`;
const errorCause = new ZodError(
- // @ts-expect-error - issues are assigned because success is false
Object.values(result.errors).flatMap((error) => error.issues)
);
diff --git a/packages/parser/src/envelopes/eventbridge.ts b/packages/parser/src/envelopes/eventbridge.ts
index adc47d75b3..601c04b6b2 100644
--- a/packages/parser/src/envelopes/eventbridge.ts
+++ b/packages/parser/src/envelopes/eventbridge.ts
@@ -1,4 +1,4 @@
-import type { ZodError, ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import { ParseError } from '../errors.js';
import { EventBridgeSchema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
@@ -13,29 +13,22 @@ export const EventBridgeEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
- const extendedSchema = EventBridgeSchema.extend({
- detail: schema,
- });
+ parse(data: unknown, schema: ZodType): T {
try {
- const parsed = extendedSchema.parse(data);
- return parsed.detail;
+ return EventBridgeSchema.extend({
+ detail: schema,
+ }).parse(data).detail;
} catch (error) {
throw new ParseError('Failed to parse EventBridge envelope', {
- cause: error as ZodError,
+ cause: error,
});
}
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult> {
- const extendedSchema = EventBridgeSchema.extend({
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
+ const parsedResult = EventBridgeSchema.extend({
detail: schema,
- });
-
- const parsedResult = extendedSchema.safeParse(data);
+ }).safeParse(data);
if (!parsedResult.success) {
return {
success: false,
diff --git a/packages/parser/src/envelopes/kafka.ts b/packages/parser/src/envelopes/kafka.ts
index 04fbb678d6..055d8091c1 100644
--- a/packages/parser/src/envelopes/kafka.ts
+++ b/packages/parser/src/envelopes/kafka.ts
@@ -1,4 +1,4 @@
-import { ZodError, type ZodIssue, type ZodSchema, z } from 'zod';
+import { ZodError, type ZodType, z } from 'zod';
import { ParseError } from '../errors.js';
import {
KafkaMskEventSchema,
@@ -43,7 +43,7 @@ export const KafkaEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): z.infer>[] {
const eventSource = extractEventSource(data);
const parsedEnvelope =
@@ -51,7 +51,7 @@ export const KafkaEnvelope = {
? KafkaMskEventSchema.parse(data)
: KafkaSelfManagedEventSchema.parse(data);
- const values: z.infer[] = [];
+ const values: z.infer>[] = [];
for (const topicRecord of Object.values(parsedEnvelope.records)) {
for (const record of topicRecord) {
values.push(schema.parse(record.value));
@@ -61,10 +61,10 @@ export const KafkaEnvelope = {
return values;
},
- safeParse(
+ safeParse(
data: unknown,
- schema: T
- ): ParsedResult[]> {
+ schema: ZodType
+ ): ParsedResult>[]> {
// manually fetch event source to deside between Msk or SelfManaged
const eventSource = (data as KafkaMskEvent).eventSource;
@@ -83,8 +83,8 @@ export const KafkaEnvelope = {
};
}
- const values: z.infer[] = [];
- const issues: ZodIssue[] = [];
+ const values: z.infer>[] = [];
+ const issues: z.core.$ZodIssue[] = [];
for (const [topicKey, topicRecord] of Object.entries(
parsedEnvelope.data.records
)) {
@@ -92,11 +92,12 @@ export const KafkaEnvelope = {
const parsedRecord = schema.safeParse(record.value);
if (!parsedRecord.success) {
issues.push(
- ...(parsedRecord.error as ZodError).issues.map((issue) => ({
+ ...parsedRecord.error.issues.map((issue) => ({
...issue,
path: ['records', topicKey, ...issue.path],
}))
);
+ continue;
}
values.push(parsedRecord.data);
}
diff --git a/packages/parser/src/envelopes/kinesis-firehose.ts b/packages/parser/src/envelopes/kinesis-firehose.ts
index 859f5b15f3..0a7a905607 100644
--- a/packages/parser/src/envelopes/kinesis-firehose.ts
+++ b/packages/parser/src/envelopes/kinesis-firehose.ts
@@ -1,9 +1,6 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
-import {
- type KinesisFirehoseRecordSchema,
- KinesisFirehoseSchema,
-} from '../schemas/index.js';
+import { KinesisFirehoseSchema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
import { envelopeDiscriminator } from './envelope.js';
@@ -25,7 +22,7 @@ export const KinesisFirehoseEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
let parsedEnvelope: z.infer;
try {
parsedEnvelope = KinesisFirehoseSchema.parse(data);
@@ -36,7 +33,7 @@ export const KinesisFirehoseEnvelope = {
}
return parsedEnvelope.records.map((record, recordIndex) => {
- let parsedRecord: z.infer;
+ let parsedRecord: T;
try {
parsedRecord = schema.parse(record.data);
} catch (error) {
@@ -56,10 +53,7 @@ export const KinesisFirehoseEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const parsedEnvelope = KinesisFirehoseSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -73,9 +67,9 @@ export const KinesisFirehoseEnvelope = {
const result = parsedEnvelope.data.records.reduce<{
success: boolean;
- records: z.infer[];
+ records: T[];
errors: {
- [key: number | string]: { issues: ZodIssue[] };
+ [key: number | string]: { issues: z.core.$ZodIssue[] };
};
}>(
(acc, record, index) => {
diff --git a/packages/parser/src/envelopes/kinesis.ts b/packages/parser/src/envelopes/kinesis.ts
index 9259a56f0b..a7cbfc461b 100644
--- a/packages/parser/src/envelopes/kinesis.ts
+++ b/packages/parser/src/envelopes/kinesis.ts
@@ -1,9 +1,6 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
-import {
- type KinesisDataStreamRecord,
- KinesisDataStreamSchema,
-} from '../schemas/kinesis.js';
+import { KinesisDataStreamSchema } from '../schemas/kinesis.js';
import type { ParsedResult } from '../types/index.js';
import { envelopeDiscriminator } from './envelope.js';
@@ -23,7 +20,7 @@ export const KinesisEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
let parsedEnvelope: z.infer;
try {
parsedEnvelope = KinesisDataStreamSchema.parse(data);
@@ -34,7 +31,7 @@ export const KinesisEnvelope = {
}
return parsedEnvelope.Records.map((record, recordIndex) => {
- let parsedRecord: z.infer;
+ let parsedRecord: T;
try {
parsedRecord = schema.parse(record.kinesis.data);
} catch (error) {
@@ -60,10 +57,7 @@ export const KinesisEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const parsedEnvelope = KinesisDataStreamSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -77,8 +71,8 @@ export const KinesisEnvelope = {
const result = parsedEnvelope.data.Records.reduce<{
success: boolean;
- records: z.infer[];
- errors: { index?: number; issues?: ZodIssue[] };
+ records: T[];
+ errors: { [key: number]: { issues: z.core.$ZodIssue[] } };
}>(
(acc, record, index) => {
const parsedRecord = schema.safeParse(record.kinesis.data);
@@ -89,7 +83,6 @@ export const KinesisEnvelope = {
path: ['Records', index, 'kinesis', 'data', ...issue.path],
}));
acc.success = false;
- // @ts-expect-error - index is assigned
acc.errors[index] = { issues };
return acc;
}
@@ -112,7 +105,6 @@ export const KinesisEnvelope = {
success: false,
error: new ParseError(errorMessage, {
cause: new ZodError(
- // @ts-expect-error - issues are assigned because success is false
Object.values(result.errors).flatMap((error) => error.issues)
),
}),
diff --git a/packages/parser/src/envelopes/lambda.ts b/packages/parser/src/envelopes/lambda.ts
index d68af29a88..96a1f13268 100644
--- a/packages/parser/src/envelopes/lambda.ts
+++ b/packages/parser/src/envelopes/lambda.ts
@@ -1,4 +1,4 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import { ParseError } from '../errors.js';
import { LambdaFunctionUrlSchema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
@@ -13,22 +13,19 @@ export const LambdaFunctionUrlEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
+ parse(data: unknown, schema: ZodType): T {
try {
return LambdaFunctionUrlSchema.extend({
body: schema,
}).parse(data).body;
} catch (error) {
throw new ParseError('Failed to parse Lambda function URL body', {
- cause: error as Error,
+ cause: error,
});
}
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const results = LambdaFunctionUrlSchema.extend({
body: schema,
}).safeParse(data);
diff --git a/packages/parser/src/envelopes/sns-sqs.ts b/packages/parser/src/envelopes/sns-sqs.ts
index a75a709f31..1aa17288e0 100644
--- a/packages/parser/src/envelopes/sns-sqs.ts
+++ b/packages/parser/src/envelopes/sns-sqs.ts
@@ -1,11 +1,11 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
import { SnsSqsNotificationSchema } from '../schemas/sns.js';
import { SqsSchema } from '../schemas/sqs.js';
import type { ParsedResult, SnsSqsNotification } from '../types/index.js';
import { envelopeDiscriminator } from './envelope.js';
-const createError = (index: number, issues: ZodIssue[]) => ({
+const createError = (index: number, issues: z.core.$ZodIssue[]) => ({
issues: issues.map((issue) => ({
...issue,
path: ['Records', index, 'body', ...issue.path],
@@ -19,13 +19,13 @@ type ParseStepSuccess = {
type ParseStepError = {
success: false;
- error: { issues: ZodIssue[] };
+ error: { issues: z.core.$ZodIssue[] };
};
type ParseStepResult = ParseStepSuccess | ParseStepError;
const parseStep = (
- parser: (data: unknown) => z.SafeParseReturnType,
+ parser: (data: unknown) => z.ZodSafeParseResult,
data: unknown,
index: number
): ParseStepResult => {
@@ -56,7 +56,7 @@ export const SnsSqsEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
let parsedEnvelope: z.infer;
try {
parsedEnvelope = SqsSchema.parse(data);
@@ -84,7 +84,8 @@ export const SnsSqsEnvelope = {
: [
{
code: 'custom',
- message: 'Invalid JSON',
+ input: record.body,
+ message: `Invalid JSON - ${(error as Error).message}`,
path: ['Records', recordIndex, 'body'],
},
]
@@ -95,10 +96,7 @@ export const SnsSqsEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const parsedEnvelope = SqsSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -113,7 +111,7 @@ export const SnsSqsEnvelope = {
const parseRecord = (
record: { body: string },
index: number
- ): ParseStepResult> => {
+ ): ParseStepResult => {
try {
const body = JSON.parse(record.body);
const notification = parseStep(
@@ -123,18 +121,19 @@ export const SnsSqsEnvelope = {
);
if (!notification.success) return notification;
- return parseStep>(
+ return parseStep(
(data) => schema.safeParse(data),
notification.data.Message,
index
);
- } catch {
+ } catch (error) {
return {
success: false,
error: createError(index, [
{
code: 'custom',
- message: 'Invalid JSON',
+ message: `Invalid JSON - ${(error as Error).message}`,
+ input: record.body,
path: [],
},
]),
@@ -144,9 +143,9 @@ export const SnsSqsEnvelope = {
const result = parsedEnvelope.data.Records.reduce<{
success: boolean;
- records: z.infer[];
+ records: T[];
errors: {
- [key: number | string]: { issues: ZodIssue[] };
+ [key: number | string]: { issues: z.core.$ZodIssue[] };
};
}>(
(acc, record, index) => {
diff --git a/packages/parser/src/envelopes/sns.ts b/packages/parser/src/envelopes/sns.ts
index 3a7d40cb32..038b2b1f13 100644
--- a/packages/parser/src/envelopes/sns.ts
+++ b/packages/parser/src/envelopes/sns.ts
@@ -1,4 +1,4 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
import { SnsSchema } from '../schemas/sns.js';
import type { ParsedResult } from '../types/index.js';
@@ -19,7 +19,7 @@ export const SnsEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
const parsedEnvelope = SnsSchema.parse(data);
return parsedEnvelope.Records.map((record, index) => {
@@ -38,10 +38,7 @@ export const SnsEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const parsedEnvelope = SnsSchema.safeParse(data);
if (!parsedEnvelope.success) {
@@ -56,8 +53,8 @@ export const SnsEnvelope = {
const result = parsedEnvelope.data.Records.reduce<{
success: boolean;
- messages: z.infer[];
- errors: { index?: number; issues?: ZodIssue[] };
+ messages: T[];
+ errors: { [key: number]: { issues: z.core.$ZodIssue[] } };
}>(
(acc, message, index) => {
const parsedMessage = schema.safeParse(message.Sns.Message);
@@ -67,7 +64,6 @@ export const SnsEnvelope = {
...issue,
path: ['Records', index, 'Sns', 'Message', ...issue.path],
}));
- // @ts-expect-error - index is assigned
acc.errors[index] = { issues };
return acc;
}
@@ -87,7 +83,6 @@ export const SnsEnvelope = {
? `Failed to parse SNS messages at indexes ${Object.keys(result.errors).join(', ')}`
: `Failed to parse SNS message at index ${Object.keys(result.errors)[0]}`;
const errorCause = new ZodError(
- // @ts-expect-error - issues are assigned because success is false
Object.values(result.errors).flatMap((error) => error.issues)
);
diff --git a/packages/parser/src/envelopes/sqs.ts b/packages/parser/src/envelopes/sqs.ts
index 6d57606f7b..71472ffd4a 100644
--- a/packages/parser/src/envelopes/sqs.ts
+++ b/packages/parser/src/envelopes/sqs.ts
@@ -1,6 +1,6 @@
-import { ZodError, type ZodIssue, type ZodSchema, type z } from 'zod';
+import { ZodError, type ZodType, type z } from 'zod';
import { ParseError } from '../errors.js';
-import { type SqsRecordSchema, SqsSchema } from '../schemas/sqs.js';
+import { SqsSchema } from '../schemas/sqs.js';
import type { ParsedResult } from '../types/index.js';
import { envelopeDiscriminator } from './envelope.js';
@@ -29,7 +29,7 @@ const SqsEnvelope = {
* @hidden
*/
[envelopeDiscriminator]: 'array' as const,
- parse(data: unknown, schema: T): z.infer[] {
+ parse(data: unknown, schema: ZodType): T[] {
let parsedEnvelope: z.infer;
try {
parsedEnvelope = SqsSchema.parse(data);
@@ -40,7 +40,7 @@ const SqsEnvelope = {
}
return parsedEnvelope.Records.map((record, recordIndex) => {
- let parsedRecord: z.infer;
+ let parsedRecord: T;
try {
parsedRecord = schema.parse(record.body);
} catch (error) {
@@ -60,10 +60,7 @@ const SqsEnvelope = {
});
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult[]> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const parsedEnvelope = SqsSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
@@ -77,8 +74,8 @@ const SqsEnvelope = {
const result = parsedEnvelope.data.Records.reduce<{
success: boolean;
- records: z.infer[];
- errors: { index?: number; issues?: ZodIssue[] };
+ records: T[];
+ errors: { index?: number; issues?: z.core.$ZodIssue[] };
}>(
(acc, record, index) => {
const parsedRecord = schema.safeParse(record.body);
diff --git a/packages/parser/src/envelopes/vpc-lattice.ts b/packages/parser/src/envelopes/vpc-lattice.ts
index 6c8190ae8c..7a686f64b0 100644
--- a/packages/parser/src/envelopes/vpc-lattice.ts
+++ b/packages/parser/src/envelopes/vpc-lattice.ts
@@ -1,4 +1,4 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType, z } from 'zod';
import { ParseError } from '../errors.js';
import { VpcLatticeSchema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
@@ -7,14 +7,13 @@ import { envelopeDiscriminator } from './envelope.js';
/**
* Amazon VPC Lattice envelope to extract data within body key
*/
-
export const VpcLatticeEnvelope = {
/**
* This is a discriminator to differentiate whether an envelope returns an array or an object
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
+ parse(data: unknown, schema: ZodType): z.infer> {
try {
return VpcLatticeSchema.extend({
body: schema,
@@ -26,10 +25,10 @@ export const VpcLatticeEnvelope = {
}
},
- safeParse(
+ safeParse(
data: unknown,
- schema: T
- ): ParsedResult> {
+ schema: ZodType
+ ): ParsedResult>> {
const result = VpcLatticeSchema.extend({
body: schema,
}).safeParse(data);
diff --git a/packages/parser/src/envelopes/vpc-latticev2.ts b/packages/parser/src/envelopes/vpc-latticev2.ts
index 0ff99dffe6..7a7f245518 100644
--- a/packages/parser/src/envelopes/vpc-latticev2.ts
+++ b/packages/parser/src/envelopes/vpc-latticev2.ts
@@ -1,4 +1,4 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import { ParseError } from '../errors.js';
import { VpcLatticeV2Schema } from '../schemas/index.js';
import type { ParsedResult } from '../types/index.js';
@@ -13,7 +13,7 @@ export const VpcLatticeV2Envelope = {
* @hidden
*/
[envelopeDiscriminator]: 'object' as const,
- parse(data: unknown, schema: T): z.infer {
+ parse(data: unknown, schema: ZodType): T {
try {
return VpcLatticeV2Schema.extend({
body: schema,
@@ -25,10 +25,7 @@ export const VpcLatticeV2Envelope = {
}
},
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult> {
+ safeParse(data: unknown, schema: ZodType): ParsedResult {
const result = VpcLatticeV2Schema.extend({
body: schema,
}).safeParse(data);
diff --git a/packages/parser/src/errors.ts b/packages/parser/src/errors.ts
index d4aac4d7ad..089d414a93 100644
--- a/packages/parser/src/errors.ts
+++ b/packages/parser/src/errors.ts
@@ -3,23 +3,14 @@
* The cause of the error is included in the message, if possible.
*/
class ParseError extends Error {
- public constructor(message: string, options?: { cause?: Error }) {
- const errorMessage = options?.cause
- ? `${message}. This error was caused by: ${options?.cause.message}.`
- : message;
+ public constructor(message: string, options?: ErrorOptions) {
+ const errorMessage =
+ options?.cause && options.cause instanceof Error
+ ? `${message}. This error was caused by: ${options?.cause.message}.`
+ : message;
super(errorMessage, options);
this.name = 'ParseError';
}
}
-/**
- * Custom error thrown when decompression fails.
- */
-class DecompressError extends ParseError {
- constructor(message: string, options?: { cause?: Error }) {
- super(message, options);
- this.name = 'DecompressError';
- }
-}
-
-export { ParseError, DecompressError };
+export { ParseError };
diff --git a/packages/parser/src/helpers/dynamodb.ts b/packages/parser/src/helpers/dynamodb.ts
index 56c9dc9144..ace793d3ca 100644
--- a/packages/parser/src/helpers/dynamodb.ts
+++ b/packages/parser/src/helpers/dynamodb.ts
@@ -1,6 +1,6 @@
import { unmarshallDynamoDB } from '@aws-lambda-powertools/commons/utils/unmarshallDynamoDB';
import type { AttributeValue } from '@aws-sdk/client-dynamodb';
-import { type ZodTypeAny, z } from 'zod';
+import { type ZodType, z } from 'zod';
/**
* A helper function to unmarshall DynamoDB stream events and validate them against a schema.
@@ -61,7 +61,7 @@ import { type ZodTypeAny, z } from 'zod';
*
* @param schema - The schema to validate the JSON string against
*/
-const DynamoDBMarshalled = (schema: T) =>
+const DynamoDBMarshalled = (schema: ZodType) =>
z
.union([
z.custom(),
diff --git a/packages/parser/src/helpers/index.ts b/packages/parser/src/helpers/index.ts
index 17cf945274..4098233df2 100644
--- a/packages/parser/src/helpers/index.ts
+++ b/packages/parser/src/helpers/index.ts
@@ -1,4 +1,4 @@
-import { type ZodTypeAny, z } from 'zod';
+import { type ZodType, z } from 'zod';
/**
* A helper function to parse a JSON string and validate it against a schema.
@@ -38,16 +38,17 @@ import { type ZodTypeAny, z } from 'zod';
*
* @param schema - The schema to validate the JSON string against
*/
-const JSONStringified = (schema: T) =>
+const JSONStringified = (schema: T) =>
z
.string()
.transform((str, ctx) => {
try {
return JSON.parse(str);
- } catch {
+ } catch (error) {
ctx.addIssue({
code: 'custom',
- message: 'Invalid JSON',
+ message: `Invalid JSON - ${(error as Error).message}`,
+ fatal: true,
});
}
})
diff --git a/packages/parser/src/middleware/index.ts b/packages/parser/src/middleware/index.ts
index 3a31e0992f..c57cdf34cd 100644
--- a/packages/parser/src/middleware/index.ts
+++ b/packages/parser/src/middleware/index.ts
@@ -1,12 +1,12 @@
import type { MiddyLikeRequest } from '@aws-lambda-powertools/commons/types';
import type { MiddlewareObj } from '@middy/core';
-import type { ZodType } from 'zod';
+import type { StandardSchemaV1 } from '@standard-schema/spec';
import { parse } from '../parser.js';
import type { Envelope } from '../types/envelope.js';
import type { ParserOptions, ParserOutput } from '../types/parser.js';
/**
- * A middiy middleware to parse your event.
+ * A Middy.js middleware to parse incoming events using a specified schema and optional envelope.
*
* @example
* ```typescript
@@ -22,19 +22,17 @@ import type { ParserOptions, ParserOutput } from '../types/parser.js';
*
* type Order = z.infer;
*
- * export const handler = middy(
- * async (event: Order, _context: unknown): Promise => {
- * // event is validated as sqs message envelope
- * // the body is unwrapped and parsed into object ready to use
- * // you can now use event as Order in your code
- * }
- * ).use(parser({ schema: oderSchema, envelope: sqsEnvelope }));
+ * export const handler = middy()
+ * .use(parser({ schema: oderSchema, envelope: sqsEnvelope }))
+ * .handler(async (event) => {
+ * // ^ event is inferred as Order[]
+ * })
* ```
*
- * @param options
+ * @param options - options for the parser
*/
const parser = <
- TSchema extends ZodType,
+ TSchema extends StandardSchemaV1,
TEnvelope extends Envelope,
TSafeParse extends boolean = false,
>(
diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts
index dccb6fd8f9..9a7077f8c1 100644
--- a/packages/parser/src/parser.ts
+++ b/packages/parser/src/parser.ts
@@ -1,10 +1,15 @@
-import type { ZodSchema, z } from 'zod';
+import type { StandardSchemaV1 } from '@standard-schema/spec';
import { ParseError } from './errors.js';
-import type { Envelope } from './types/index.js';
-import type { ParseFunction } from './types/parser.js';
+import type {
+ ArrayEnvelope,
+ DynamoDBArrayEnvelope,
+ DynamoDBStreamEnvelopeResponse,
+ Envelope,
+} from './types/index.js';
+import type { InferOutput, ParsedResult } from './types/parser.js';
/**
- * Parse the data using the provided schema, envelope and safeParse flag
+ * Parse the data using the provided schema and optional envelope.
*
* @example
* ```typescript
@@ -20,55 +25,128 @@ import type { ParseFunction } from './types/parser.js';
*
* const handler = async (event: SqsEvent, context: unknown): Promise => {
* const parsedEvent = parse(event, SqsEnvelope, Order);
- *
- * const parsedSafe: ParsedResult = parse(event, SqsEnvelope, Order, true)
* }
- * @param data the data to parse
- * @param envelope the envelope to use, can be undefined
- * @param schema the schema to use
- * @param safeParse whether to use safeParse or not, if true it will return a ParsedResult with the original event if the parsing fails
+ * ```
+ *
+ * @param data - the data to parse
+ * @param envelope - optional envelope to use when parsing the data
+ * @param schema - the schema to use
+ * @param safeParse - whether to throw on error, if `true` it will return a `ParsedResult` with the original event if the parsing fails
*/
-const parse: ParseFunction = (
- data: z.infer,
+function parse(
+ data: unknown,
+ envelope: undefined,
+ schema: T,
+ safeParse?: false
+): InferOutput;
+
+// No envelope, with safeParse
+function parse(
+ data: unknown,
+ envelope: undefined,
+ schema: T,
+ safeParse: true
+): ParsedResult>;
+
+// No envelope, with boolean safeParse
+function parse(
+ data: unknown,
+ envelope: undefined,
+ schema: T,
+ safeParse: boolean
+): InferOutput | ParsedResult>;
+
+// With envelope, no safeParse
+function parse(
+ data: unknown,
+ envelope: E,
+ schema: T,
+ safeParse?: false
+): E extends DynamoDBArrayEnvelope
+ ? DynamoDBStreamEnvelopeResponse>[]
+ : E extends ArrayEnvelope
+ ? InferOutput[]
+ : InferOutput;
+
+// With envelope, with safeParse
+function parse(
+ data: unknown,
+ envelope: E,
+ schema: T,
+ safeParse: true
+): E extends DynamoDBArrayEnvelope
+ ? ParsedResult>[]>
+ : E extends ArrayEnvelope
+ ? ParsedResult[]>
+ : ParsedResult>;
+
+// No envelope, with boolean | undefined safeParse
+function parse(
+ data: unknown,
+ envelope: undefined,
+ schema: T,
+ safeParse?: boolean
+): InferOutput | ParsedResult>;
+
+// With envelope, with boolean | undefined safeParse
+function parse(
+ data: unknown,
+ envelope: E,
+ schema: T,
+ safeParse?: boolean
+): E extends DynamoDBArrayEnvelope
+ ?
+ | DynamoDBStreamEnvelopeResponse>[]
+ | ParsedResult>[]>
+ : E extends ArrayEnvelope
+ ? InferOutput[] | ParsedResult[]>
+ : InferOutput | ParsedResult>;
+
+// Implementation
+function parse(
+ data: unknown,
envelope: E | undefined,
schema: T,
safeParse?: boolean
-) => {
+): InferOutput | ParsedResult> {
if (envelope && safeParse) {
- return envelope.safeParse(data, schema);
+ // biome-ignore lint/suspicious/noExplicitAny: at least for now, we need to broaden the type because the envelope's parse and safeParse methods are not typed with StandardSchemaV1 but with ZodSchema
+ return envelope.safeParse(data, schema as any);
}
if (envelope) {
- return envelope.parse(data, schema);
+ // biome-ignore lint/suspicious/noExplicitAny: at least for now, we need to broaden the type because the envelope's parse and safeParse methods are not typed with StandardSchemaV1 but with ZodSchema
+ return envelope.parse(data, schema as any);
}
- if (safeParse) {
- return safeParseSchema(data, schema);
- }
- try {
- return schema.parse(data);
- } catch (error) {
- throw new ParseError('Failed to parse schema', { cause: error as Error });
- }
-};
-/**
- * Parse the data safely using the provided schema.
- * This function will not throw an error if the parsing fails, instead it will return a ParsedResultError with the original event.
- * Otherwise, it will return ParsedResultSuccess with the parsed data.
- * @param data the data to parse
- * @param schema the zod schema to use
- */
-const safeParseSchema = (data: z.infer, schema: T) => {
- const result = schema.safeParse(data);
+ const result = schema['~standard'].validate(data);
+ /* v8 ignore start */
+ if (result instanceof Promise) {
+ throw new ParseError('Schema parsing supports only synchronous validation');
+ }
+ /* v8 ignore stop */
- return result.success
- ? result
- : {
+ if (result.issues) {
+ const error = new ParseError('Failed to parse schema', {
+ cause: result.issues,
+ });
+ if (safeParse) {
+ return {
success: false,
- error: new ParseError('Failed to parse schema safely', {
- cause: result.error,
- }),
+ error,
originalEvent: data,
};
-};
+ }
+ throw error;
+ }
+
+ if (safeParse) {
+ return {
+ success: true,
+ data: result.value,
+ };
+ }
+
+ return result.value;
+}
export { parse };
diff --git a/packages/parser/src/parserDecorator.ts b/packages/parser/src/parserDecorator.ts
index 0e9ee7ed87..bb9656ca2a 100644
--- a/packages/parser/src/parserDecorator.ts
+++ b/packages/parser/src/parserDecorator.ts
@@ -1,6 +1,6 @@
import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
+import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { Context, Handler } from 'aws-lambda';
-import type { ZodSchema } from 'zod';
import { parse } from './parser.js';
import type { Envelope, ParserOptions } from './types/index.js';
import type { ParserOutput } from './types/parser.js';
@@ -69,7 +69,7 @@ import type { ParserOutput } from './types/parser.js';
* @param options Configure the parser with the `schema`, `envelope` and whether to `safeParse` or not
*/
export const parser = <
- TSchema extends ZodSchema,
+ TSchema extends StandardSchemaV1,
TEnvelope extends Envelope = undefined,
TSafeParse extends boolean = false,
>(
diff --git a/packages/parser/src/schemas/alb.ts b/packages/parser/src/schemas/alb.ts
index 732cabc31d..9b61825ba4 100644
--- a/packages/parser/src/schemas/alb.ts
+++ b/packages/parser/src/schemas/alb.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { ALBEvent } from '../types/schema.js';
/**
* Zod schema for Application Load Balancer events.
@@ -59,7 +60,7 @@ import { z } from 'zod';
* }
* ```
*
- * @see {@link types.ALBEvent | ALBEvent}
+ * @see {@link ALBEvent | `ALBEvent`}
* @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html}
*/
diff --git a/packages/parser/src/schemas/api-gateway-websocket.ts b/packages/parser/src/schemas/api-gateway-websocket.ts
index a70148c9ba..ca26281e79 100644
--- a/packages/parser/src/schemas/api-gateway-websocket.ts
+++ b/packages/parser/src/schemas/api-gateway-websocket.ts
@@ -1,4 +1,6 @@
import { z } from 'zod';
+import type { APIGatewayProxyWebsocketEvent } from '../types/schema.js';
+import { APIGatewayRecord, APIGatewayStringArray } from './apigw-proxy.js';
/**
* A zod schema for API Gateway Proxy WebSocket events.
@@ -59,19 +61,19 @@ import { z } from 'zod';
* }
* ```
*
+ * @see {@link APIGatewayProxyWebsocketEvent | `APIGatewayProxyWebsocketEvent`}
* @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-integrations.html}
*/
export const APIGatewayProxyWebsocketEventSchema = z.object({
type: z.string(),
methodArn: z.string(),
- headers: z.record(z.string()),
- multiValueHeaders: z.record(z.array(z.string())),
- queryStringParameters: z.record(z.string()).nullable().optional(),
+ headers: z.record(z.string(), z.string()).nullish(),
+ multiValueHeaders: z.record(z.string(), APIGatewayStringArray),
+ queryStringParameters: APIGatewayRecord.nullable(),
multiValueQueryStringParameters: z
- .record(z.array(z.string()))
- .nullable()
- .optional(),
- stageVariables: z.record(z.string()).nullable().optional(),
+ .record(z.string(), APIGatewayStringArray)
+ .nullable(),
+ stageVariables: APIGatewayRecord.nullable().optional(),
requestContext: z.object({
routeKey: z.string(),
eventType: z.enum(['CONNECT', 'DISCONNECT', 'MESSAGE']),
diff --git a/packages/parser/src/schemas/api-gateway.ts b/packages/parser/src/schemas/api-gateway.ts
index 991cd88bcb..9baa7c322e 100644
--- a/packages/parser/src/schemas/api-gateway.ts
+++ b/packages/parser/src/schemas/api-gateway.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { APIGatewayProxyEvent } from '../types/schema.js';
import {
APIGatewayCert,
APIGatewayHttpMethod,
@@ -29,9 +30,7 @@ const APIGatewayEventIdentity = z.object({
*
* See aws-powertools/powertools-lambda-python#1562 for more information.
*/
- sourceIp: z
- .union([z.string().ip(), z.literal('test-invoke-source-ip')])
- .optional(),
+ sourceIp: z.union([z.ipv4(), z.literal('test-invoke-source-ip')]).optional(),
user: z.string().nullish(),
userAgent: z.string().nullish(),
userArn: z.string().nullish(),
@@ -147,8 +146,7 @@ const APIGatewayEventRequestContextSchema = z
* "apiId": "abcdef123"
* }
* ```
- * @see {@link types.APIGatewayProxyEvent | APIGatewayProxyEvent}
- *
+ * @see {@link APIGatewayProxyEvent | `APIGatewayProxyEvent`}
* @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html}
*/
const APIGatewayProxyEventSchema = z.object({
@@ -156,9 +154,11 @@ const APIGatewayProxyEventSchema = z.object({
path: z.string(),
httpMethod: APIGatewayHttpMethod,
headers: APIGatewayRecord.nullish(),
- multiValueHeaders: z.record(APIGatewayStringArray).nullish(),
+ multiValueHeaders: z.record(z.string(), APIGatewayStringArray).nullish(),
queryStringParameters: APIGatewayRecord.nullable(),
- multiValueQueryStringParameters: z.record(APIGatewayStringArray).nullable(),
+ multiValueQueryStringParameters: z
+ .record(z.string(), APIGatewayStringArray)
+ .nullable(),
pathParameters: APIGatewayRecord.nullish(),
stageVariables: APIGatewayRecord.nullish(),
requestContext: APIGatewayEventRequestContextSchema,
@@ -227,9 +227,9 @@ const APIGatewayRequestAuthorizerEventSchema = z.object({
path: z.string(),
httpMethod: APIGatewayHttpMethod,
headers: APIGatewayRecord,
- multiValueHeaders: z.record(APIGatewayStringArray),
+ multiValueHeaders: z.record(z.string(), APIGatewayStringArray),
queryStringParameters: APIGatewayRecord,
- multiValueQueryStringParameters: z.record(APIGatewayStringArray),
+ multiValueQueryStringParameters: z.record(z.string(), APIGatewayStringArray),
pathParameters: APIGatewayRecord,
stageVariables: APIGatewayRecord,
requestContext: APIGatewayEventRequestContextSchema,
diff --git a/packages/parser/src/schemas/api-gatewayv2.ts b/packages/parser/src/schemas/api-gatewayv2.ts
index 6bab874fb0..bff5fdfdd6 100644
--- a/packages/parser/src/schemas/api-gatewayv2.ts
+++ b/packages/parser/src/schemas/api-gatewayv2.ts
@@ -1,4 +1,8 @@
import { z } from 'zod';
+import {
+ APIGatewayProxyEventV2,
+ APIGatewayRequestAuthorizerEventV2,
+} from '../types/schema.js';
import {
APIGatewayCert,
APIGatewayHttpMethod,
@@ -106,7 +110,7 @@ const APIGatewayRequestContextV2Schema = z.object({
method: APIGatewayHttpMethod,
path: z.string(),
protocol: z.string(),
- sourceIp: z.string().ip(),
+ sourceIp: z.ipv4(),
userAgent: z.string(),
}),
requestId: z.string(),
@@ -161,6 +165,7 @@ const APIGatewayRequestContextV2Schema = z.object({
* }
* ```
*
+ * @see {@link APIGatewayProxyEventV2 | `APIGatewayProxyEventV2`}
* @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html}
*/
const APIGatewayProxyEventV2Schema = z.object({
@@ -224,6 +229,7 @@ const APIGatewayProxyEventV2Schema = z.object({
* }
* ```
*
+ * @see {@link APIGatewayRequestAuthorizerEventV2 | `APIGatewayRequestAuthorizerEventV2`}
* @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html}
*/
const APIGatewayRequestAuthorizerEventV2Schema = z.object({
diff --git a/packages/parser/src/schemas/apigw-proxy.ts b/packages/parser/src/schemas/apigw-proxy.ts
index ea35ee5191..35ce6b5110 100644
--- a/packages/parser/src/schemas/apigw-proxy.ts
+++ b/packages/parser/src/schemas/apigw-proxy.ts
@@ -17,7 +17,7 @@ const APIGatewayCert = z.object({
/**
* A zod schema for an object with string keys and string values
*/
-const APIGatewayRecord = z.record(z.string());
+const APIGatewayRecord = z.record(z.string(), z.string());
/**
* A zod schema for an array of strings
diff --git a/packages/parser/src/schemas/appsync-events.ts b/packages/parser/src/schemas/appsync-events.ts
index 7c90d68a99..5c7b39f809 100644
--- a/packages/parser/src/schemas/appsync-events.ts
+++ b/packages/parser/src/schemas/appsync-events.ts
@@ -1,4 +1,8 @@
import { z } from 'zod';
+import type {
+ AppSyncEventsPublishEvent,
+ AppSyncEventsSubscribeEvent,
+} from '../types/schema.js';
import {
AppSyncCognitoIdentity,
AppSyncIamIdentity,
@@ -105,6 +109,8 @@ const AppSyncEventsBaseSchema = z.object({
* ]
* }
* ```
+ *
+ * @see {@link AppSyncEventsPublishEvent | `AppSyncEventsPublishEvent`}
*/
const AppSyncEventsPublishSchema = AppSyncEventsBaseSchema.extend({
info: AppSyncEventsInfoSchema.extend({
@@ -151,6 +157,8 @@ const AppSyncEventsPublishSchema = AppSyncEventsBaseSchema.extend({
* "events": null,
* }
* ```
+ *
+ * @see {@link AppSyncEventsSubscribeEvent | `AppSyncEventsSubscribeEvent`}
*/
const AppSyncEventsSubscribeSchema = AppSyncEventsBaseSchema.extend({
info: AppSyncEventsInfoSchema.extend({
diff --git a/packages/parser/src/schemas/appsync-shared.ts b/packages/parser/src/schemas/appsync-shared.ts
index c04d4916e9..7e7278bf49 100644
--- a/packages/parser/src/schemas/appsync-shared.ts
+++ b/packages/parser/src/schemas/appsync-shared.ts
@@ -16,7 +16,7 @@ const AppSyncCognitoIdentity = z.object({
issuer: z.string(),
username: z.string(),
claims: z.record(z.string(), z.unknown()),
- sourceIp: z.array(z.string().ip()),
+ sourceIp: z.array(z.ipv4()),
defaultAuthStrategy: z.string().nullable(),
groups: z.array(z.string()).nullable(),
});
diff --git a/packages/parser/src/schemas/appsync.ts b/packages/parser/src/schemas/appsync.ts
index 610c507d75..ca2c554bec 100644
--- a/packages/parser/src/schemas/appsync.ts
+++ b/packages/parser/src/schemas/appsync.ts
@@ -1,4 +1,8 @@
import { z } from 'zod';
+import type {
+ AppSyncBatchResolverEvent,
+ AppSyncResolverEvent,
+} from '../types/schema.js';
import {
AppSyncCognitoIdentity,
AppSyncIamIdentity,
@@ -80,30 +84,30 @@ const AppSyncIdentity = z.union([
* }
* ```
*
+ * @see {@link AppSyncResolverEvent | `AppSyncResolverEvent`}
* @see {@link https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html}
*/
-
const AppSyncResolverSchema = z.object({
- arguments: z.record(z.any()),
+ arguments: z.record(z.string(), z.any()),
identity: z.optional(AppSyncIdentity),
- source: z.record(z.any()).nullable(),
+ source: z.record(z.string(), z.any()).nullable(),
request: z.object({
domainName: z.string().nullable(),
- headers: z.record(z.string()),
+ headers: z.record(z.string(), z.string()),
}),
info: z.object({
selectionSetList: z.array(z.string()),
selectionSetGraphQL: z.string(),
parentTypeName: z.string(),
fieldName: z.string(),
- variables: z.record(z.any()),
+ variables: z.record(z.string(), z.any()),
}),
prev: z
.object({
- result: z.record(z.any()),
+ result: z.record(z.string(), z.any()),
})
.nullable(),
- stash: z.record(z.any()),
+ stash: z.record(z.string(), z.any()),
});
/**
@@ -224,9 +228,9 @@ const AppSyncResolverSchema = z.object({
* }]
* ```
*
+ * @see {@link AppSyncBatchResolverEvent | `AppSyncBatchResolverEvent`}
* @see {@link https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers.html#advanced-use-case-batching}
*/
-
const AppSyncBatchResolverSchema = z.array(AppSyncResolverSchema);
export {
diff --git a/packages/parser/src/schemas/cloudformation-custom-resource.ts b/packages/parser/src/schemas/cloudformation-custom-resource.ts
index cb18cad58c..ac88f3c03d 100644
--- a/packages/parser/src/schemas/cloudformation-custom-resource.ts
+++ b/packages/parser/src/schemas/cloudformation-custom-resource.ts
@@ -1,13 +1,18 @@
import { z } from 'zod';
+import type {
+ CloudFormationCustomResourceCreateEvent,
+ CloudFormationCustomResourceDeleteEvent,
+ CloudFormationCustomResourceUpdateEvent,
+} from '../types/schema.js';
const CloudFormationCustomResourceBaseSchema = z.object({
ServiceToken: z.string(),
- ResponseURL: z.string().url(),
+ ResponseURL: z.url(),
StackId: z.string(),
RequestId: z.string(),
LogicalResourceId: z.string(),
ResourceType: z.string(),
- ResourceProperties: z.record(z.any()),
+ ResourceProperties: z.record(z.string(), z.any()),
});
/**
@@ -29,15 +34,13 @@ const CloudFormationCustomResourceBaseSchema = z.object({
* }
* }
* ```
- * @see {@link types.CloudFormationCustomResourceCreateEvent | CloudFormationCustomResourceCreateEvent}
+ * @see {@link CloudFormationCustomResourceCreateEvent | `CloudFormationCustomResourceCreateEvent`}
* @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-create.html}
*/
-const CloudFormationCustomResourceCreateSchema =
- CloudFormationCustomResourceBaseSchema.merge(
- z.object({
- RequestType: z.literal('Create'),
- })
- );
+const CloudFormationCustomResourceCreateSchema = z.object({
+ ...CloudFormationCustomResourceBaseSchema.shape,
+ RequestType: z.literal('Create'),
+});
/**
* Zod schema for CloudFormation Custom Resource event with RequestType = 'Delete'
@@ -58,15 +61,13 @@ const CloudFormationCustomResourceCreateSchema =
* }
* }
* ```
- * @see {@link types.CloudFormationCustomResourceDeleteEvent | CloudFormationCustomResourceDeleteEvent}
+ * @see {@link CloudFormationCustomResourceDeleteEvent | `CloudFormationCustomResourceDeleteEvent`}
* @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-delete.html}
*/
-const CloudFormationCustomResourceDeleteSchema =
- CloudFormationCustomResourceBaseSchema.merge(
- z.object({
- RequestType: z.literal('Delete'),
- })
- );
+const CloudFormationCustomResourceDeleteSchema = z.object({
+ ...CloudFormationCustomResourceBaseSchema.shape,
+ RequestType: z.literal('Delete'),
+});
/**
* Zod schema for CloudFormation Custom Resource event with RequestType = 'Update'
@@ -91,16 +92,14 @@ const CloudFormationCustomResourceDeleteSchema =
* }
* }
* ```
- * @see {@link types.CloudFormationCustomResourceUpdateEvent | CloudFormationCustomResourceUpdateEvent}
+ * @see {@link CloudFormationCustomResourceUpdateEvent | `CloudFormationCustomResourceUpdateEvent`}
* @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-update.html}
*/
-const CloudFormationCustomResourceUpdateSchema =
- CloudFormationCustomResourceBaseSchema.merge(
- z.object({
- RequestType: z.literal('Update'),
- OldResourceProperties: z.record(z.any()),
- })
- );
+const CloudFormationCustomResourceUpdateSchema = z.object({
+ ...CloudFormationCustomResourceBaseSchema.shape,
+ RequestType: z.literal('Update'),
+ OldResourceProperties: z.record(z.string(), z.any()),
+});
export {
CloudFormationCustomResourceCreateSchema,
diff --git a/packages/parser/src/schemas/cloudwatch.ts b/packages/parser/src/schemas/cloudwatch.ts
index 0ab849f448..b599160bf5 100644
--- a/packages/parser/src/schemas/cloudwatch.ts
+++ b/packages/parser/src/schemas/cloudwatch.ts
@@ -1,6 +1,6 @@
import { gunzipSync } from 'node:zlib';
import { z } from 'zod';
-import { DecompressError } from '../errors.js';
+import type { CloudWatchLogsEvent } from '../types/schema.js';
const CloudWatchLogEventSchema = z.object({
id: z.string(),
@@ -14,23 +14,9 @@ const CloudWatchLogsDecodeSchema = z.object({
logGroup: z.string(),
logStream: z.string(),
subscriptionFilters: z.array(z.string()),
- logEvents: z.array(CloudWatchLogEventSchema).min(1),
+ logEvents: z.array(CloudWatchLogEventSchema).nonempty(),
});
-const decompressRecordToJSON = (
- data: string
-): z.infer => {
- try {
- const uncompressed = gunzipSync(Buffer.from(data, 'base64')).toString(
- 'utf8'
- );
-
- return CloudWatchLogsDecodeSchema.parse(JSON.parse(uncompressed));
- } catch {
- throw new DecompressError('Failed to decompress CloudWatch log data');
- }
-};
-
/**
* Zod schema for CloudWatch Logs.
*
@@ -74,18 +60,33 @@ const decompressRecordToJSON = (
* }
* ```
*
- * @see {@link types.CloudWatchLogsEvent | CloudWatchLogsEvent}
+ * @see {@link CloudWatchLogsEvent | `CloudWatchLogsEvent`}
* @see {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample}
*/
const CloudWatchLogsSchema = z.object({
awslogs: z.object({
- data: z.string().transform((data) => decompressRecordToJSON(data)),
+ data: z.base64().transform((data, ctx) => {
+ try {
+ const uncompressed = gunzipSync(Buffer.from(data, 'base64')).toString(
+ 'utf8'
+ );
+
+ return CloudWatchLogsDecodeSchema.parse(JSON.parse(uncompressed));
+ } catch {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'Failed to decompress CloudWatch log data',
+ fatal: true,
+ });
+
+ return z.NEVER;
+ }
+ }),
}),
});
export {
CloudWatchLogsSchema,
CloudWatchLogsDecodeSchema,
- decompressRecordToJSON,
CloudWatchLogEventSchema,
};
diff --git a/packages/parser/src/schemas/cognito.ts b/packages/parser/src/schemas/cognito.ts
index d38b582722..a19ab0db52 100644
--- a/packages/parser/src/schemas/cognito.ts
+++ b/packages/parser/src/schemas/cognito.ts
@@ -568,8 +568,8 @@ const CreateAuthChallengeTriggerSchema = CognitoTriggerBaseSchema.extend({
userNotFound: z.boolean().optional(),
}),
response: z.object({
- publicChallengeParameters: z.record(z.string()).nullish(),
- privateChallengeParameters: z.record(z.string()).nullish(),
+ publicChallengeParameters: z.record(z.string(), z.string()).nullish(),
+ privateChallengeParameters: z.record(z.string(), z.string()).nullish(),
challengeMetadata: z.string().nullish(),
}),
});
diff --git a/packages/parser/src/schemas/dynamodb.ts b/packages/parser/src/schemas/dynamodb.ts
index f61ec57734..445bb749a5 100644
--- a/packages/parser/src/schemas/dynamodb.ts
+++ b/packages/parser/src/schemas/dynamodb.ts
@@ -2,6 +2,7 @@ import { unmarshallDynamoDB } from '@aws-lambda-powertools/commons/utils/unmarsh
import { z } from 'zod';
import type { KinesisEnvelope } from '../envelopes/kinesis.js';
import type { DynamoDBMarshalled } from '../helpers/dynamodb.js';
+import type { DynamoDBStreamEvent } from '../types/schema.js';
const DynamoDBStreamChangeRecordBase = z.object({
ApproximateCreationDateTime: z.number().optional(),
@@ -251,15 +252,15 @@ const DynamoDBStreamToKinesisRecord = DynamoDBStreamRecord.extend({
* }
* ```
*
- * @see {@link types.DynamoDBStreamEvent | DynamoDBStreamEvent}
+ * @see {@link DynamoDBStreamEvent | DynamoDBStreamEvent}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html}
*/
const DynamoDBStreamSchema = z.object({
- Records: z.array(DynamoDBStreamRecord).min(1),
+ Records: z.array(DynamoDBStreamRecord).nonempty(),
window: z
.object({
- start: z.string().datetime(),
- end: z.string().datetime(),
+ start: z.iso.datetime(),
+ end: z.iso.datetime(),
})
.optional(),
state: z.record(z.string(), z.string()).optional(),
diff --git a/packages/parser/src/schemas/eventbridge.ts b/packages/parser/src/schemas/eventbridge.ts
index 82b3a48674..fae595dda9 100644
--- a/packages/parser/src/schemas/eventbridge.ts
+++ b/packages/parser/src/schemas/eventbridge.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { EventBridgeEvent } from '../types/schema.js';
/**
* Zod schema for EventBridge event
@@ -24,7 +25,7 @@ import { z } from 'zod';
* }
* ```
*
- * @see {@link types.EventBridgeEvent | EventBridgeEvent}
+ * @see {@link EventBridgeEvent | `EventBridgeEvent`}
* @see {@link https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-and-events.html}
*/
const EventBridgeSchema = z.object({
@@ -32,7 +33,7 @@ const EventBridgeSchema = z.object({
id: z.string(),
source: z.string(),
account: z.string(),
- time: z.string().datetime(),
+ time: z.iso.datetime(),
region: z.string(),
resources: z.array(z.string()),
'detail-type': z.string(),
diff --git a/packages/parser/src/schemas/kafka.ts b/packages/parser/src/schemas/kafka.ts
index eb23e78e3d..3747755a2d 100644
--- a/packages/parser/src/schemas/kafka.ts
+++ b/packages/parser/src/schemas/kafka.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { KafkaMskEvent, KafkaSelfManagedEvent } from '../types/schema.js';
/**
* Zod schema for a Kafka record from an Kafka event.
@@ -33,7 +34,7 @@ const KafkaBaseEventSchema = z.object({
.string()
.transform((bootstrapServers) => bootstrapServers.split(','))
.nullish(),
- records: z.record(z.string(), z.array(KafkaRecordSchema).min(1)),
+ records: z.record(z.string(), z.array(KafkaRecordSchema).nonempty()),
});
/** Zod schema for Kafka event from Self Managed Kafka
@@ -76,7 +77,7 @@ const KafkaBaseEventSchema = z.object({
* }
* ```
*
- * @see {@link types.KafkaSelfManagedEvent | KafkaSelfManagedEvent}
+ * @see {@link KafkaSelfManagedEvent | `KafkaSelfManagedEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/with-kafka.html}
*/
const KafkaSelfManagedEventSchema = KafkaBaseEventSchema.extend({
@@ -125,7 +126,7 @@ const KafkaSelfManagedEventSchema = KafkaBaseEventSchema.extend({
* }
* ```
*
- * @see {@link types.KafkaMskEvent | KafkaMskEvent}
+ * @see {@link KafkaMskEvent | `KafkaMskEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html}
*/
const KafkaMskEventSchema = KafkaBaseEventSchema.extend({
diff --git a/packages/parser/src/schemas/kinesis-firehose.ts b/packages/parser/src/schemas/kinesis-firehose.ts
index dddb628e01..2b63ef2e01 100644
--- a/packages/parser/src/schemas/kinesis-firehose.ts
+++ b/packages/parser/src/schemas/kinesis-firehose.ts
@@ -1,4 +1,8 @@
import { z } from 'zod';
+import type {
+ KinesisFireHoseEvent,
+ KinesisFireHoseSqsEvent,
+} from '../types/schema.js';
import { SqsRecordSchema } from './sqs.js';
const KinesisRecordMetadata = z.object({
@@ -27,7 +31,7 @@ const KinesisFireHoseBaseSchema = z.object({
*/
const KinesisFirehoseRecordSchema = KinesisFireHoseRecordBase.extend({
data: z
- .string()
+ .base64()
.transform((data) => Buffer.from(data, 'base64').toString('utf8')),
});
@@ -42,7 +46,7 @@ const KinesisFirehoseSqsRecordSchema = KinesisFireHoseRecordBase.extend({
);
} catch {
ctx.addIssue({
- code: z.ZodIssueCode.custom,
+ code: 'custom',
message: 'Failed to parse SQS record',
fatal: true,
});
@@ -91,11 +95,11 @@ const KinesisFirehoseSqsRecordSchema = KinesisFireHoseRecordBase.extend({
* }
* ```
*
- * @see {@link types.KinesisFireHoseEvent | KinesisFireHoseEvent}
+ * @see {@link KinesisFireHoseEvent | `KinesisFireHoseEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/services-kinesisfirehose.html}
*/
const KinesisFirehoseSchema = KinesisFireHoseBaseSchema.extend({
- records: z.array(KinesisFirehoseRecordSchema).min(1),
+ records: z.array(KinesisFirehoseRecordSchema).nonempty(),
});
/**
@@ -117,10 +121,10 @@ const KinesisFirehoseSchema = KinesisFireHoseBaseSchema.extend({
* }
* ```
*
- * @see {@link types.KinesisFireHoseSqsEvent | KinesisFireHoseSqsEvent}
+ * @see {@link KinesisFireHoseSqsEvent | `KinesisFireHoseSqsEvent`}
*/
const KinesisFirehoseSqsSchema = KinesisFireHoseBaseSchema.extend({
- records: z.array(KinesisFirehoseSqsRecordSchema).min(1),
+ records: z.array(KinesisFirehoseSqsRecordSchema).nonempty(),
});
export {
diff --git a/packages/parser/src/schemas/kinesis.ts b/packages/parser/src/schemas/kinesis.ts
index e0ea622359..882812e3ff 100644
--- a/packages/parser/src/schemas/kinesis.ts
+++ b/packages/parser/src/schemas/kinesis.ts
@@ -1,6 +1,7 @@
import { gunzipSync } from 'node:zlib';
import { fromBase64 } from '@aws-lambda-powertools/commons/utils/base64';
import { z } from 'zod';
+import type { KinesisDataStreamEvent } from '../types/schema.js';
import { DynamoDBStreamToKinesisRecord } from './dynamodb.js';
const decoder = new TextDecoder();
@@ -95,7 +96,7 @@ const KinesisDynamoDBStreamSchema = z.object({
* "isWindowTerminatedEarly": false
* }
*```
- * @see {@link types.KinesisDataStreamEvent | KinesisDataStreamEvent}
+ * @see {@link KinesisDataStreamEvent | `KinesisDataStreamEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/services-kinesis-windows.html#streams-tumbling-processing}
*
*/
@@ -103,8 +104,8 @@ const KinesisDataStreamSchema = z.object({
Records: z.array(KinesisDataStreamRecord).min(1),
window: z
.object({
- start: z.string().datetime(),
- end: z.string().datetime(),
+ start: z.iso.datetime(),
+ end: z.iso.datetime(),
})
.optional(),
state: z.record(z.string(), z.unknown()).optional(),
diff --git a/packages/parser/src/schemas/lambda.ts b/packages/parser/src/schemas/lambda.ts
index e5fe679329..471e6a09e4 100644
--- a/packages/parser/src/schemas/lambda.ts
+++ b/packages/parser/src/schemas/lambda.ts
@@ -1,3 +1,4 @@
+import type { LambdaFunctionUrlEvent } from '../types/schema.js';
import { APIGatewayProxyEventV2Schema } from './api-gatewayv2.js';
/**
@@ -56,7 +57,7 @@ import { APIGatewayProxyEventV2Schema } from './api-gatewayv2.js';
* }
* ```
*
- * @see {@link types.LambdaFunctionUrlEvent | LambdaFunctionUrlEvent}
+ * @see {@link LambdaFunctionUrlEvent | `LambdaFunctionUrlEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads}
*/
const LambdaFunctionUrlSchema = APIGatewayProxyEventV2Schema.extend({});
diff --git a/packages/parser/src/schemas/s3.ts b/packages/parser/src/schemas/s3.ts
index 650712a78a..66fd81ee7a 100644
--- a/packages/parser/src/schemas/s3.ts
+++ b/packages/parser/src/schemas/s3.ts
@@ -1,5 +1,11 @@
import { z } from 'zod';
import { JSONStringified } from '../helpers/index.js';
+import type {
+ S3Event,
+ S3EventNotificationEventBridge,
+ S3ObjectLambdaEvent,
+ S3SqsEventNotification,
+} from '../types/schema.js';
import { EventBridgeSchema } from './eventbridge.js';
import { SqsRecordSchema } from './sqs.js';
@@ -8,7 +14,7 @@ const S3Identity = z.object({
});
const S3RequestParameters = z.object({
- sourceIPAddress: z.union([z.string().ip(), z.literal('s3.amazonaws.com')]),
+ sourceIPAddress: z.union([z.ipv4(), z.literal('s3.amazonaws.com')]),
});
const S3ResponseElements = z.object({
@@ -45,7 +51,7 @@ const S3RecordSchema = z.object({
eventVersion: z.string(),
eventSource: z.literal('aws:s3'),
awsRegion: z.string(),
- eventTime: z.string().datetime(),
+ eventTime: z.iso.datetime(),
eventName: z.string(),
userIdentity: S3Identity,
requestParameters: S3RequestParameters,
@@ -68,7 +74,7 @@ const S3EventNotificationEventBridgeDetailSchema = z.object({
}),
'request-id': z.string(),
requester: z.string(),
- 'source-ip-address': z.string().ip().optional(),
+ 'source-ip-address': z.ipv4().optional(),
reason: z.string().optional(),
'deletion-type': z.string().optional(),
'restore-expiry-time': z.string().optional(),
@@ -112,7 +118,7 @@ const S3EventNotificationEventBridgeDetailSchema = z.object({
* }
* ```
*
- * @see {@link types.S3EventNotificationEventBridge | S3EventNotificationEventBridge }
+ * @see {@link S3EventNotificationEventBridge | `S3EventNotificationEventBridge` }
* @see {@link https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html#ev-events-list}
*/
const S3EventNotificationEventBridgeSchema = EventBridgeSchema.extend({
@@ -163,7 +169,7 @@ const S3EventNotificationEventBridgeSchema = EventBridgeSchema.extend({
* ]
* }
* ```
- * @see {@link types.S3Event | S3Event }
+ * @see {@link S3Event | `S3Event` }
* @see {@link https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html}
*/
const S3Schema = z.object({
@@ -205,14 +211,14 @@ const S3SqsEventNotificationRecordSchema = SqsRecordSchema.extend({
* }
* ```
*
- * @see {@link types.S3SqsEventNotification | S3SqsEventNotification }
+ * @see {@link S3SqsEventNotification | `S3SqsEventNotification` }
*/
const S3SqsEventNotificationSchema = z.object({
- Records: z.array(S3SqsEventNotificationRecordSchema).min(1),
+ Records: z.array(S3SqsEventNotificationRecordSchema).nonempty(),
});
const S3ObjectContext = z.object({
- inputS3Url: z.string().url(),
+ inputS3Url: z.string(),
outputRoute: z.string(),
outputToken: z.string(),
});
@@ -291,8 +297,8 @@ const S3ObjectUserIdentity = z.object({
* }
* ```
*
+ * @see {@link S3ObjectLambdaEvent | `S3ObjectLambdaEvent` }
* @see {@link https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-event-context.html}
- * @see {@link types.S3ObjectLambdaEvent | S3ObjectLambdaEvent }
*/
const S3ObjectLambdaEventSchema = z.object({
xAmzRequestId: z.string(),
diff --git a/packages/parser/src/schemas/ses.ts b/packages/parser/src/schemas/ses.ts
index 8121d70c70..09dc915492 100644
--- a/packages/parser/src/schemas/ses.ts
+++ b/packages/parser/src/schemas/ses.ts
@@ -1,11 +1,12 @@
import { z } from 'zod';
+import type { SesEvent } from '../types/schema.js';
const SesReceiptVerdict = z.object({
status: z.enum(['PASS', 'FAIL', 'GRAY', 'PROCESSING_FAILED']),
});
const SesReceipt = z.object({
- timestamp: z.string().datetime(),
+ timestamp: z.iso.datetime(),
processingTimeMillis: z.number().int().positive(),
recipients: z.array(z.string()),
spamVerdict: SesReceiptVerdict,
@@ -22,7 +23,7 @@ const SesReceipt = z.object({
});
const SesMail = z.object({
- timestamp: z.string().datetime(),
+ timestamp: z.iso.datetime(),
source: z.string(),
messageId: z.string(),
destination: z.array(z.string()),
@@ -169,11 +170,11 @@ const SesRecordSchema = z.object({
* }
* ```
*
- * @see {@link types.SesEvent | SesEvent}
+ * @see {@link SesEvent | SesEvent}
* @see {@link https://docs.aws.amazon.com/ses/latest/dg/receiving-email-notifications-examples.html}
*/
const SesSchema = z.object({
- Records: z.array(SesRecordSchema).min(1),
+ Records: z.array(SesRecordSchema).nonempty(),
});
export { SesSchema, SesRecordSchema };
diff --git a/packages/parser/src/schemas/sns.ts b/packages/parser/src/schemas/sns.ts
index a76711e014..b62cb950a1 100644
--- a/packages/parser/src/schemas/sns.ts
+++ b/packages/parser/src/schemas/sns.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { SnsEvent, SnsSqsNotification } from '../types/schema.js';
const SnsMsgAttribute = z.object({
Type: z.string(),
@@ -11,17 +12,17 @@ const SnsMsgAttribute = z.object({
const SnsNotificationSchema = z.object({
Subject: z.string().nullish(),
TopicArn: z.string(),
- UnsubscribeUrl: z.string().url(),
- UnsubscribeURL: z.string().url().optional(),
- SigningCertUrl: z.string().url().optional(),
- SigningCertURL: z.string().url().optional(),
+ UnsubscribeUrl: z.url(),
+ UnsubscribeURL: z.url().optional(),
+ SigningCertUrl: z.url().optional(),
+ SigningCertURL: z.url().optional(),
Type: z.literal('Notification'),
MessageAttributes: z.record(z.string(), SnsMsgAttribute).optional(),
Message: z.string(),
MessageId: z.string(),
Signature: z.string().optional(),
SignatureVersion: z.string().optional(),
- Timestamp: z.string().datetime(),
+ Timestamp: z.iso.datetime(),
});
/**
@@ -51,11 +52,11 @@ const SnsNotificationSchema = z.object({
* }
* ```
*
- * @see {@link types.SnsSqsNotification | SnsSqsNotification}
+ * @see {@link SnsSqsNotification | `SnsSqsNotification`}
*/
const SnsSqsNotificationSchema = SnsNotificationSchema.extend({
UnsubscribeURL: z.string().optional(),
- SigningCertURL: z.string().url().optional(),
+ SigningCertURL: z.url().optional(),
}).omit({
UnsubscribeUrl: true,
SigningCertUrl: true,
@@ -109,11 +110,11 @@ const SnsRecordSchema = z.object({
* }
* ```
*
- * @see {@link types.SnsEvent | SnsEvent}
+ * @see {@link SnsEvent | `SnsEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html#sns-sample-event}
*/
const SnsSchema = z.object({
- Records: z.array(SnsRecordSchema).min(1),
+ Records: z.array(SnsRecordSchema).nonempty(),
});
export {
diff --git a/packages/parser/src/schemas/sqs.ts b/packages/parser/src/schemas/sqs.ts
index 83fde66e6c..0947fea96a 100644
--- a/packages/parser/src/schemas/sqs.ts
+++ b/packages/parser/src/schemas/sqs.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { SqsEvent } from '../types/schema.js';
const SqsMsgAttributeDataTypeSchema = z.union([
z.literal('String'),
@@ -96,11 +97,11 @@ const SqsRecordSchema = z.object({
* }
* ```
*
- * @see {@link types.SqsEvent | SqsEvent}
+ * @see {@link SqsEvent | `SqsEvent`}
* @see {@link https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#example-standard-queue-message-event}
*/
const SqsSchema = z.object({
- Records: z.array(SqsRecordSchema).min(1),
+ Records: z.array(SqsRecordSchema).nonempty(),
});
export {
diff --git a/packages/parser/src/schemas/transfer-family.ts b/packages/parser/src/schemas/transfer-family.ts
index 145a45d43a..feb272f01a 100644
--- a/packages/parser/src/schemas/transfer-family.ts
+++ b/packages/parser/src/schemas/transfer-family.ts
@@ -1,7 +1,7 @@
import { z } from 'zod';
+import type { TransferFamilyEvent } from '../types/schema.js';
/**
- *
* Zod schema for AWS Transfer Family events.
*
* @example
@@ -15,15 +15,15 @@ import { z } from 'zod';
* }
* ```
*
- * TransferFamilySchema validates events coming from AWS Transfer Family.
- *
+ * @see {@link TransferFamilyEvent | `TransferFamilyEvent`}
+ * @see {@link https://docs.aws.amazon.com/transfer/latest/userguide/custom-lambda-idp.html}
*/
const TransferFamilySchema = z.object({
username: z.string(),
password: z.string(),
protocol: z.string(),
serverId: z.string(),
- sourceIp: z.string().ip(),
+ sourceIp: z.ipv4(),
});
export { TransferFamilySchema };
diff --git a/packages/parser/src/schemas/vpc-lattice.ts b/packages/parser/src/schemas/vpc-lattice.ts
index 569caf2c93..c9bf434ad1 100644
--- a/packages/parser/src/schemas/vpc-lattice.ts
+++ b/packages/parser/src/schemas/vpc-lattice.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { VpcLatticeEvent } from '../types/schema.js';
/**
* Zod schema for VPC Lattice event
@@ -22,7 +23,7 @@ import { z } from 'zod';
*}
* ```
*
- * @see {@link types.VpcLatticeEvent | VpcLatticeEvent}
+ * @see {@link VpcLatticeEvent | `VpcLatticeEvent`}
* @see {@link https://docs.aws.amazon.com/vpc-lattice/latest/ug/lambda-functions.html#receive-event-from-service}
*/
const VpcLatticeSchema = z.object({
diff --git a/packages/parser/src/schemas/vpc-latticev2.ts b/packages/parser/src/schemas/vpc-latticev2.ts
index ef80f0a8e3..19c4d80c40 100644
--- a/packages/parser/src/schemas/vpc-latticev2.ts
+++ b/packages/parser/src/schemas/vpc-latticev2.ts
@@ -1,4 +1,5 @@
import { z } from 'zod';
+import type { VpcLatticeEventV2 } from '../types/schema.js';
const VpcLatticeV2RequestContextIdentity = z.object({
sourceVpcArn: z.string().optional(),
@@ -58,7 +59,7 @@ const VpcLatticeV2RequestContext = z.object({
* }
* }
* ```
- * @see {@link types.VpcLatticeEventV2 | VpcLatticeEventV2}
+ * @see {@link VpcLatticeEventV2 | `VpcLatticeEventV2`}
* @see {@link https://docs.aws.amazon.com/vpc-lattice/latest/ug/lambda-functions.html#receive-event-from-service}
*/
const VpcLatticeV2Schema = z.object({
diff --git a/packages/parser/src/types/envelope.ts b/packages/parser/src/types/envelope.ts
index 4659ccad76..6d5669fb5e 100644
--- a/packages/parser/src/types/envelope.ts
+++ b/packages/parser/src/types/envelope.ts
@@ -1,34 +1,45 @@
-import type { ZodSchema, z } from 'zod';
+import type { ZodType } from 'zod';
import type { envelopeDiscriminator } from '../envelopes/envelope.js';
import type { ParsedResult } from './parser.js';
-type DynamoDBStreamEnvelopeResponse = {
- NewImage?: z.infer;
- OldImage?: z.infer;
+type DynamoDBStreamEnvelopeResponse = {
+ NewImage?: T;
+ OldImage?: T;
};
interface ArrayEnvelope {
[envelopeDiscriminator]: 'array';
- parse(data: unknown, schema: T): z.infer[];
- safeParse(
+ parse(data: unknown, schema: ZodType): T[];
+ safeParse(data: unknown, schema: ZodType): ParsedResult;
+}
+
+interface DynamoDBArrayEnvelope {
+ [envelopeDiscriminator]: 'array';
+ parse(
data: unknown,
- schema: T
- ): ParsedResult[]>;
+ schema: ZodType
+ ): DynamoDBStreamEnvelopeResponse[];
+ safeParse(
+ data: unknown,
+ schema: ZodType
+ ): ParsedResult[]>;
}
interface ObjectEnvelope {
[envelopeDiscriminator]: 'object';
- parse(data: unknown, schema: T): z.infer;
- safeParse(
- data: unknown,
- schema: T
- ): ParsedResult>;
+ parse(data: unknown, schema: ZodType): T;
+ safeParse(data: unknown, schema: ZodType): ParsedResult;
}
-type Envelope = ArrayEnvelope | ObjectEnvelope | undefined;
+type Envelope =
+ | ArrayEnvelope
+ | DynamoDBArrayEnvelope
+ | ObjectEnvelope
+ | undefined;
export type {
ArrayEnvelope,
+ DynamoDBArrayEnvelope,
DynamoDBStreamEnvelopeResponse,
Envelope,
ObjectEnvelope,
diff --git a/packages/parser/src/types/index.ts b/packages/parser/src/types/index.ts
index 213e89691e..d325dadc81 100644
--- a/packages/parser/src/types/index.ts
+++ b/packages/parser/src/types/index.ts
@@ -5,16 +5,22 @@ export type {
ParseFunction,
ParserOptions,
} from '../types/parser.js';
-export type { Envelope } from './envelope.js';
+export type {
+ ArrayEnvelope,
+ DynamoDBArrayEnvelope,
+ DynamoDBStreamEnvelopeResponse,
+ Envelope,
+ ObjectEnvelope,
+} from './envelope.js';
export type {
ALBEvent,
APIGatewayEventRequestContext,
APIGatewayProxyEvent,
APIGatewayProxyEventV2,
+ APIGatewayProxyWebsocketEvent,
APIGatewayRequestAuthorizerEvent,
- APIGatewayRequestAuthorizerV2,
- APIGatewayRequestContextV2,
+ APIGatewayRequestAuthorizerEventV2,
APIGatewayTokenAuthorizerEvent,
AppSyncBatchResolverEvent,
AppSyncEventsPublishEvent,
diff --git a/packages/parser/src/types/parser.ts b/packages/parser/src/types/parser.ts
index 91b423e8cc..56f4f1d109 100644
--- a/packages/parser/src/types/parser.ts
+++ b/packages/parser/src/types/parser.ts
@@ -1,11 +1,16 @@
-import type { ZodError, ZodSchema, z } from 'zod';
-import type { ArrayEnvelope, Envelope } from './envelope.js';
+import type { StandardSchemaV1 } from '@standard-schema/spec';
+import type {
+ ArrayEnvelope,
+ DynamoDBArrayEnvelope,
+ DynamoDBStreamEnvelopeResponse,
+ Envelope,
+} from './envelope.js';
/**
* Options for the parser used in middy middleware and decorator
*/
type ParserOptions<
- TSchema extends ZodSchema,
+ TSchema extends StandardSchemaV1,
TEnvelope extends Envelope,
TSafeParse extends boolean,
> = {
@@ -27,7 +32,7 @@ type ParsedResultSuccess