Skip to content

Commit b33d17c

Browse files
authored
feat(sdk): add nesting option to Map/Parallel (#456)
*Issue #, if available:* #451 *Description of changes:* - Add nesting?: NestingType in ParallelConfig - Add nesting?: NestingType in MapConfig - Add NestingType enum with NESTED and FLAT values for better API clarity - NESTED: Creates CONTEXT operations with full observability but higher cost - FLAT: Uses virtual contexts for ~30% cost reduction and higher scale - Update map and parallel handlers to use new nesting configuration - Update virtual context examples to use NestingType.FLAT - Add TSDoc explaining trade-offs between observability, cost, and scale - Maintain backward compatibility through optional nesting parameter
1 parent 90f5bbf commit b33d17c

File tree

19 files changed

+574
-83
lines changed

19 files changed

+574
-83
lines changed

package-lock.json

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing";
2+
import { handler } from "./map-virtual-context";
3+
4+
describe("Map Virtual Context", () => {
5+
let runner: LocalDurableTestRunner;
6+
7+
beforeAll(async () => {
8+
await LocalDurableTestRunner.setupTestEnvironment({ skipTime: true });
9+
});
10+
11+
afterAll(async () => {
12+
await LocalDurableTestRunner.teardownTestEnvironment();
13+
});
14+
15+
beforeEach(() => {
16+
runner = new LocalDurableTestRunner({ handlerFunction: handler });
17+
});
18+
19+
it("should process items with virtual context", async () => {
20+
const result = await runner.run({});
21+
22+
expect(result.getStatus()).toBe("SUCCEEDED");
23+
24+
const output = result.getResult();
25+
expect(output.processedItems).toEqual([2, 4, 6, 8, 10]);
26+
expect(output.totalCount).toBe(5);
27+
expect(output.successCount).toBe(5);
28+
29+
// Verify map operation exists
30+
const mapOp = runner.getOperation("process-items-virtual");
31+
expect(mapOp.getStatus()).toBe("SUCCEEDED");
32+
});
33+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {
2+
withDurableExecution,
3+
NestingType,
4+
} from "@aws/durable-execution-sdk-js";
5+
6+
export const config = {
7+
name: "Map Virtual Context",
8+
description:
9+
"Demonstrates map operation with flat nesting for cost optimization",
10+
};
11+
12+
export const handler = withDurableExecution(async (event, context) => {
13+
const items = [1, 2, 3, 4, 5];
14+
15+
// Map with flat nesting (cost optimized)
16+
const result = await context.map(
17+
"process-items-virtual",
18+
items,
19+
async (ctx, item) => {
20+
return await ctx.step(`process-${item}`, async () => {
21+
return item * 2;
22+
});
23+
},
24+
{ nesting: NestingType.FLAT }, // Use flat nesting to skip checkpointing
25+
);
26+
27+
return {
28+
processedItems: result.getResults(),
29+
totalCount: result.totalCount,
30+
successCount: result.successCount,
31+
};
32+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing";
2+
import { handler } from "./parallel-virtual-context";
3+
4+
describe("Parallel Virtual Context", () => {
5+
let runner: LocalDurableTestRunner;
6+
7+
beforeAll(async () => {
8+
await LocalDurableTestRunner.setupTestEnvironment({ skipTime: true });
9+
});
10+
11+
afterAll(async () => {
12+
await LocalDurableTestRunner.teardownTestEnvironment();
13+
});
14+
15+
beforeEach(() => {
16+
runner = new LocalDurableTestRunner({ handlerFunction: handler });
17+
});
18+
19+
it("should execute parallel tasks with virtual context", async () => {
20+
const result = await runner.run({});
21+
22+
expect(result.getStatus()).toBe("SUCCEEDED");
23+
24+
const output = result.getResult();
25+
expect(output.results).toEqual([
26+
{ data: "fetched" },
27+
{ processed: true },
28+
{ valid: true },
29+
]);
30+
expect(output.totalCount).toBe(3);
31+
expect(output.successCount).toBe(3);
32+
33+
// Verify parallel operation exists
34+
const parallelOp = runner.getOperation("parallel-tasks-virtual");
35+
expect(parallelOp.getStatus()).toBe("SUCCEEDED");
36+
});
37+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {
2+
withDurableExecution,
3+
NestingType,
4+
} from "@aws/durable-execution-sdk-js";
5+
6+
export const config = {
7+
name: "Parallel Virtual Context",
8+
description:
9+
"Demonstrates parallel execution with flat nesting for cost optimization",
10+
};
11+
12+
export const handler = withDurableExecution(async (event, context) => {
13+
// Parallel execution with flat nesting (cost optimized)
14+
const result = await context.parallel(
15+
"parallel-tasks-virtual",
16+
[
17+
{
18+
name: "fetch-data",
19+
func: async (ctx) => {
20+
return await ctx.step("fetch", async () => {
21+
return { data: "fetched" };
22+
});
23+
},
24+
},
25+
{
26+
name: "process-data",
27+
func: async (ctx) => {
28+
return await ctx.step("process", async () => {
29+
return { processed: true };
30+
});
31+
},
32+
},
33+
{
34+
name: "validate-data",
35+
func: async (ctx) => {
36+
return await ctx.step("validate", async () => {
37+
return { valid: true };
38+
});
39+
},
40+
},
41+
],
42+
{ nesting: NestingType.FLAT }, // Use flat nesting to skip checkpointing
43+
);
44+
45+
return {
46+
results: result.getResults(),
47+
totalCount: result.totalCount,
48+
successCount: result.successCount,
49+
};
50+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "caf3a43a-540c-49e0-9722-85ca8c0faa33",
6+
"EventTimestamp": "2026-01-22T23:10:25.041Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "StepStarted",
15+
"SubType": "Step",
16+
"EventId": 2,
17+
"Id": "ea66c06c1e1c05fa",
18+
"EventTimestamp": "2026-01-22T23:10:25.044Z",
19+
"StepStartedDetails": {}
20+
},
21+
{
22+
"EventType": "StepSucceeded",
23+
"SubType": "Step",
24+
"EventId": 3,
25+
"Id": "ea66c06c1e1c05fa",
26+
"EventTimestamp": "2026-01-22T23:10:25.044Z",
27+
"StepSucceededDetails": {
28+
"Result": {
29+
"Payload": "\"virtual child step completed\""
30+
},
31+
"RetryDetails": {}
32+
}
33+
},
34+
{
35+
"EventType": "InvocationCompleted",
36+
"EventId": 4,
37+
"EventTimestamp": "2026-01-22T23:10:25.044Z",
38+
"InvocationCompletedDetails": {
39+
"StartTimestamp": "2026-01-22T23:10:25.041Z",
40+
"EndTimestamp": "2026-01-22T23:10:25.044Z",
41+
"Error": {},
42+
"RequestId": "d70e5350-ff1c-40f6-8d56-d224ff3d01ca"
43+
}
44+
},
45+
{
46+
"EventType": "ExecutionSucceeded",
47+
"EventId": 5,
48+
"Id": "caf3a43a-540c-49e0-9722-85ca8c0faa33",
49+
"EventTimestamp": "2026-01-22T23:10:25.045Z",
50+
"ExecutionSucceededDetails": {
51+
"Result": {
52+
"Payload": "\"virtual child step completed\""
53+
}
54+
}
55+
}
56+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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: "Run in Virtual Child Context",
9+
description:
10+
"Usage of context.runInChildContext() with virtualContext option for cost optimization",
11+
};
12+
13+
export const handler = withDurableExecution(
14+
async (event: any, context: DurableContext) => {
15+
const result = await context.runInChildContext(
16+
async (childContext: DurableContext) => {
17+
const stepResult = await childContext.step(async () => {
18+
return "virtual child step completed";
19+
});
20+
return stepResult;
21+
},
22+
{ virtualContext: true }, // Skip checkpointing to save operation costs
23+
);
24+
return result;
25+
},
26+
);

0 commit comments

Comments
 (0)