Skip to content

Commit 266b070

Browse files
authored
feat(sdk): add specific error types for callback operations (#441)
Add CallbackTimeoutError and CallbackSubmitterError to provide clear, unambiguous error handling for callback operations. Replace errorClass with more flexible errorMapper in ChildConfig. Changes: - Add CallbackTimeoutError for callback timeout scenarios - Add CallbackSubmitterError for submitter failures in waitForCallback - Add errorMapper to ChildConfig for flexible error type mapping - Remove errorClass from ChildConfig (replaced by errorMapper) - Update callback.ts to throw CallbackTimeoutError for TIMED_OUT status - Update waitForCallback to use errorMapper for consistent error types - Update all promise combinators to use errorMapper - Update fromErrorObject to reconstruct new error types - Export CallbackTimeoutError and CallbackSubmitterError - Update all tests to expect correct error types Error types by operation: - createCallback timeout -> CallbackTimeoutError - createCallback failure -> CallbackError - waitForCallback timeout -> CallbackTimeoutError - waitForCallback failure -> CallbackError - waitForCallback submitter failure -> CallbackSubmitterError Benefits: - Users can catch specific error types without parsing error messages - Consistent error handling across createCallback and waitForCallback - More flexible error customization via errorMapper - Type-safe error handling in TypeScript *Issue #, if available:* #420
1 parent c1bde31 commit 266b070

26 files changed

+1188
-170
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "d0ad0e67-4c41-444a-addd-a60bc9bc29f4",
6+
"EventTimestamp": "2026-02-12T21:58:02.738Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "CallbackStarted",
15+
"SubType": "Callback",
16+
"EventId": 2,
17+
"Id": "c4ca4238a0b92382",
18+
"Name": "timeout-test",
19+
"EventTimestamp": "2026-02-12T21:58:02.743Z",
20+
"CallbackStartedDetails": {
21+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjVjOTRiMmM0LTg1MjEtNDY5OC04ZTkzLWIzNWFhYTE4ZGQ0OCIsIm9wZXJhdGlvbklkIjoiYzRjYTQyMzhhMGI5MjM4MiIsInRva2VuIjoiMjY3NTVkM2YtY2FkOS00NmUzLWExMmQtMTJmNWI0YmY0N2VmIn0=",
22+
"Timeout": 1,
23+
"Input": {}
24+
}
25+
},
26+
{
27+
"EventType": "InvocationCompleted",
28+
"EventId": 3,
29+
"EventTimestamp": "2026-02-12T21:58:02.765Z",
30+
"InvocationCompletedDetails": {
31+
"StartTimestamp": "2026-02-12T21:58:02.738Z",
32+
"EndTimestamp": "2026-02-12T21:58:02.765Z",
33+
"Error": {},
34+
"RequestId": "f818180c-eb9b-4023-a7b1-081463b48b54"
35+
}
36+
},
37+
{
38+
"EventType": "CallbackTimedOut",
39+
"SubType": "Callback",
40+
"EventId": 4,
41+
"Id": "c4ca4238a0b92382",
42+
"Name": "timeout-test",
43+
"EventTimestamp": "2026-02-12T21:58:03.744Z",
44+
"CallbackTimedOutDetails": {
45+
"Error": {
46+
"Payload": {
47+
"ErrorMessage": "Callback timed out"
48+
}
49+
}
50+
}
51+
},
52+
{
53+
"EventType": "CallbackStarted",
54+
"SubType": "Callback",
55+
"EventId": 5,
56+
"Id": "c81e728d9d4c2f63",
57+
"Name": "failure-test",
58+
"EventTimestamp": "2026-02-12T21:58:03.747Z",
59+
"CallbackStartedDetails": {
60+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjVjOTRiMmM0LTg1MjEtNDY5OC04ZTkzLWIzNWFhYTE4ZGQ0OCIsIm9wZXJhdGlvbklkIjoiYzgxZTcyOGQ5ZDRjMmY2MyIsInRva2VuIjoiMDU2MGYzY2EtNmVlZi00MzlhLWFjMTAtOTgzNjkzOTI2NGY0In0=",
61+
"Timeout": 10,
62+
"Input": {}
63+
}
64+
},
65+
{
66+
"EventType": "CallbackFailed",
67+
"SubType": "Callback",
68+
"EventId": 6,
69+
"Id": "c81e728d9d4c2f63",
70+
"Name": "failure-test",
71+
"EventTimestamp": "2026-02-12T21:58:03.748Z",
72+
"CallbackFailedDetails": {
73+
"Error": {
74+
"Payload": {
75+
"ErrorMessage": "Callback failed"
76+
}
77+
}
78+
}
79+
},
80+
{
81+
"EventType": "InvocationCompleted",
82+
"EventId": 7,
83+
"EventTimestamp": "2026-02-12T21:58:03.769Z",
84+
"InvocationCompletedDetails": {
85+
"StartTimestamp": "2026-02-12T21:58:03.745Z",
86+
"EndTimestamp": "2026-02-12T21:58:03.769Z",
87+
"Error": {},
88+
"RequestId": "c0f805de-a98c-4971-b585-39d22c6c0293"
89+
}
90+
},
91+
{
92+
"EventType": "WaitStarted",
93+
"SubType": "Wait",
94+
"EventId": 8,
95+
"Id": "eccbc87e4b5ce2fe",
96+
"EventTimestamp": "2026-02-12T21:58:03.774Z",
97+
"WaitStartedDetails": {
98+
"Duration": 1,
99+
"ScheduledEndTimestamp": "2026-02-12T21:58:04.774Z"
100+
}
101+
},
102+
{
103+
"EventType": "InvocationCompleted",
104+
"EventId": 9,
105+
"EventTimestamp": "2026-02-12T21:58:03.796Z",
106+
"InvocationCompletedDetails": {
107+
"StartTimestamp": "2026-02-12T21:58:03.770Z",
108+
"EndTimestamp": "2026-02-12T21:58:03.796Z",
109+
"Error": {},
110+
"RequestId": "c65fd8c3-c05d-4124-814a-c5303ed6a9c8"
111+
}
112+
},
113+
{
114+
"EventType": "WaitSucceeded",
115+
"SubType": "Wait",
116+
"EventId": 10,
117+
"Id": "eccbc87e4b5ce2fe",
118+
"EventTimestamp": "2026-02-12T21:58:04.773Z",
119+
"WaitSucceededDetails": {
120+
"Duration": 1
121+
}
122+
},
123+
{
124+
"EventType": "StepStarted",
125+
"SubType": "Step",
126+
"EventId": 11,
127+
"Id": "a87ff679a2f3e71d",
128+
"Name": "check-error-types",
129+
"EventTimestamp": "2026-02-12T21:58:04.776Z",
130+
"StepStartedDetails": {}
131+
},
132+
{
133+
"EventType": "StepSucceeded",
134+
"SubType": "Step",
135+
"EventId": 12,
136+
"Id": "a87ff679a2f3e71d",
137+
"Name": "check-error-types",
138+
"EventTimestamp": "2026-02-12T21:58:04.776Z",
139+
"StepSucceededDetails": {
140+
"Result": {
141+
"Payload": "{\"timeoutError\":{\"isCallbackTimeoutError\":true,\"errorName\":\"CallbackTimeoutError\",\"errorMessage\":\"Callback timed out\"},\"failureError\":{\"isCallbackError\":true,\"errorName\":\"CallbackError\",\"errorMessage\":\"Callback failed\"}}"
142+
},
143+
"RetryDetails": {}
144+
}
145+
},
146+
{
147+
"EventType": "InvocationCompleted",
148+
"EventId": 13,
149+
"EventTimestamp": "2026-02-12T21:58:04.777Z",
150+
"InvocationCompletedDetails": {
151+
"StartTimestamp": "2026-02-12T21:58:04.774Z",
152+
"EndTimestamp": "2026-02-12T21:58:04.777Z",
153+
"Error": {},
154+
"RequestId": "6158a992-8105-4be6-a0d1-3903db62bd5e"
155+
}
156+
},
157+
{
158+
"EventType": "ExecutionSucceeded",
159+
"EventId": 14,
160+
"Id": "d0ad0e67-4c41-444a-addd-a60bc9bc29f4",
161+
"EventTimestamp": "2026-02-12T21:58:04.777Z",
162+
"ExecutionSucceededDetails": {
163+
"Result": {
164+
"Payload": "{\"timeoutError\":{\"isCallbackTimeoutError\":true,\"errorName\":\"CallbackTimeoutError\",\"errorMessage\":\"Callback timed out\"},\"failureError\":{\"isCallbackError\":true,\"errorName\":\"CallbackError\",\"errorMessage\":\"Callback failed\"}}"
165+
}
166+
}
167+
}
168+
]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { InvocationType } from "@aws/durable-execution-sdk-js-testing";
2+
import { handler } from "./error-instance";
3+
import { createTests } from "../../../utils/test-helper";
4+
5+
createTests({
6+
handler,
7+
invocationType: InvocationType.Event,
8+
tests: (runner, { assertEventSignatures }) => {
9+
it("should catch correct error instances for timeout and failure", async () => {
10+
const callbackOp2 = runner.getOperation("failure-test");
11+
12+
const executionPromise = runner.run({ payload: {} });
13+
14+
// Wait for second callback to start
15+
await callbackOp2.waitForData();
16+
17+
// Send failure to second callback
18+
await callbackOp2.sendCallbackFailure({
19+
ErrorMessage: "Callback failed",
20+
});
21+
22+
const result = await executionPromise;
23+
const errorCheck = result.getResult();
24+
25+
expect(errorCheck).toEqual({
26+
timeoutError: {
27+
isCallbackTimeoutError: true,
28+
errorName: "CallbackTimeoutError",
29+
errorMessage: "Callback timed out",
30+
},
31+
failureError: {
32+
isCallbackError: true,
33+
errorName: "CallbackError",
34+
errorMessage: "Callback failed",
35+
},
36+
});
37+
38+
assertEventSignatures(result);
39+
});
40+
},
41+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
CallbackError,
3+
CallbackTimeoutError,
4+
DurableContext,
5+
withDurableExecution,
6+
} from "@aws/durable-execution-sdk-js";
7+
import { ExampleConfig } from "../../../types";
8+
9+
export const config: ExampleConfig = {
10+
name: "Create Callback - Error Instance",
11+
description:
12+
"Verifies callback errors throw correct error instances (timeout, failure)",
13+
};
14+
15+
export const handler = withDurableExecution(
16+
async (_event: unknown, context: DurableContext) => {
17+
const errors: Array<Error | null> = [];
18+
19+
// Test 1: Callback timeout
20+
try {
21+
const [callbackPromise] = await context.createCallback("timeout-test", {
22+
timeout: { seconds: 1 },
23+
});
24+
await callbackPromise;
25+
} catch (error) {
26+
errors.push(error as Error);
27+
}
28+
29+
// Test 2: Callback failure
30+
try {
31+
const [callbackPromise] = await context.createCallback("failure-test", {
32+
timeout: { seconds: 10 },
33+
});
34+
await callbackPromise;
35+
} catch (error) {
36+
errors.push(error as Error);
37+
}
38+
39+
await context.wait({ seconds: 1 });
40+
41+
const errorTypes = await context.step("check-error-types", async () => {
42+
return {
43+
timeoutError: {
44+
isCallbackTimeoutError: errors[0] instanceof CallbackTimeoutError,
45+
errorName: errors[0]?.constructor.name,
46+
errorMessage: errors[0]?.message,
47+
},
48+
failureError: {
49+
isCallbackError: errors[1] instanceof CallbackError,
50+
errorName: errors[1]?.constructor.name,
51+
errorMessage: errors[1]?.message,
52+
},
53+
};
54+
});
55+
56+
return errorTypes;
57+
},
58+
);

packages/aws-durable-execution-sdk-js-examples/src/examples/create-callback/timeout/create-callback-timeout.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ createTests({
1212
expect(result.getError()).toEqual({
1313
errorData: undefined,
1414
errorMessage: "Callback timed out on heartbeat",
15-
errorType: "CallbackError",
15+
errorType: "CallbackTimeoutError",
1616
stackTrace: undefined,
1717
});
1818

@@ -27,7 +27,7 @@ createTests({
2727
expect(result.getError()).toEqual({
2828
errorData: undefined,
2929
errorMessage: "Callback timed out",
30-
errorType: "CallbackError",
30+
errorType: "CallbackTimeoutError",
3131
stackTrace: undefined,
3232
});
3333

0 commit comments

Comments
 (0)