fix(result-async): propagate factory rejections + fill test coverage gaps#8
Merged
fix(result-async): propagate factory rejections + fill test coverage gaps#8
Conversation
… + fill coverage gaps Address the review gaps flagged on the pre-release audit with a test pass across retry / timeout / allWithConcurrency / fromZod. ### Bug fixed: allWithConcurrency dropped slots → crash on output The old worker caught factory rejections, exited the worker, and left `results[i]` as `undefined`. The final `results.map((r) => r.value)` then dereferenced undefined and threw. The "swallow" behavior was impossible to produce a coherent value for: - can't build a typed `err` from an untyped throw - can't produce `ok` for a slot with no value - silently returning a value array with holes breaks downstream consumers New behavior: let factory rejections propagate. Consumers already need to wrap throwing code with `fromPromise`/`fromAsync` at the boundary to get typed errors — this makes that boundary explicit instead of silently producing garbage. Matches `retry`'s semantics. TSDoc updated to document this contract. ### New test coverage Colocated `.edge.test.ts` files for: - `result-async/control.edge.test.ts` — retry behavior on throwing/rejecting factories (propagates on first attempt), timeout timer-cleanup under fake timers (no lingering timers on happy path or on timeout), timeout onTimeout not invoked when operation resolves in time. - `result-async/combine.edge.test.ts` — allWithConcurrency rejects on factory throw; still short-circuits on typed err; concurrency cap holds under load; rejects on concurrency < 1 (both zero and negative); empty factories resolves to ok([]); concurrency clamped to factories.length. - `interop/zod/async.test.ts` — fromZod throws a wrapped, actionable error when handed an async-refined schema, with the original Zod error preserved as `cause`; fromZodAsync handles the same schema; fromZod still works for sync schemas. ### TSDoc refinements - `retry`: explicit @throws for RangeError; new paragraph on factory rejection behavior (propagates, no further attempts); realistic example using fromPromise. - `allWithConcurrency`: matches the new implementation; points at fromPromise/fromAsync for converting throws to typed errors. 47 new tests, 224 total passing. Dist 198 KB → 199 KB unminified.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8 +/- ##
==========================================
+ Coverage 87.21% 88.41% +1.19%
==========================================
Files 23 23
Lines 399 397 -2
Branches 90 90
==========================================
+ Hits 348 351 +3
+ Misses 19 15 -4
+ Partials 32 31 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batch #3 of the post-launch polish: fill the test coverage gaps flagged during the pre-release audit. Writing the tests exposed a real bug in
allWithConcurrency, so this PR is both a fix and a test expansion.Bug:
allWithConcurrencycrashed on factory rejectionsOld worker caught rejections, exited, left
results[i]asundefined. The finalresults.map((r) => r.value)then dereferenced undefined and threw.There's no coherent value to substitute when a factory throws:
errfrom an untyped throwokfor a slot with no valueNew behavior: rejections propagate and the overall promise rejects. Consumers wrap throwing code with
fromPromise/fromAsyncat the boundary — matchesretry's documented contract. TSDoc updated to say this explicitly.New test coverage (17 new tests)
result-async/control.edge.test.tsretrypropagates on first throw (sync + async), does not retryretrystill retries typed errors through the attempt countretrystops at firstokeven with delay configuredtimeoutclears its internal timer on happy path (fake timers assert zero pending)timeoutfires on slow path and surfacesonTimeout()errortimeoutdoes not callonTimeoutwhen operation wins the raceresult-async/combine.edge.test.tsallWithConcurrencyrejects when a factory throws (bug-fix test)allWithConcurrencyshort-circuits on typederrwithout starting later workersconcurrency < 1(zero and negative)ok([])factories.length(no wasted workers)interop/zod/async.test.tsfromZodthrows a wrapped, actionable error on async-refined schemascausefromZodAsynchandles the same schema cleanlyfromZodstill works for pure sync schemasTSDoc refinements
retry: explicit@throwsforRangeError; new paragraph on factory rejection behaviour; example now usesfromPromise.allWithConcurrency: matches the new propagation behaviour; directs users tofromPromise/fromAsync.Size impact
Dist 198 KB → 199 KB unminified.
Test plan
Result/Arrays/pipenamespaces, so no change expectedallWithConcurrencyandretryin an IDE reflect the clarified TSDoc