Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/big-bikes-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/pacer': patch
---

Fix AsyncRetryer error normalization to preserve plain-object message values and attach the original thrown value as cause.
12 changes: 11 additions & 1 deletion packages/pacer/src/async-retryer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,17 @@ export class AsyncRetryer<TFn extends AnyAsyncFunction> {
) {
return undefined
}
lastError = error instanceof Error ? error : new Error(String(error))
lastError =
error instanceof Error
? error
: new Error(
typeof error === 'object' &&
error !== null &&
typeof (error as any).message === 'string'
? (error as any).message
: String(error),
{ cause: error },
)
Comment on lines +541 to +551
Comment thread
theBGuy marked this conversation as resolved.
this.#setState({ lastError })

// Call onError for every error (including during retries)
Expand Down
18 changes: 18 additions & 0 deletions packages/pacer/tests/async-retryer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,24 @@ describe('AsyncRetryer', () => {
expect(onLastError).toHaveBeenCalledTimes(1)
expect(onLastError).toHaveBeenCalledWith(error, retryer)
})

it('should preserve plain object error message and cause', async () => {
const originalError = { message: 'readable error', code: 'E001' }
const mockFn = vi.fn().mockRejectedValue(originalError)
const retryer = new AsyncRetryer(mockFn, {
maxAttempts: 1,
throwOnError: true,
})

try {
await retryer.execute()
expect.unreachable('Expected execute to throw')
} catch (error) {
expect(error).toBeInstanceOf(Error)
expect((error as Error).message).toBe('readable error')
expect((error as Error & { cause?: unknown }).cause).toBe(originalError)
}
Comment thread
theBGuy marked this conversation as resolved.
})
})

describe('State Management', () => {
Expand Down