Skip to content

Commit aadd7f5

Browse files
authored
feat(sdk): restrict promise combinators to accept DurablePromise (#308)
- Updated promise handler to only accept DurablePromise<T>[] instead of Promise<T>[] - Removed unnecessary conversion from DurablePromise to Promise since native combinators work directly - Updated DurableContext type definitions to enforce DurablePromise constraint - Updated all tests to use DurablePromise objects - Added explicit type constraint test to verify compile-time enforcement
1 parent 1453064 commit aadd7f5

File tree

6 files changed

+146
-138
lines changed

6 files changed

+146
-138
lines changed

packages/aws-durable-execution-sdk-js/src/handlers/concurrent-execution-handler/concurrent-execution-handler-two-phase.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe("Concurrent Execution Handler Two-Phase Execution", () => {
2727
// Mock runInChildContext to track when execution starts
2828
mockRunInChildContext = jest
2929
.fn()
30-
.mockImplementation(async (name, fn, config) => {
30+
.mockImplementation(async (name, fn, _config) => {
3131
executionStarted = true;
3232
// Create a mock child context with runInChildContext method
3333
const mockChildContext = {

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DurableContext,
55
DurableExecutionMode,
66
ExecutionContext,
7+
DurablePromise,
78
} from "../../types";
89
import { OperationStatus, OperationType } from "@aws-sdk/client-lambda";
910

@@ -67,9 +68,11 @@ describe("ConcurrencyController - Replay Mode", () => {
6768
});
6869

6970
mockParentContext.runInChildContext.mockImplementation(
70-
async (nameOrFn, fnOrConfig) => {
71+
(nameOrFn, fnOrConfig) => {
7172
const fn = typeof nameOrFn === "function" ? nameOrFn : fnOrConfig;
72-
return await (fn as any)({} as any);
73+
return new DurablePromise(async () => {
74+
return await (fn as any)({} as any);
75+
});
7376
},
7477
);
7578

@@ -137,13 +140,15 @@ describe("ConcurrencyController - Replay Mode", () => {
137140
});
138141

139142
mockParentContext.runInChildContext.mockImplementation(
140-
async (nameOrFn, fnOrConfig) => {
143+
(nameOrFn, fnOrConfig) => {
141144
const name = typeof nameOrFn === "string" ? nameOrFn : undefined;
142145
const fn = typeof nameOrFn === "function" ? nameOrFn : fnOrConfig;
143-
if (name === "item-1") {
144-
throw new Error("Replay error");
145-
}
146-
return await (fn as any)({} as any);
146+
return new DurablePromise(async () => {
147+
if (name === "item-1") {
148+
throw new Error("Replay error");
149+
}
150+
return await (fn as any)({} as any);
151+
});
147152
},
148153
);
149154

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

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DurableContext,
88
BatchItemStatus,
99
DurableLogger,
10+
DurablePromise,
1011
} from "../../types";
1112
import { MockBatchResult } from "../../testing/mock-batch-result";
1213
import { ChildContextError } from "../../errors/durable-error/durable-error";
@@ -329,11 +330,11 @@ describe("Concurrent Execution Handler", () => {
329330
} else {
330331
capturedExecuteOperation = nameOrFn;
331332
}
332-
return Promise.resolve(
333+
return new DurablePromise(() => Promise.resolve(
333334
new MockBatchResult([
334335
{ index: 0, result: "result", status: BatchItemStatus.SUCCEEDED },
335336
]) as any,
336-
);
337+
));
337338
},
338339
);
339340

@@ -352,7 +353,7 @@ describe("Concurrent Execution Handler", () => {
352353
// Create a real execution context that will execute the actual path
353354
let actualExecuteOperation: any;
354355
mockRunInChildContext.mockImplementation(
355-
async (nameOrFn: any, fnOrConfig?: any, _maybeConfig?: any) => {
356+
(nameOrFn: any, fnOrConfig?: any, _maybeConfig?: any) => {
356357
// Handle the overloaded signature
357358
let actualFn;
358359
if (typeof nameOrFn === "string" || nameOrFn === undefined) {
@@ -361,15 +362,17 @@ describe("Concurrent Execution Handler", () => {
361362
actualFn = nameOrFn;
362363
}
363364
actualExecuteOperation = actualFn;
364-
// Execute the actual operation to cover the executeOperation function
365-
if (typeof actualFn === "function") {
366-
const mockDurableContext = {
367-
runInChildContext: jest.fn().mockResolvedValue("test-result"),
368-
} as any;
369-
370-
return await actualFn(mockDurableContext);
371-
}
372-
return new MockBatchResult([]) as any;
365+
return new DurablePromise(async () => {
366+
// Execute the actual operation to cover the executeOperation function
367+
if (typeof actualFn === "function") {
368+
const mockDurableContext = {
369+
runInChildContext: jest.fn().mockResolvedValue("test-result"),
370+
} as any;
371+
372+
return await actualFn(mockDurableContext);
373+
}
374+
return new MockBatchResult([]) as any;
375+
});
373376
},
374377
);
375378

@@ -386,7 +389,7 @@ describe("Concurrent Execution Handler", () => {
386389
// Test with undefined config to cover the config || {} branch
387390
let actualExecuteOperation: any;
388391
mockRunInChildContext.mockImplementation(
389-
async (nameOrFn: any, fnOrConfig?: any, _maybeConfig?: any) => {
392+
(nameOrFn: any, fnOrConfig?: any, _maybeConfig?: any) => {
390393
// Handle the overloaded signature
391394
let actualFn;
392395
if (typeof nameOrFn === "string" || nameOrFn === undefined) {
@@ -395,14 +398,16 @@ describe("Concurrent Execution Handler", () => {
395398
actualFn = nameOrFn;
396399
}
397400
actualExecuteOperation = actualFn;
398-
if (typeof actualFn === "function") {
399-
const mockDurableContext = {
400-
runInChildContext: jest.fn().mockResolvedValue("test-result"),
401-
} as any;
402-
403-
return await actualFn(mockDurableContext);
404-
}
405-
return new MockBatchResult([]) as any;
401+
return new DurablePromise(async () => {
402+
if (typeof actualFn === "function") {
403+
const mockDurableContext = {
404+
runInChildContext: jest.fn().mockResolvedValue("test-result"),
405+
} as any;
406+
407+
return await actualFn(mockDurableContext);
408+
}
409+
return new MockBatchResult([]) as any;
410+
});
406411
},
407412
);
408413

@@ -494,12 +499,12 @@ describe("ConcurrencyController", () => {
494499
mockParentContext.runInChildContext.mockImplementation(() => {
495500
activeCount++;
496501
maxActive = Math.max(maxActive, activeCount);
497-
return new Promise((resolve) => {
502+
return new DurablePromise(() => new Promise((resolve) => {
498503
setTimeout(() => {
499504
activeCount--;
500505
resolve("result");
501506
}, 10);
502-
});
507+
}));
503508
});
504509

505510
await controller.executeItems(items, executor, mockParentContext, {
@@ -519,7 +524,7 @@ describe("ConcurrencyController", () => {
519524

520525
mockParentContext.runInChildContext
521526
.mockResolvedValueOnce("result1")
522-
.mockImplementation(() => new Promise(() => {})); // Never resolves
527+
.mockImplementation(() => new DurablePromise(() => new Promise(() => {}))); // Never resolves
523528

524529
const result = await controller.executeItems(
525530
items,
@@ -546,7 +551,7 @@ describe("ConcurrencyController", () => {
546551
mockParentContext.runInChildContext
547552
.mockRejectedValueOnce(new Error("error1"))
548553
.mockRejectedValueOnce(new Error("error2"))
549-
.mockImplementation(() => new Promise(() => {}));
554+
.mockImplementation(() => new DurablePromise(() => new Promise(() => {})));
550555

551556
const result = await controller.executeItems(
552557
items,
@@ -573,7 +578,7 @@ describe("ConcurrencyController", () => {
573578
mockParentContext.runInChildContext
574579
.mockRejectedValueOnce(new Error("error1"))
575580
.mockRejectedValueOnce(new Error("error2"))
576-
.mockImplementation(() => new Promise(() => {}));
581+
.mockImplementation(() => new DurablePromise(() => new Promise(() => {})));
577582

578583
const result = await controller.executeItems(
579584
items,
@@ -616,7 +621,7 @@ describe("ConcurrencyController", () => {
616621

617622
mockParentContext.runInChildContext
618623
.mockResolvedValueOnce("result1")
619-
.mockImplementation(() => new Promise(() => {})); // Never resolves
624+
.mockImplementation(() => new DurablePromise(() => new Promise(() => {}))); // Never resolves
620625

621626
const result = await controller.executeItems(
622627
items,
@@ -749,9 +754,9 @@ describe("ConcurrencyController", () => {
749754
// Resolve in reverse order
750755
let resolvers: Array<(value: any) => void> = [];
751756
mockParentContext.runInChildContext.mockImplementation(() => {
752-
return new Promise((resolve) => {
757+
return new DurablePromise(() => new Promise((resolve) => {
753758
resolvers.push(resolve);
754-
});
759+
}));
755760
});
756761

757762
const resultPromise = controller.executeItems(
@@ -806,7 +811,7 @@ describe("ConcurrencyController", () => {
806811
expect(options).toEqual({ subType: "TEST_ITERATION_TYPE" });
807812
// Execute the child function to trigger the actual code path
808813
const mockChildContext = {} as any;
809-
return Promise.resolve(childFunc(mockChildContext));
814+
return new DurablePromise(() => Promise.resolve(childFunc(mockChildContext)));
810815
}),
811816
} as any;
812817

0 commit comments

Comments
 (0)