From 66ca32ba7c5e931b048848aa07540aa8ffe59b75 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 15 Dec 2024 16:22:28 +0600 Subject: [PATCH 01/10] feat: add peer dependency for `@aws-sdk/util-dynamodb` --- packages/parser/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/parser/package.json b/packages/parser/package.json index a504e15cc2..8e7015f722 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -358,7 +358,8 @@ ], "peerDependencies": { "@middy/core": "4.x || 5.x || 6.x", - "zod": ">=3.x" + "zod": ">=3.x", + "@aws-sdk/util-dynamodb": ">=3.x" }, "peerDependenciesMeta": { "zod": { From 02940140f24582f3a26fa4dbf5b328f1d3e03667 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 11:04:35 +0600 Subject: [PATCH 02/10] feat: `DynamoDBMarshalled` function --- packages/parser/src/helpers.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/parser/src/helpers.ts b/packages/parser/src/helpers.ts index 10915620d7..dd3c3c8f43 100644 --- a/packages/parser/src/helpers.ts +++ b/packages/parser/src/helpers.ts @@ -1,3 +1,5 @@ +import type { AttributeValue } from '@aws-sdk/client-dynamodb'; +import { unmarshall } from '@aws-sdk/util-dynamodb'; import { type ZodTypeAny, z } from 'zod'; /** * @typedef {import('../schemas/alb').AlbSchema} AlbSchema @@ -56,4 +58,22 @@ const JSONStringified = (schema: T) => }) .pipe(schema); -export { JSONStringified }; +const DynamoDBMarshalled = (schema: T) => + z + .record(z.string(), z.custom()) + .transform((str, ctx) => { + try { + return unmarshall(str); + } catch (err) { + ctx.addIssue({ + code: 'custom', + message: 'Could not unmarshall DynamoDB stream record', + fatal: true, + }); + + return z.NEVER; + } + }) + .pipe(schema); + +export { JSONStringified, DynamoDBMarshalled }; From 4f12aef6acefaf2c6ed5752c2b56ed0d62d4baa4 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 11:05:56 +0600 Subject: [PATCH 03/10] doc: `getTestEvent` function description for better understanding --- packages/parser/tests/unit/schema/utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/parser/tests/unit/schema/utils.ts b/packages/parser/tests/unit/schema/utils.ts index aa5c9ab531..261b24f23d 100644 --- a/packages/parser/tests/unit/schema/utils.ts +++ b/packages/parser/tests/unit/schema/utils.ts @@ -121,6 +121,14 @@ const createTestEvents = (fileList: readonly string[]): TestEvents => { export const TestEvents = createTestEvents(filenames); +/** + * Reads and parses a JSON file from the specified events path and filename, returning the parsed object. + * + * @template T - The expected type of the parsed JSON object. + * @param {Object} params - The parameters for the function. + * @param {string} params.eventsPath - The relative path to the directory containing the event files. + * @param {string} params.filename - The name of the JSON file (without extension) to be read and parsed. + */ export const getTestEvent = >({ eventsPath, filename, From bbf43e6cd28018a98f77064aefa977e89979e45d Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 11:24:28 +0600 Subject: [PATCH 04/10] test: `DynamoDBMarshalled` function for dynamodb stream --- packages/parser/tests/unit/helpers.test.ts | 177 ++++++++++++++++++++- 1 file changed, 174 insertions(+), 3 deletions(-) diff --git a/packages/parser/tests/unit/helpers.test.ts b/packages/parser/tests/unit/helpers.test.ts index d52d0f44df..852a7b5c4a 100644 --- a/packages/parser/tests/unit/helpers.test.ts +++ b/packages/parser/tests/unit/helpers.test.ts @@ -1,13 +1,21 @@ import { describe, expect, it } from 'vitest'; -import { z } from 'zod'; -import { JSONStringified } from '../../src/helpers.js'; +import { ZodError, z } from 'zod'; +import { DynamoDBMarshalled, JSONStringified } from '../../src/helpers.js'; import { AlbSchema } from '../../src/schemas/alb.js'; +import { + DynamoDBStreamRecord, + DynamoDBStreamSchema, +} from '../../src/schemas/dynamodb'; import { SnsNotificationSchema, SnsRecordSchema, } from '../../src/schemas/sns.js'; import { SqsRecordSchema, SqsSchema } from '../../src/schemas/sqs.js'; -import type { SnsEvent, SqsEvent } from '../../src/types/schema.js'; +import type { + DynamoDBStreamEvent, + SnsEvent, + SqsEvent, +} from '../../src/types/schema.js'; import { getTestEvent } from './schema/utils.js'; const bodySchema = z.object({ @@ -152,3 +160,166 @@ describe('JSONStringified', () => { }); }); }); + +describe('DynamoDBMarshalled', () => { + // Prepare + const schema = z.object({ + Message: z.string(), + Id: z.number(), + }); + + it('should correctly unmarshall and validate a valid DynamoDB stream record', () => { + // Prepare + const testInput = [ + { + Message: { + S: 'New item!', + }, + Id: { + N: '101', + }, + }, + { + Message: { + S: 'This item has changed', + }, + Id: { + N: '101', + }, + }, + ]; + const expectedOutput = [ + { + Id: 101, + Message: 'New item!', + }, + { + Id: 101, + Message: 'This item has changed', + }, + ]; + + const testEvent = getTestEvent({ + eventsPath: '.', + filename: 'dynamoStreamEvent', + }); + + testEvent.Records[0].dynamodb.NewImage = testInput[0]; + testEvent.Records[1].dynamodb.NewImage = testInput[1]; + + // Act + const extendedSchema = DynamoDBStreamSchema.extend({ + Records: z.array( + DynamoDBStreamRecord.extend({ + dynamodb: z.object({ + NewImage: DynamoDBMarshalled(schema).optional(), + }), + }) + ), + }); + + // Assess + expect(extendedSchema.parse(testEvent)).toStrictEqual({ + Records: [ + { + ...testEvent.Records[0], + dynamodb: { + NewImage: expectedOutput[0], + }, + }, + { + ...testEvent.Records[1], + dynamodb: { + NewImage: expectedOutput[1], + }, + }, + ], + }); + }); + + it('should throw an error if the DynamoDB stream record cannot be unmarshalled', () => { + // Prepare + const testInput = [ + { + Message: { + S: 'New item!', + }, + Id: { + NNN: '101', + }, + }, + { + Message: { + S: 'This item has changed', + }, + Id: { + N: '101', + }, + }, + ]; + + const testEvent = getTestEvent({ + eventsPath: '.', + filename: 'dynamoStreamEvent', + }); + + testEvent.Records[0].dynamodb.NewImage = testInput[0]; + testEvent.Records[1].dynamodb.NewImage = testInput[1]; + + // Act + const extendedSchema = DynamoDBStreamSchema.extend({ + Records: z.array( + DynamoDBStreamRecord.extend({ + dynamodb: z.object({ + NewImage: DynamoDBMarshalled(schema).optional(), + }), + }) + ), + }); + + // Assess + expect(() => extendedSchema.parse(testEvent)).toThrow(); + }); + + it('should throw a validation error if the unmarshalled record does not match the schema', () => { + // Prepare + const testInput = [ + { + Message: { + S: 'New item!', + }, + Id: { + N: '101', + }, + }, + { + Message: { + S: 'This item has changed', + }, + // Id is missing + }, + ]; + + const testEvent = getTestEvent({ + eventsPath: '.', + filename: 'dynamoStreamEvent', + }); + + testEvent.Records[0].dynamodb.NewImage = testInput[0]; + testEvent.Records[1].dynamodb.NewImage = testInput[1]; + + // Act + const extendedSchema = DynamoDBStreamSchema.extend({ + Records: z.array( + DynamoDBStreamRecord.extend({ + dynamodb: z.object({ + NewImage: DynamoDBMarshalled(schema).optional(), + }), + }) + ), + }); + + // Assess + expect(() => extendedSchema.parse(testEvent)).toThrow(ZodError); + }); +}); From 66085b89c76312edeb67b21e41f90fd379da205c Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 11:35:51 +0600 Subject: [PATCH 05/10] refactor: reuse `extendedSchema` for `DynamoDBMarshalled` tests --- packages/parser/tests/unit/helpers.test.ts | 49 ++++++---------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/packages/parser/tests/unit/helpers.test.ts b/packages/parser/tests/unit/helpers.test.ts index 852a7b5c4a..08ded994b6 100644 --- a/packages/parser/tests/unit/helpers.test.ts +++ b/packages/parser/tests/unit/helpers.test.ts @@ -168,6 +168,16 @@ describe('DynamoDBMarshalled', () => { Id: z.number(), }); + const extendedSchema = DynamoDBStreamSchema.extend({ + Records: z.array( + DynamoDBStreamRecord.extend({ + dynamodb: z.object({ + NewImage: DynamoDBMarshalled(schema).optional(), + }), + }) + ), + }); + it('should correctly unmarshall and validate a valid DynamoDB stream record', () => { // Prepare const testInput = [ @@ -207,18 +217,7 @@ describe('DynamoDBMarshalled', () => { testEvent.Records[0].dynamodb.NewImage = testInput[0]; testEvent.Records[1].dynamodb.NewImage = testInput[1]; - // Act - const extendedSchema = DynamoDBStreamSchema.extend({ - Records: z.array( - DynamoDBStreamRecord.extend({ - dynamodb: z.object({ - NewImage: DynamoDBMarshalled(schema).optional(), - }), - }) - ), - }); - - // Assess + // Act & Assess expect(extendedSchema.parse(testEvent)).toStrictEqual({ Records: [ { @@ -266,18 +265,7 @@ describe('DynamoDBMarshalled', () => { testEvent.Records[0].dynamodb.NewImage = testInput[0]; testEvent.Records[1].dynamodb.NewImage = testInput[1]; - // Act - const extendedSchema = DynamoDBStreamSchema.extend({ - Records: z.array( - DynamoDBStreamRecord.extend({ - dynamodb: z.object({ - NewImage: DynamoDBMarshalled(schema).optional(), - }), - }) - ), - }); - - // Assess + // Act & Assess expect(() => extendedSchema.parse(testEvent)).toThrow(); }); @@ -308,18 +296,7 @@ describe('DynamoDBMarshalled', () => { testEvent.Records[0].dynamodb.NewImage = testInput[0]; testEvent.Records[1].dynamodb.NewImage = testInput[1]; - // Act - const extendedSchema = DynamoDBStreamSchema.extend({ - Records: z.array( - DynamoDBStreamRecord.extend({ - dynamodb: z.object({ - NewImage: DynamoDBMarshalled(schema).optional(), - }), - }) - ), - }); - - // Assess + // Act & Assess expect(() => extendedSchema.parse(testEvent)).toThrow(ZodError); }); }); From 07d06f6d0a63b56d1810cd57997fecfa03858f18 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 11:57:55 +0600 Subject: [PATCH 06/10] feat: dynamodb helper subpath export --- packages/parser/package.json | 4 ++++ packages/parser/src/helpers.ts | 22 +-------------------- packages/parser/src/helpers/dynamodb.ts | 23 ++++++++++++++++++++++ packages/parser/tests/unit/helpers.test.ts | 5 +++-- 4 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 packages/parser/src/helpers/dynamodb.ts diff --git a/packages/parser/package.json b/packages/parser/package.json index 8e7015f722..81c54866c9 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -180,6 +180,10 @@ "require": "./lib/cjs/helpers.js", "import": "./lib/esm/helpers.js" }, + "./helpers/dynamodb": { + "require": "./lib/cjs/helpers/dynamodb.js", + "import": "./lib/esm/helpers/dynamodb.js" + }, "./types": { "require": "./lib/cjs/types/index.js", "import": "./lib/esm/types/index.js" diff --git a/packages/parser/src/helpers.ts b/packages/parser/src/helpers.ts index dd3c3c8f43..10915620d7 100644 --- a/packages/parser/src/helpers.ts +++ b/packages/parser/src/helpers.ts @@ -1,5 +1,3 @@ -import type { AttributeValue } from '@aws-sdk/client-dynamodb'; -import { unmarshall } from '@aws-sdk/util-dynamodb'; import { type ZodTypeAny, z } from 'zod'; /** * @typedef {import('../schemas/alb').AlbSchema} AlbSchema @@ -58,22 +56,4 @@ const JSONStringified = (schema: T) => }) .pipe(schema); -const DynamoDBMarshalled = (schema: T) => - z - .record(z.string(), z.custom()) - .transform((str, ctx) => { - try { - return unmarshall(str); - } catch (err) { - ctx.addIssue({ - code: 'custom', - message: 'Could not unmarshall DynamoDB stream record', - fatal: true, - }); - - return z.NEVER; - } - }) - .pipe(schema); - -export { JSONStringified, DynamoDBMarshalled }; +export { JSONStringified }; diff --git a/packages/parser/src/helpers/dynamodb.ts b/packages/parser/src/helpers/dynamodb.ts new file mode 100644 index 0000000000..bac068b6df --- /dev/null +++ b/packages/parser/src/helpers/dynamodb.ts @@ -0,0 +1,23 @@ +import type { AttributeValue } from '@aws-sdk/client-dynamodb'; +import { unmarshall } from '@aws-sdk/util-dynamodb'; +import { type ZodTypeAny, z } from 'zod'; + +const DynamoDBMarshalled = (schema: T) => + z + .record(z.string(), z.custom()) + .transform((str, ctx) => { + try { + return unmarshall(str); + } catch (err) { + ctx.addIssue({ + code: 'custom', + message: 'Could not unmarshall DynamoDB stream record', + fatal: true, + }); + + return z.NEVER; + } + }) + .pipe(schema); + +export { DynamoDBMarshalled }; diff --git a/packages/parser/tests/unit/helpers.test.ts b/packages/parser/tests/unit/helpers.test.ts index 08ded994b6..258ac27703 100644 --- a/packages/parser/tests/unit/helpers.test.ts +++ b/packages/parser/tests/unit/helpers.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { ZodError, z } from 'zod'; -import { DynamoDBMarshalled, JSONStringified } from '../../src/helpers.js'; +import { JSONStringified } from '../../src/helpers.js'; +import { DynamoDBMarshalled } from '../../src/helpers/dynamodb.js'; import { AlbSchema } from '../../src/schemas/alb.js'; import { DynamoDBStreamRecord, @@ -244,7 +245,7 @@ describe('DynamoDBMarshalled', () => { S: 'New item!', }, Id: { - NNN: '101', + NNN: '101', //unknown type }, }, { From 4b5f054ab8be66e24ed8663ab0f6a21fa6ddbaf2 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 13:21:52 +0600 Subject: [PATCH 07/10] refactor: error message check --- packages/parser/tests/unit/helpers.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/parser/tests/unit/helpers.test.ts b/packages/parser/tests/unit/helpers.test.ts index 258ac27703..15f03b136c 100644 --- a/packages/parser/tests/unit/helpers.test.ts +++ b/packages/parser/tests/unit/helpers.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { ZodError, z } from 'zod'; +import { z } from 'zod'; import { JSONStringified } from '../../src/helpers.js'; import { DynamoDBMarshalled } from '../../src/helpers/dynamodb.js'; import { AlbSchema } from '../../src/schemas/alb.js'; @@ -267,7 +267,9 @@ describe('DynamoDBMarshalled', () => { testEvent.Records[1].dynamodb.NewImage = testInput[1]; // Act & Assess - expect(() => extendedSchema.parse(testEvent)).toThrow(); + expect(() => extendedSchema.parse(testEvent)).toThrow( + 'Could not unmarshall DynamoDB stream record' + ); }); it('should throw a validation error if the unmarshalled record does not match the schema', () => { @@ -298,6 +300,6 @@ describe('DynamoDBMarshalled', () => { testEvent.Records[1].dynamodb.NewImage = testInput[1]; // Act & Assess - expect(() => extendedSchema.parse(testEvent)).toThrow(ZodError); + expect(() => extendedSchema.parse(testEvent)).toThrow(); }); }); From a1f9b8cca3dbf76e3a9c3af0f704eec87566a496 Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 17:52:09 +0600 Subject: [PATCH 08/10] doc: `DynamoDBMarshalled` function doc description --- packages/parser/src/helpers/dynamodb.ts | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/parser/src/helpers/dynamodb.ts b/packages/parser/src/helpers/dynamodb.ts index bac068b6df..a05d884de9 100644 --- a/packages/parser/src/helpers/dynamodb.ts +++ b/packages/parser/src/helpers/dynamodb.ts @@ -2,6 +2,64 @@ import type { AttributeValue } from '@aws-sdk/client-dynamodb'; import { unmarshall } from '@aws-sdk/util-dynamodb'; import { type ZodTypeAny, z } from 'zod'; +/** + * A helper function to unmarshall DynamoDB stream events and validate them against a schema. + * + * @example + * ```typescript + * const mySchema = z.object({ + * id: z.string(), + * name: z.string(), + * }); + * const eventSchema = DynamoDBStreamSchema.extend({ + * Records: z.array( + * DynamoDBStreamRecord.extend({ + * dynamodb: z.object({ + * NewImage: DynamoDBMarshalled(mySchema).optional(), + * }), + * }) + * ), + * }); + * ``` + * For example, if you have a DynamoDB stream event like the following: + * + * ```json + * { + * "Records": [ + * { + * "dynamodb": { + * "NewImage": { + * "id": { + * "S": "12345" + * }, + * "name": { + * "S": "John Doe" + * } + * } + * } + * } + * ] + * } + * ``` + * Resulting in: + * + * ```json + * { + * "Records": [ + * { + * "dynamodb": { + * "NewImage": { + * "id": "12345", + * "name": "John Doe" + * } + * } + * } + * ] + * } + * ``` + * + * @param schema - The schema to validate the JSON string against + */ const DynamoDBMarshalled = (schema: T) => z .record(z.string(), z.custom()) From 8b3721a7f645b3fd9c53d4bd3300923d0ecd3ffb Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Sun, 22 Dec 2024 18:18:58 +0600 Subject: [PATCH 09/10] doc: `DynamoDBMarshalled` function description in user doc --- docs/utilities/parser.md | 15 +++++ .../parser/extendDynamoDBStreamSchema.ts | 23 +++++++ .../samples/exampleDynamoDBStreamPayload.json | 66 +++++++++++++++++++ packages/parser/src/helpers/dynamodb.ts | 1 + 4 files changed, 105 insertions(+) create mode 100644 examples/snippets/parser/extendDynamoDBStreamSchema.ts create mode 100644 examples/snippets/parser/samples/exampleDynamoDBStreamPayload.json diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index d1c534f9fe..8b6e750cb2 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -151,6 +151,21 @@ If you want to extend a schema and transform a JSON stringified payload to an ob --8<-- "examples/snippets/parser/samples/exampleSqsPayload.json" ``` +### DynamoDB Stream event parsing + +If you want to parse a DynamoDB stream event with unmarshalling, you can use the helper function `DynamoDBMarshalled`: + +=== "DynamoDBStreamSchema with DynamoDBMarshalled" + ```typescript hl_lines="17" + --8<-- "examples/snippets/parser/extendDynamoDBStreamSchema.ts" + ``` + +=== "DynamoDBStream event payload" + + ```json hl_lines="13-20 49-56" + --8<-- "examples/snippets/parser/samples/exampleDynamoDBStreamPayload.json" + ``` + ## Envelopes When trying to parse your payload you might encounter the following situations: diff --git a/examples/snippets/parser/extendDynamoDBStreamSchema.ts b/examples/snippets/parser/extendDynamoDBStreamSchema.ts new file mode 100644 index 0000000000..86639f5cfb --- /dev/null +++ b/examples/snippets/parser/extendDynamoDBStreamSchema.ts @@ -0,0 +1,23 @@ +import { DynamoDBMarshalled } from '@aws-lambda-powertools/parser/helpers/dynamodb'; +import { + DynamoDBStreamRecord, + DynamoDBStreamSchema, +} from '@aws-lambda-powertools/parser/schemas/dynamodb'; +import { z } from 'zod'; + +const customSchema = z.object({ + id: z.string(), + message: z.string(), +}); + +const extendedSchema = DynamoDBStreamSchema.extend({ + Records: z.array( + DynamoDBStreamRecord.extend({ + dynamodb: z.object({ + NewImage: DynamoDBMarshalled(customSchema).optional(), + }), + }) + ), +}); + +type ExtendedDynamoDBStreamEvent = z.infer; diff --git a/examples/snippets/parser/samples/exampleDynamoDBStreamPayload.json b/examples/snippets/parser/samples/exampleDynamoDBStreamPayload.json new file mode 100644 index 0000000000..621c5dc47f --- /dev/null +++ b/examples/snippets/parser/samples/exampleDynamoDBStreamPayload.json @@ -0,0 +1,66 @@ +{ + "Records": [ + { + "eventID": "1", + "eventVersion": "1.0", + "dynamodb": { + "ApproximateCreationDateTime": 1693997155.0, + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "StreamViewType": "NEW_AND_OLD_IMAGES", + "SequenceNumber": "111", + "SizeBytes": 26 + }, + "awsRegion": "us-west-2", + "eventName": "INSERT", + "eventSourceARN": "eventsource_arn", + "eventSource": "aws:dynamodb" + }, + { + "eventID": "2", + "eventVersion": "1.0", + "dynamodb": { + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "SequenceNumber": "222", + "Keys": { + "Id": { + "N": "101" + } + }, + "SizeBytes": 59, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "awsRegion": "us-west-2", + "eventName": "MODIFY", + "eventSourceARN": "source_arn", + "eventSource": "aws:dynamodb" + } + ] + } + \ No newline at end of file diff --git a/packages/parser/src/helpers/dynamodb.ts b/packages/parser/src/helpers/dynamodb.ts index a05d884de9..ca3dfc32c4 100644 --- a/packages/parser/src/helpers/dynamodb.ts +++ b/packages/parser/src/helpers/dynamodb.ts @@ -20,6 +20,7 @@ import { type ZodTypeAny, z } from 'zod'; * }) * ), * }); + * type eventSchema = z.infer; * ``` * For example, if you have a DynamoDB stream event like the following: * From 9597985e6015e18cb28beadacedc70015f08498f Mon Sep 17 00:00:00 2001 From: arnabrahman Date: Tue, 14 Jan 2025 10:04:09 +0600 Subject: [PATCH 10/10] fix: correct helper signature for `unmarshall` function --- packages/parser/src/helpers/dynamodb.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/parser/src/helpers/dynamodb.ts b/packages/parser/src/helpers/dynamodb.ts index ca3dfc32c4..7e1755ca2a 100644 --- a/packages/parser/src/helpers/dynamodb.ts +++ b/packages/parser/src/helpers/dynamodb.ts @@ -63,7 +63,10 @@ import { type ZodTypeAny, z } from 'zod'; */ const DynamoDBMarshalled = (schema: T) => z - .record(z.string(), z.custom()) + .union([ + z.custom(), + z.record(z.string(), z.custom()), + ]) .transform((str, ctx) => { try { return unmarshall(str);