Skip to content

Commit b1e16f9

Browse files
authored
feat(sdk): add PromiseCombinatorError for promise combinators (#436)
Promise combinators (all, allSettled, any, race) now throw PromiseCombinatorError instead of the generic ChildContextError when failures occur. This provides more specific error types for better error handling and debugging. Changes: - Add PromiseCombinatorError class extending DurableOperationError - Add errorClass parameter to ChildConfig interface - Update runInChildContext to use configurable error class - Pass PromiseCombinatorError to all promise combinator operations - Update unit and integration tests to expect new error type *Issue #, if available:* #433
1 parent 1afc771 commit b1e16f9

File tree

6 files changed

+47
-8
lines changed

6 files changed

+47
-8
lines changed

packages/aws-durable-execution-sdk-js-examples/src/examples/promise/any/promise-any.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ createTests<string>({
2929

3030
expect(execution.getError()).toEqual({
3131
errorMessage: "All promises were rejected",
32-
errorType: "ChildContextError",
32+
errorType: "PromiseCombinatorError",
3333
errorData: undefined,
3434
stackTrace: undefined,
3535
});

packages/aws-durable-execution-sdk-js/src/errors/durable-error/durable-error.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ export class ChildContextError extends DurableOperationError {
135135
}
136136
}
137137

138+
/**
139+
* Error thrown when a promise combinator operation fails
140+
* @public
141+
*/
142+
export class PromiseCombinatorError extends DurableOperationError {
143+
readonly errorType = "PromiseCombinatorError";
144+
145+
constructor(message?: string, cause?: Error, errorData?: string) {
146+
super(message || "Promise combinator failed", cause, errorData);
147+
}
148+
}
149+
138150
/**
139151
* Error thrown when a wait for condition operation fails
140152
* @public

packages/aws-durable-execution-sdk-js/src/handlers/promise-handler/promise-handler.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ describe("Promise Handler", () => {
209209
expect(mockRunInChildContext).toHaveBeenCalledWith(
210210
undefined,
211211
expect.any(Function),
212+
expect.objectContaining({ errorClass: expect.any(Function) }),
212213
);
213214
expect(result).toEqual([1, 2]);
214215
});
@@ -225,6 +226,7 @@ describe("Promise Handler", () => {
225226
expect(mockRunInChildContext).toHaveBeenCalledWith(
226227
"test-all",
227228
expect.any(Function),
229+
expect.objectContaining({ errorClass: expect.any(Function) }),
228230
);
229231
expect(result).toEqual([1, 2]);
230232
});
@@ -259,6 +261,7 @@ describe("Promise Handler", () => {
259261
expect.any(Function),
260262
expect.objectContaining({
261263
serdes: expect.any(Object),
264+
errorClass: expect.any(Function),
262265
}),
263266
);
264267
});
@@ -278,7 +281,10 @@ describe("Promise Handler", () => {
278281
expect(mockRunInChildContext).toHaveBeenCalledWith(
279282
"test-allSettled",
280283
expect.any(Function),
281-
expect.any(Object),
284+
expect.objectContaining({
285+
serdes: expect.any(Object),
286+
errorClass: expect.any(Function),
287+
}),
282288
);
283289
expect(result).toHaveLength(2);
284290
expect(result[0]).toEqual({ status: "fulfilled", value: 1 });
@@ -316,6 +322,7 @@ describe("Promise Handler", () => {
316322
expect(mockRunInChildContext).toHaveBeenCalledWith(
317323
undefined,
318324
expect.any(Function),
325+
expect.objectContaining({ errorClass: expect.any(Function) }),
319326
);
320327
expect(result).toBe(1); // Promise.any returns the first resolved value
321328
});
@@ -332,6 +339,7 @@ describe("Promise Handler", () => {
332339
expect(mockRunInChildContext).toHaveBeenCalledWith(
333340
"test-any",
334341
expect.any(Function),
342+
expect.objectContaining({ errorClass: expect.any(Function) }),
335343
);
336344
expect(result).toBe(1); // Promise.any returns the first resolved value
337345
});
@@ -373,6 +381,7 @@ describe("Promise Handler", () => {
373381
expect(mockRunInChildContext).toHaveBeenCalledWith(
374382
undefined,
375383
expect.any(Function),
384+
expect.objectContaining({ errorClass: expect.any(Function) }),
376385
);
377386
expect(result).toBe(1); // Promise.race returns the first resolved value
378387
});
@@ -389,6 +398,7 @@ describe("Promise Handler", () => {
389398
expect(mockRunInChildContext).toHaveBeenCalledWith(
390399
"test-race",
391400
expect.any(Function),
401+
expect.objectContaining({ errorClass: expect.any(Function) }),
392402
);
393403
expect(result).toBe(1); // Promise.race returns the first resolved value
394404
});

packages/aws-durable-execution-sdk-js/src/handlers/promise-handler/promise-handler.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DurableContext, DurablePromise, DurableLogger } from "../../types";
22
import { Serdes, SerdesContext } from "../../utils/serdes/serdes";
3+
import { PromiseCombinatorError } from "../../errors/durable-error/durable-error";
34

45
// Minimal error decoration for Promise.allSettled results
56
function decorateErrors<T>(
@@ -100,7 +101,9 @@ export const createPromiseHandler = <Logger extends DurableLogger>(
100101
const { name, promises } = parseParams(nameOrPromises, maybePromises);
101102

102103
// Wrap Promise.all execution in a child context for persistence
103-
return await runInChildContext(name, () => Promise.all(promises));
104+
return await runInChildContext(name, () => Promise.all(promises), {
105+
errorClass: PromiseCombinatorError,
106+
});
104107
});
105108
};
106109

@@ -114,6 +117,7 @@ export const createPromiseHandler = <Logger extends DurableLogger>(
114117
// Wrap Promise.allSettled execution in a child context for persistence
115118
return await runInChildContext(name, () => Promise.allSettled(promises), {
116119
serdes: createErrorAwareSerdes<T>(),
120+
errorClass: PromiseCombinatorError,
117121
});
118122
});
119123
};
@@ -126,7 +130,9 @@ export const createPromiseHandler = <Logger extends DurableLogger>(
126130
const { name, promises } = parseParams(nameOrPromises, maybePromises);
127131

128132
// Wrap Promise.any execution in a child context for persistence
129-
return await runInChildContext(name, () => Promise.any(promises));
133+
return await runInChildContext(name, () => Promise.any(promises), {
134+
errorClass: PromiseCombinatorError,
135+
});
130136
});
131137
};
132138

@@ -138,7 +144,9 @@ export const createPromiseHandler = <Logger extends DurableLogger>(
138144
const { name, promises } = parseParams(nameOrPromises, maybePromises);
139145

140146
// Wrap Promise.race execution in a child context for persistence
141-
return await runInChildContext(name, () => Promise.race(promises));
147+
return await runInChildContext(name, () => Promise.race(promises), {
148+
errorClass: PromiseCombinatorError,
149+
});
142150
});
143151
};
144152

packages/aws-durable-execution-sdk-js/src/handlers/run-in-child-context-handler/run-in-child-context-handler.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export const handleCompletedChildContext = async <
199199
) => DurableContext<Logger>,
200200
): Promise<T> => {
201201
const serdes = options?.serdes || defaultSerdes;
202+
const ErrorClass = options?.errorClass || ChildContextError;
202203
const stepData = context.getStepData(entityId);
203204
const result = stepData?.ContextDetails?.Result;
204205

@@ -208,9 +209,9 @@ export const handleCompletedChildContext = async <
208209
const originalError = DurableOperationError.fromErrorObject(
209210
stepData.ContextDetails.Error,
210211
);
211-
throw new ChildContextError(originalError.message, originalError);
212+
throw new ErrorClass(originalError.message, originalError);
212213
} else {
213-
throw new ChildContextError("Child context failed");
214+
throw new ErrorClass("Child context failed");
214215
}
215216
}
216217

@@ -273,6 +274,7 @@ export const executeChildContext = async <T, Logger extends DurableLogger>(
273274
parentId?: string,
274275
): Promise<T> => {
275276
const serdes = options?.serdes || defaultSerdes;
277+
const ErrorClass = options?.errorClass || ChildContextError;
276278

277279
// Checkpoint at start if not already started (fire-and-forget for performance)
278280
if (context.getStepData(entityId) === undefined) {
@@ -391,6 +393,6 @@ export const executeChildContext = async <T, Logger extends DurableLogger>(
391393
const errorObject = createErrorObjectFromError(error);
392394
const reconstructedError =
393395
DurableOperationError.fromErrorObject(errorObject);
394-
throw new ChildContextError(reconstructedError.message, reconstructedError);
396+
throw new ErrorClass(reconstructedError.message, reconstructedError);
395397
}
396398
};

packages/aws-durable-execution-sdk-js/src/types/child-context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Serdes } from "../utils/serdes/serdes";
22
import { DurableLogger } from "./durable-logger";
33
import { DurableContext } from "./durable-context";
4+
import { DurableOperationError } from "../errors/durable-error/durable-error";
45

56
/**
67
* Configuration options for child context operations
@@ -13,6 +14,12 @@ export interface ChildConfig<T> {
1314
subType?: string;
1415
/** Function to generate summaries for large results (used internally by map/parallel) */
1516
summaryGenerator?: (result: T) => string;
17+
/** Custom error class to throw when child context fails */
18+
errorClass?: new (
19+
message?: string,
20+
cause?: Error,
21+
errorData?: string,
22+
) => DurableOperationError;
1623
}
1724

1825
/**

0 commit comments

Comments
 (0)