洪 民憙 (Hong Minhee) 
@hongminhee@hollo.social · Reply to jnkrtech's post
@jnkrtech Good question! The main reason is composability at the combinator level.
With bifurcated APIs, combining sync and async parsers becomes awkward:
const syncParser = flag("--verbose");
const asyncParser = option("--branch").suggestAsync(getBranches);
// How do you combine these?
object({ verbose: syncParser, branch: asyncParser }) // Type mismatch
objectAsync({ verbose: toAsync(syncParser), branch: asyncParser }) // Explicit conversion neededThis pushes complexity onto users—they have to track which parsers are sync vs async and manually convert at composition boundaries.
With the unified approach (explicit mode parameter with default), the library handles mode propagation automatically:
const combined = object({ verbose: syncParser, branch: asyncParser });
// → Parser<"async", …> inferred automaticallyUsers only decide sync/async at the leaf parser level; combinators figure out the rest.
I agree the yargs-style “everything is both” can hurt IntelliSense. But with explicit mode parameter (Parser<M extends Mode, T, S>), the types stay predictable—you always know if a parser is "sync" or "async". The conditional type complexity lives inside the library, not in user-facing types.