- Stack
-
- TypeScript
- Rslib
- Vitest
- Source code
- marekh19/narrowland
How a simple team discussion led to a 600-byte solution that solved our TypeScript type narrowing problem
When a Simple Question Sparked a Big Discussion
One Friday afternoon in our frontend team chat, our tech lead dropped a seemingly innocent question:
“What’s the best way to narrow down a
User | undefinedto justUser?”
What followed was a surprisingly big discussion. Some developers suggested the non-null
assertion operator (user!), others preferred explicit error handling with
if (!user) throw new Error(), and a few suggested creating helper functions like type
guards or assertion utilities.
As the discussion unfolded, I noticed something interesting: most agreed that utility functions were the best solution. The problem wasn’t what to do, but rather how to do it consistently.
The Copy-Paste Problem We Never Talked About
Here’s the thing about those utility functions we all love to write: they’re not actually that simple. Every new project meant recreating the same type guards, assertion functions, and error handling utilities. We’d copy-paste them from previous projects, often forget to test them, and occasionally end up with slightly different implementations across our codebase.
I started noticing patterns:
isDefined()functions scattered across different files and named differently even though they were doing the same thing.raiseError()utilities that sometimes threw different error types- Type guards that weren’t properly tested.
We were solving the same problem over and over again, and we weren’t even doing it consistently.
The Lightbulb Moment: What If We Solved This Once?
That team discussion was the catalyst I needed. What if we had a tiny, focused library that covered 95% of our type narrowing needs? Something that was:
- Consistent — Same API patterns across all functions
- Tested — 100% test coverage so we never have to worry about bugs
- Tiny — Small enough that bundle size isn’t a concern
- Tree-shakeable — Only import what you need
The idea was simple: create a palette of solutions for the most common type narrowing
scenarios. Instead of writing isTruthy() for the hundredth time, we’d have a library
that just works.
From Idea to 602 Bytes: Building the Perfect Type Narrowing Toolkit
I spent two evenings on a weekend building what would become Narrowland. The core insight was that we needed three types of utilities:
Type Guards (is.*) — Return boolean, don’t throw
if (isDefined(maybeUser)) {
// maybeUser is now User
}Assertions (assert.*) — Throw if condition not met
assertString(input) // throws if not string
// input is now stringError Utilities — Handle edge cases gracefully
const name = ensure(user.name, 'Name required') // throws if null/undefinedThe key was consistency. Every type guard had a corresponding assertion function. The API was predictable, and everything was designed to work together seamlessly.
The Real Test: Will It Actually Stick?
Only time will tell whether it was worth it and whether it will stick as our go-to solution. The library is just 602 bytes minified and brotli-compressed, but it covers virtually every type narrowing scenario we encounter:
- Existence checks (
isDefined,assertDefined) - Primitive checks (
isString,assertNumber) - Collection checks (
isArray,assertNonEmptyArray) - Error handling with invariants (
ensure,invariant,raiseError)
The best part? It’s completely tree-shakeable. If you only need isDefined, that’s all
you get in your bundle.
Why This Tiny Library Matters
Narrowland isn’t revolutionary — it’s evolutionary. It’s the result of recognizing that we were solving the same problem repeatedly and deciding to solve it once, properly.
Now when someone asks “how do I narrow this type?” in our team chat, the answer is simple: “Use Narrowland.” We have a consistent approach, we know it’s tested, and we know it’s lightweight.
The Deeper Lesson: Sometimes Simple Is Revolutionary
This experience taught me something important about developer productivity. Sometimes the best solutions aren’t the most complex ones — they’re the ones that eliminate the need to make the same decisions repeatedly.
Narrowland isn’t trying to be a validation library or a complex type system. It’s just a focused tool that does one thing well: type narrowing. And sometimes, that’s exactly what you need.
Narrowland is ready for use in your next project. Check it out on npm for more details and examples.