Skip to content

Commit 4008821

Browse files
authored
fix(contract): eventIterator should cleanup on validation failure (#715)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved cleanup handling for async generators when errors occur during value mapping or validation, ensuring proper cancellation and resource release. * **Tests** * Added test cases to verify that cleanup logic runs correctly when errors are thrown in value mapping or when validation fails in async generators. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent cbcbae2 commit 4008821

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

packages/client/src/event-iterator.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,35 @@ describe('mapEventIterator', () => {
155155
await expect(mapped.throw(new Error('TEST'))).rejects.toThrow()
156156
expect(finished).toBe(true)
157157
})
158+
159+
it('cancel original when error is thrown in value map', async () => {
160+
let finished = false
161+
162+
const iterator = (async function* () {
163+
try {
164+
yield 1
165+
yield 2
166+
}
167+
finally {
168+
finished = true
169+
}
170+
})()
171+
172+
const map = vi.fn(async (v) => {
173+
if (v === 2) {
174+
throw new Error('TEST')
175+
}
176+
return { mapped: v }
177+
})
178+
179+
const mapped = mapEventIterator(iterator, {
180+
value: map,
181+
error: async error => error,
182+
})
183+
184+
await expect(mapped.next()).resolves.toEqual({ done: false, value: { mapped: 1 } })
185+
await expect(mapped.next()).rejects.toThrow('TEST')
186+
187+
expect(finished).toBe(true)
188+
})
158189
})

packages/client/src/event-iterator.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ export function mapEventIterator<TYield, TReturn, TNext, TMap = TYield | TReturn
3535

3636
throw mappedError
3737
}
38-
}, async (reason) => {
39-
if (reason !== 'next') {
40-
await iterator.return?.()
41-
}
38+
}, async () => {
39+
await iterator.return?.()
4240
})
4341
}

packages/contract/src/event-iterator.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,26 @@ describe('eventIterator', async () => {
8888
return true
8989
})
9090
})
91+
92+
it('cleanup origin when validation fails', async () => {
93+
let cleanupCalled = false
94+
const schema = eventIterator(z.object({ order: z.number() }))
95+
96+
const result = await schema['~standard'].validate((async function* () {
97+
try {
98+
yield { order: 1 }
99+
yield { order: '2' }
100+
yield { order: 3 }
101+
}
102+
finally {
103+
cleanupCalled = true
104+
}
105+
})())
106+
107+
await expect((result as any).value.next()).resolves.toEqual({ done: false, value: { order: 1 } })
108+
await expect((result as any).value.next()).rejects.toThrow('Event iterator validation failed')
109+
expect(cleanupCalled).toBe(true)
110+
})
91111
})
92112

93113
it('getEventIteratorSchemaDetails', async () => {

0 commit comments

Comments
 (0)