1
+ import type { StandardSchemaV1 } from '@standard-schema/spec' ;
2
+ import type { StreamRecord } from 'aws-lambda' ;
1
3
import { BasePartialBatchProcessor } from './BasePartialBatchProcessor.js' ;
4
+ import { EventType , SchemaVendor } from './constants.js' ;
2
5
import { BatchProcessingError } from './errors.js' ;
3
- import type { BaseRecord , FailureResponse , SuccessResponse } from './types.js' ;
6
+ import type {
7
+ BaseRecord ,
8
+ EventSourceDataClassTypes ,
9
+ FailureResponse ,
10
+ SuccessResponse ,
11
+ } from './types.js' ;
4
12
5
13
/**
6
14
* Process records in a batch asynchronously and handle partial failure cases.
@@ -79,7 +87,7 @@ import type { BaseRecord, FailureResponse, SuccessResponse } from './types.js';
79
87
* });
80
88
* ```
81
89
*
82
- * @param eventType The type of event to process (SQS, Kinesis, DynamoDB)
90
+ * @param eventType - The type of event to process (SQS, Kinesis, DynamoDB)
83
91
*/
84
92
class BatchProcessor extends BasePartialBatchProcessor {
85
93
/**
@@ -94,13 +102,17 @@ class BatchProcessor extends BasePartialBatchProcessor {
94
102
* If the handler function completes successfully, the method returns a success response.
95
103
* Otherwise, it returns a failure response with the error that occurred during processing.
96
104
*
97
- * @param record The record to be processed
105
+ * @param record - The record to be processed
98
106
*/
99
107
public async processRecord (
100
108
record : BaseRecord
101
109
) : Promise < SuccessResponse | FailureResponse > {
102
110
try {
103
- const data = this . toBatchType ( record , this . eventType ) ;
111
+ const recordToProcess =
112
+ this . schema == null
113
+ ? record
114
+ : await this . #parseRecord( record , this . eventType , this . schema ) ;
115
+ const data = this . toBatchType ( recordToProcess , this . eventType ) ;
104
116
const result = await this . handler ( data , this . options ?. context ) ;
105
117
106
118
return this . successHandler ( record , result ) ;
@@ -112,7 +124,7 @@ class BatchProcessor extends BasePartialBatchProcessor {
112
124
/**
113
125
* @throws {BatchProcessingError } This method is not implemented for synchronous processing.
114
126
*
115
- * @param _record The record to be processed
127
+ * @param _record - The record to be processed
116
128
*/
117
129
public processRecordSync (
118
130
_record : BaseRecord
@@ -121,6 +133,163 @@ class BatchProcessor extends BasePartialBatchProcessor {
121
133
'Not implemented. Use asyncProcess() instead.'
122
134
) ;
123
135
}
136
+
137
+ /**
138
+ * Extend the schema according to the event type passed.
139
+ *
140
+ * If useTransformers is true, extend using opinionated transformers.
141
+ * Otherwise, extend without any transformers.
142
+ *
143
+ * @param eventType - The type of event to process (SQS, Kinesis, DynamoDB)
144
+ * @param schema - The StandardSchema to be used for parsing
145
+ * @param useTransformers - Whether to use transformers for parsing
146
+ */
147
+ async #createExtendedSchema( options : {
148
+ eventType : keyof typeof EventType ;
149
+ schema : StandardSchemaV1 ;
150
+ useTransformers : boolean ;
151
+ } ) {
152
+ const { eventType, schema, useTransformers } = options ;
153
+ switch ( eventType ) {
154
+ case EventType . SQS : {
155
+ if ( useTransformers ) {
156
+ const [ { JSONStringified } , { SqsRecordSchema } ] = await Promise . all ( [
157
+ import ( '@aws-lambda-powertools/parser/helpers' ) ,
158
+ import ( '@aws-lambda-powertools/parser/schemas/sqs' ) ,
159
+ ] ) ;
160
+ return SqsRecordSchema . extend ( {
161
+ body : JSONStringified ( schema as any ) ,
162
+ } ) ;
163
+ }
164
+ const { SqsRecordSchema } = await import (
165
+ '@aws-lambda-powertools/parser/schemas/sqs'
166
+ ) ;
167
+ return SqsRecordSchema . extend ( { body : schema } ) ;
168
+ }
169
+
170
+ case EventType . KinesisDataStreams : {
171
+ if ( useTransformers ) {
172
+ const [
173
+ { Base64Encoded } ,
174
+ { KinesisDataStreamRecord, KinesisDataStreamRecordPayload } ,
175
+ ] = await Promise . all ( [
176
+ import ( '@aws-lambda-powertools/parser/helpers' ) ,
177
+ import ( '@aws-lambda-powertools/parser/schemas/kinesis' ) ,
178
+ ] ) ;
179
+ return KinesisDataStreamRecord . extend ( {
180
+ kinesis : KinesisDataStreamRecordPayload . extend ( {
181
+ data : Base64Encoded ( schema as any ) ,
182
+ } ) ,
183
+ } ) ;
184
+ }
185
+ const { KinesisDataStreamRecord, KinesisDataStreamRecordPayload } =
186
+ await import ( '@aws-lambda-powertools/parser/schemas/kinesis' ) ;
187
+ return KinesisDataStreamRecord . extend ( {
188
+ kinesis : KinesisDataStreamRecordPayload . extend ( { data : schema } ) ,
189
+ } ) ;
190
+ }
191
+
192
+ case EventType . DynamoDBStreams : {
193
+ if ( useTransformers ) {
194
+ const [
195
+ { DynamoDBMarshalled } ,
196
+ { DynamoDBStreamRecord, DynamoDBStreamChangeRecordBase } ,
197
+ ] = await Promise . all ( [
198
+ import ( '@aws-lambda-powertools/parser/helpers/dynamodb' ) ,
199
+ import ( '@aws-lambda-powertools/parser/schemas/dynamodb' ) ,
200
+ ] ) ;
201
+ return DynamoDBStreamRecord . extend ( {
202
+ dynamodb : DynamoDBStreamChangeRecordBase . extend ( {
203
+ OldImage : DynamoDBMarshalled < StreamRecord [ 'OldImage' ] > (
204
+ schema as any
205
+ ) . optional ( ) ,
206
+ NewImage : DynamoDBMarshalled < StreamRecord [ 'NewImage' ] > (
207
+ schema as any
208
+ ) . optional ( ) ,
209
+ } ) ,
210
+ } ) ;
211
+ }
212
+ const { DynamoDBStreamRecord, DynamoDBStreamChangeRecordBase } =
213
+ await import ( '@aws-lambda-powertools/parser/schemas/dynamodb' ) ;
214
+ return DynamoDBStreamRecord . extend ( {
215
+ dynamodb : DynamoDBStreamChangeRecordBase . extend ( {
216
+ OldImage : ( schema as any ) . optional ( ) ,
217
+ NewImage : ( schema as any ) . optional ( ) ,
218
+ } ) ,
219
+ } ) ;
220
+ }
221
+
222
+ default : {
223
+ console . warn (
224
+ `The event type provided is not supported. Supported events: ${ Object . values ( EventType ) . join ( ',' ) } `
225
+ ) ;
226
+ throw new Error ( 'Unsupported event type' ) ;
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Parse the record according to the schema and event type passed.
233
+ *
234
+ * If the passed schema is already an extended schema,
235
+ * use the schema directly to parse the record.
236
+ *
237
+ * Only Zod Schemas are supported for schema extension.
238
+ *
239
+ * @param record - The record to be parsed
240
+ * @param eventType - The type of event to process
241
+ * @param schema - The StandardSchema to be used for parsing
242
+ */
243
+ async #parseRecord(
244
+ record : EventSourceDataClassTypes ,
245
+ eventType : keyof typeof EventType ,
246
+ schema : StandardSchemaV1
247
+ ) : Promise < EventSourceDataClassTypes > {
248
+ const { parse } = await import ( '@aws-lambda-powertools/parser' ) ;
249
+ // Try parsing with the original schema first
250
+ const extendedSchemaParsing = parse ( record , undefined , schema , true ) ;
251
+ if ( extendedSchemaParsing . success ) {
252
+ return extendedSchemaParsing . data as EventSourceDataClassTypes ;
253
+ }
254
+ // Only proceed with schema extension if it's a Zod schema
255
+ if ( schema [ '~standard' ] . vendor !== SchemaVendor . Zod ) {
256
+ console . warn (
257
+ 'The schema provided is not supported. Only Zod schemas are supported for extension.'
258
+ ) ;
259
+ throw new Error ( 'Unsupported schema type' ) ;
260
+ }
261
+ // Handle schema extension based on event type
262
+ // Try without transformers first, then with transformers
263
+ const schemaWithoutTransformers = await this . #createExtendedSchema( {
264
+ eventType,
265
+ schema,
266
+ useTransformers : false ,
267
+ } ) ;
268
+ const schemaWithoutTransformersParsing = parse (
269
+ record ,
270
+ undefined ,
271
+ schemaWithoutTransformers ,
272
+ true
273
+ ) ;
274
+ if ( schemaWithoutTransformersParsing . success ) {
275
+ return schemaWithoutTransformersParsing . data as EventSourceDataClassTypes ;
276
+ }
277
+ const schemaWithTransformers = await this . #createExtendedSchema( {
278
+ eventType,
279
+ schema,
280
+ useTransformers : true ,
281
+ } ) ;
282
+ const schemaWithTransformersParsing = parse (
283
+ record ,
284
+ undefined ,
285
+ schemaWithTransformers ,
286
+ true
287
+ ) ;
288
+ if ( schemaWithTransformersParsing . success ) {
289
+ return schemaWithTransformersParsing . data as EventSourceDataClassTypes ;
290
+ }
291
+ throw new Error ( 'Failed to parse record' ) ;
292
+ }
124
293
}
125
294
126
295
export { BatchProcessor } ;
0 commit comments