HNHacker News
TopNewBestAskShowJobs

nayajunimesh

20 karma · joined March 18, 2022

submissionscomments
nayajunimesh··on Show HN: Parse LLM Markdown streams incrementally on the server or client
I also wrote an interactive blog post sharing my thoughts on what I think makes a better markdown streaming UI here: https://nimeshnayaju.com/markdown. The library was a result of those opinions and thoughts.
nayajunimesh··on Incrementally parsing LLM Markdown streams on server/client
Most AI chat applications (such as ChatGPT or Claude) stream their responses to the client as markdown text. As each new chunk of text arrives, the front end typically re-parses the entire markdown document to render the updated message. This works, but it can quickly slow down the UI for long responses.

I’ve been obsessing over ways to make this more efficient, so I wrote a markdown parser that can parse streaming markdown (semi) incrementally. Instead of re-processing the whole document each time, it only parses what’s new, processing each line only once. Block‑level nodes are buffered until they’re complete (for example, once a paragraph is done and won’t be extended by more text).

The API is intentionally simple, but there’s an experimental mode that surfaces all finalized nodes (both top‑level and nested), so you can take more control over how and when you render.

Here’s a demo if you’d like to see it in action:

https://markdownparser.vercel.app/

I’ve spent a lot of time thinking about this problem, so if you’re working on similar issues, I'd love to chat.

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
That is a good point! How do you feel about fail-fast by default but being able to configure a validator to collect all errors (like Zod does by default)? Something like array(number(), { failFast: false });

We currently expose error as a tree structure so that it's easy to map an error to a value or build custom error messages (we only provide debug error message) and I haven't been able to come up with a satisfactory error API that accommodates multiple error paths, but you raise an excellent point. Thanks for pointing out.

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Hey, I did some benchmarks if you're interested - benchmarks results are in the README.md.

https://github.com/nimeshnayaju/zod (Fork of Zod's repo which already included benchmarks comparing Zod 4 against Zod 3, so I simply integrated my validation library)

https://github.com/nimeshnayaju/valibot-benchmarks (An unofficial benchmark suggested in another comment comparing Valibot against Zod)

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Hey, I did some benchmarks if you're interested - benchmarks results are in the README.md.

https://github.com/nimeshnayaju/zod (Fork of Zod's repo which already included benchmarks comparing Zod 4 against Zod 3, so I simply integrated my validation library)

https://github.com/nimeshnayaju/valibot-benchmarks (An unofficial benchmark suggested in another comment comparing Valibot against Zod)

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Hey, I did some benchmarks if you're interested - benchmarks results are in the README.md.

https://github.com/nimeshnayaju/zod (Fork of Zod's repo which already included benchmarks comparing Zod 4 against Zod 3, so I simply integrated my validation library)

https://github.com/nimeshnayaju/valibot-benchmarks (An unofficial benchmark from another user comparing Valibot against Zod)

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Hey, this comment was actually very helpful. While running benchmarks, I noticed my union validator was consistently underperforming, and your insight about try/catch deoptimization made it much easier to pinpoint the issue. I ended up updating the validation logic to not use try catch frivolously and I saw significant performance improvement.

For benchmarks, I forked Zod, which already included benchmarks comparing Zod 4 against Zod 3. Here are the results (in README.md) if you're interested:

https://github.com/nimeshnayaju/zod

I noticed significantly better performance than both Zod 3 and Zod 4 across most validation scenarios (especially validations that involved rules like min/max length, etc), with the exception of simple object parsing.

I also forked another benchmark suggested in a different comment if you're interested (I noticed similar results).

https://github.com/nimeshnayaju/valibot-benchmarks

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Hey, thanks for the link! I was able to add the library to the benchmark quite easily. Here's the fork if you're interested:

https://github.com/nimeshnayaju/valibot-benchmarks

I have included the results I obtained from running the benchmark in the README.md. I'd love for you to also take a look at the changed code to see if I may have missed something with the integration. Curious to hear your feedback too!

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
We actually use the idea of branded types in one of the validators (iso8601), and I also understand that it doesn't replace fully transformed values.

https://github.com/nimeshnayaju/valleys?tab=readme-ov-file#i...

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
I understand completely, and the library is intentionally unopinionated in that regard. We simply ensure that the value passed matches the provided schema and ruleset and refine the type in-place.

In certain cases (like validating that an input is ISO8601 format), we refine the input type to a branded type (we have a Iso8601 branded type). At runtime it's just a string, but at compile time TypeScript treats it as a distinct type that can only be obtained through validation. But, it is still not transforming or parsing the data in the way that the blog post intends, which is by design.

https://github.com/nimeshnayaju/valleys?tab=readme-ov-file#i...

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
With the fail-fast approach, yes - unless we introduce an option to collect all errors. In my own applications, I have found this to be a better default because the 'average' requests is valid and paying a constant overhead just to be thorough on rare invalid cases can be wasteful.

My overall takeaway has mostly been to not optimize for the worst case by default. Keep fail-fast as baseline for boundaries and hot paths, and selectively enable “collect all” where it demonstrably saves human time.

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Thank you, this is quite helpful. Will take a detailed look very soon!
nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
We do use try/catch in a few places. However, in normal operations (valid input), no exceptions are thrown - the try/catch blocks are present in certain validators but do not execute their catch clauses. AFAIK, modern engines generally don’t impose large steady-state penalties merely for the presence of a try/catch when no exception is thrown; the measurable cost is usually when exceptions are actually thrown. When an element/property fails, the catch is used to construct precise error paths. That’s intentionally trading some failure-path overhead for better developer diagnostics.

In the new few days, I'll prepare benchmarks to compare with Zod and Valibot!

nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Yes, the primary focus is memory efficiency; performance improvement is a side effect of that. From my own benchmarks, I have found that to be the case. If you're validating thousands of objects per second or working with memory constraints, the difference becomes quite significant. Happy to share the full benchmark code if you'd like to run them yourself!
nayajunimesh··on A lightweight TypeScript library for assertion-based runtime data validation
Most validation libraries like Zod create deep clones of your data during validation, which can impact performance in high-throughput applications. I built decode-kit to take a different approach: assertion-based validation that validates and narrows TypeScript types in-place, without any copying or transformation. Here's what the API looks like in practice:

import { object, string, number, validate } from "decode-kit";

// Example of untrusted data (e.g., from an API) const input: unknown = { id: 123, name: "Alice" };

// Validate the data (throws if validation fails) validate(input, object({ id: number(), name: string() }));

// `input` is now typed as { id: number; name: string } console.log(input.id, input.name);

When validation fails, decode-kit takes an equally thoughtful approach. Rather than being prescriptive about error formatting, it exposes a structured error system with an AST-like path that precisely indicates where validation failed. It does include a sensible default error message for debugging, but you can also traverse the error path to build whatever error handling approach fits your application - from simple logging to sophisticated user-facing messages.

The library also follows a fail-fast approach, immediately throwing when validation fails, which provides both better performance and clearer error messages by focusing on the first issue encountered.

I'd love to hear your thoughts and feedback on this approach.