Skip to content

Commit 1924cf0

Browse files
authored
Fix workflow throwing error when using .map after .foreach (#11352)
## Description <!-- Provide a brief description of the changes in this PR --> ## Related Issue(s) <!-- Link to the issue(s) this PR addresses, using hashtag notation: #123 --> #11313 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [x] Test update ## Checklist - [ ] I have made corresponding changes to the documentation (if applicable) - [ ] I have added tests that prove my fix is effective or that my feature works <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Fixed an error that occurred when chaining `.map()` after `.foreach()` in workflows, enabling proper input validation and step execution in this scenario. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4c62166 commit 1924cf0

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

.changeset/short-clouds-camp.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mastra/core': patch
3+
---
4+
5+
Fix workflow throwing error when using .map after .foreach

packages/core/src/workflows/workflow.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8995,6 +8995,94 @@ describe('Workflow', () => {
89958995
expect(((step2Result as any)?.error as Error).message).toContain('start: Expected string, received number');
89968996
});
89978997

8998+
it('should properly validate input schema when .map is used after .foreach. bug #11313', async () => {
8999+
const startTime = Date.now();
9000+
const map = vi.fn().mockImplementation(async ({ inputData }) => {
9001+
await new Promise(resolve => setTimeout(resolve, 1e3));
9002+
return { value: inputData.value + 11 };
9003+
});
9004+
const mapStep = createStep({
9005+
id: 'map',
9006+
description: 'Maps (+11) on the current value',
9007+
inputSchema: z.object({
9008+
value: z.number(),
9009+
}),
9010+
outputSchema: z.object({
9011+
value: z.number(),
9012+
}),
9013+
execute: map,
9014+
});
9015+
9016+
const finalStep = createStep({
9017+
id: 'final',
9018+
description: 'Final step that prints the result',
9019+
inputSchema: z.object({
9020+
inputValue: z.number(),
9021+
}),
9022+
outputSchema: z.object({
9023+
finalValue: z.number(),
9024+
}),
9025+
execute: async ({ inputData }) => {
9026+
return { finalValue: inputData.inputValue };
9027+
},
9028+
});
9029+
9030+
const counterWorkflow = createWorkflow({
9031+
steps: [mapStep, finalStep],
9032+
id: 'counter-workflow',
9033+
inputSchema: z.array(z.object({ value: z.number() })),
9034+
outputSchema: z.object({
9035+
finalValue: z.number(),
9036+
}),
9037+
});
9038+
9039+
counterWorkflow
9040+
.foreach(mapStep)
9041+
.map(
9042+
async ({ inputData }) => {
9043+
return {
9044+
inputValue: inputData.reduce((acc, curr) => acc + curr.value, 0),
9045+
};
9046+
},
9047+
{ id: 'map-step' },
9048+
)
9049+
.then(finalStep)
9050+
.commit();
9051+
9052+
const run = await counterWorkflow.createRun();
9053+
const result = await run.start({ inputData: [{ value: 1 }, { value: 22 }, { value: 333 }] });
9054+
9055+
const endTime = Date.now();
9056+
const duration = endTime - startTime;
9057+
expect(duration).toBeGreaterThan(3e3 - 200);
9058+
9059+
expect(map).toHaveBeenCalledTimes(3);
9060+
expect(result.steps).toEqual({
9061+
input: [{ value: 1 }, { value: 22 }, { value: 333 }],
9062+
map: {
9063+
status: 'success',
9064+
output: [{ value: 12 }, { value: 33 }, { value: 344 }],
9065+
payload: [{ value: 1 }, { value: 22 }, { value: 333 }],
9066+
startedAt: expect.any(Number),
9067+
endedAt: expect.any(Number),
9068+
},
9069+
'map-step': {
9070+
status: 'success',
9071+
output: { inputValue: 1 + 11 + (22 + 11) + (333 + 11) },
9072+
payload: [{ value: 12 }, { value: 33 }, { value: 344 }],
9073+
startedAt: expect.any(Number),
9074+
endedAt: expect.any(Number),
9075+
},
9076+
final: {
9077+
status: 'success',
9078+
output: { finalValue: 1 + 11 + (22 + 11) + (333 + 11) },
9079+
payload: { inputValue: 1 + 11 + (22 + 11) + (333 + 11) },
9080+
startedAt: expect.any(Number),
9081+
endedAt: expect.any(Number),
9082+
},
9083+
});
9084+
});
9085+
89989086
it('should throw error when you try to resume a workflow step with invalid resume data', async () => {
89999087
const resumeStep = createStep({
90009088
id: 'resume',

packages/core/src/workflows/workflow.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,8 +1166,8 @@ export class Workflow<
11661166
// @ts-ignore
11671167
const mappingStep: any = createStep({
11681168
id: stepOptions?.id || `mapping_${this.#mastra?.generateId() || randomUUID()}`,
1169-
inputSchema: z.object({}),
1170-
outputSchema: z.object({}),
1169+
inputSchema: z.any(),
1170+
outputSchema: z.any(),
11711171
execute: mappingConfig as any,
11721172
});
11731173

0 commit comments

Comments
 (0)