Skip to content

Commit c1bde31

Browse files
authored
fix(sdk): extract customer event from stepData after pagination (#445)
Large payloads (~6MB) were being ignored during synchronous Lambda invocations because the customer event was extracted from event.InitialExecutionState.Operations[0] before pagination completed. When payloads are large, InitialExecutionState.Operations is empty and NextMarker indicates more data needs to be fetched. The fix extracts the customer event from executionContext._stepData after pagination completes in initializeExecutionContext, ensuring full payload access. Fixes large payload truncation issue for durable Lambda functions. *Issue #, if available:* #442
1 parent 1a7e6ac commit c1bde31

File tree

4 files changed

+212
-2
lines changed

4 files changed

+212
-2
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "ce4d4b85-8528-4b42-8198-9d2d0bb96926",
6+
"EventTimestamp": "2026-02-19T21:38:35.633Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"metadata\":{\"size\":1000,\"description\":\"Small test payload\"}}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "StepStarted",
15+
"SubType": "Step",
16+
"EventId": 2,
17+
"Id": "c4ca4238a0b92382",
18+
"Name": "process-large-payload",
19+
"EventTimestamp": "2026-02-19T21:38:35.641Z",
20+
"StepStartedDetails": {}
21+
},
22+
{
23+
"EventType": "StepSucceeded",
24+
"SubType": "Step",
25+
"EventId": 3,
26+
"Id": "c4ca4238a0b92382",
27+
"Name": "process-large-payload",
28+
"EventTimestamp": "2026-02-19T21:38:35.641Z",
29+
"StepSucceededDetails": {
30+
"Result": {
31+
"Payload": "{\"originalSize\":1000,\"first100Chars\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"hasMetadata\":true,\"processedAt\":\"2026-02-19T21:38:35.637Z\"}"
32+
},
33+
"RetryDetails": {}
34+
}
35+
},
36+
{
37+
"EventType": "InvocationCompleted",
38+
"EventId": 4,
39+
"EventTimestamp": "2026-02-19T21:38:35.641Z",
40+
"InvocationCompletedDetails": {
41+
"StartTimestamp": "2026-02-19T21:38:35.633Z",
42+
"EndTimestamp": "2026-02-19T21:38:35.641Z",
43+
"Error": {},
44+
"RequestId": "4f9f89ec-cc03-4d56-bf9d-8107fa2a7468"
45+
}
46+
},
47+
{
48+
"EventType": "ExecutionSucceeded",
49+
"EventId": 5,
50+
"Id": "ce4d4b85-8528-4b42-8198-9d2d0bb96926",
51+
"EventTimestamp": "2026-02-19T21:38:35.642Z",
52+
"ExecutionSucceededDetails": {
53+
"Result": {
54+
"Payload": "{\"success\":true,\"payload\":{\"originalSize\":1000,\"first100Chars\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"hasMetadata\":true,\"processedAt\":\"2026-02-19T21:38:35.637Z\"}}"
55+
}
56+
}
57+
}
58+
]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { handler } from "./large-payload";
2+
import { createTests } from "../../utils/test-helper";
3+
4+
createTests({
5+
handler,
6+
tests: (runner, { assertEventSignatures }) => {
7+
it("should handle small payload correctly", async () => {
8+
const smallPayload = {
9+
data: "A".repeat(1000), // 1KB
10+
metadata: {
11+
size: 1000,
12+
description: "Small test payload",
13+
},
14+
};
15+
16+
const execution = await runner.run({ payload: smallPayload });
17+
18+
expect(execution.getResult()).toEqual({
19+
success: true,
20+
payload: {
21+
originalSize: 1000,
22+
first100Chars: "A".repeat(100),
23+
hasMetadata: true,
24+
processedAt: expect.any(String),
25+
},
26+
});
27+
28+
expect(execution.getOperations()).toHaveLength(1);
29+
const step = runner.getOperation("process-large-payload");
30+
expect(step.getStepDetails()?.result).toEqual({
31+
originalSize: 1000,
32+
first100Chars: "A".repeat(100),
33+
hasMetadata: true,
34+
processedAt: expect.any(String),
35+
});
36+
37+
// Call assertEventSignatures only once to satisfy the test framework requirement
38+
assertEventSignatures(execution);
39+
});
40+
41+
it("should handle medium payload correctly", async () => {
42+
const mediumPayload = {
43+
data: "B".repeat(1000000), // 1MB
44+
metadata: {
45+
size: 1000000,
46+
description: "Medium test payload",
47+
},
48+
};
49+
50+
const execution = await runner.run({ payload: mediumPayload });
51+
52+
expect(execution.getResult()).toEqual({
53+
success: true,
54+
payload: {
55+
originalSize: 1000000,
56+
first100Chars: "B".repeat(100),
57+
hasMetadata: true,
58+
processedAt: expect.any(String),
59+
},
60+
});
61+
62+
expect(execution.getOperations()).toHaveLength(1);
63+
// Skip event signature validation for this test
64+
});
65+
66+
it("should handle large payload correctly", async () => {
67+
const largePayload = {
68+
data: "C".repeat(6000000), // 6MB
69+
metadata: {
70+
size: 6000000,
71+
description: "Large test payload that triggers pagination",
72+
},
73+
};
74+
75+
const execution = await runner.run({ payload: largePayload });
76+
77+
expect(execution.getResult()).toEqual({
78+
success: true,
79+
payload: {
80+
originalSize: 6000000,
81+
first100Chars: "C".repeat(100),
82+
hasMetadata: true,
83+
processedAt: expect.any(String),
84+
},
85+
});
86+
87+
expect(execution.getOperations()).toHaveLength(1);
88+
const step = runner.getOperation("process-large-payload");
89+
const stepResult = step.getStepDetails()?.result as any;
90+
expect(stepResult.originalSize).toBe(6000000);
91+
// Skip event signature validation for this test
92+
});
93+
94+
it("should handle payload without metadata", async () => {
95+
const payloadWithoutMetadata = {
96+
data: "D".repeat(5000), // 5KB
97+
};
98+
99+
const execution = await runner.run({ payload: payloadWithoutMetadata });
100+
101+
expect(execution.getResult()).toEqual({
102+
success: true,
103+
payload: {
104+
originalSize: 5000,
105+
first100Chars: "D".repeat(100),
106+
hasMetadata: false,
107+
processedAt: expect.any(String),
108+
},
109+
});
110+
// Skip event signature validation for this test
111+
});
112+
},
113+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
DurableContext,
3+
withDurableExecution,
4+
} from "@aws/durable-execution-sdk-js";
5+
import { ExampleConfig } from "../../types";
6+
7+
export const config: ExampleConfig = {
8+
name: "Large Payload",
9+
description:
10+
"Demonstrates handling of large payloads (>6MB) in durable functions",
11+
};
12+
13+
interface LargePayloadEvent {
14+
data: string;
15+
metadata?: {
16+
size: number;
17+
description: string;
18+
};
19+
}
20+
21+
export const handler = withDurableExecution(
22+
async (event: LargePayloadEvent, context: DurableContext) => {
23+
const result = await context.step("process-large-payload", async () => {
24+
return {
25+
originalSize: event.data.length,
26+
first100Chars: event.data.substring(0, 100),
27+
hasMetadata: !!event.metadata,
28+
processedAt: new Date().toISOString(),
29+
};
30+
});
31+
32+
return {
33+
success: true,
34+
payload: result,
35+
};
36+
},
37+
);

packages/aws-durable-execution-sdk-js/src/with-durable-execution.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ async function runHandler<
7575
durableExecution,
7676
);
7777

78-
// Extract customerHandlerEvent from the original event
79-
const initialExecutionEvent = event.InitialExecutionState.Operations?.[0];
78+
// Extract customerHandlerEvent from the complete operations array (after pagination)
79+
// This ensures we get the full payload even for large payloads that are paginated
80+
const initialExecutionEvent =
81+
executionContext._stepData[Object.keys(executionContext._stepData)[0]];
8082
const customerHandlerEvent = JSON.parse(
8183
initialExecutionEvent?.ExecutionDetails?.InputPayload ?? "{}",
8284
);

0 commit comments

Comments
 (0)