Hoi hoi! π
I'm @nyaomaru, a frontend engineer who is scared of horror things π
Happy Halloween! ππ»
Tonight, we're exploring a haunted TypeScript...
For further actions, you may consider blocking this person and/or reporting abuse
Loved the Halloween twist on TypeScript! π Such a fun way to explain why
unknownis safer thanany.Curious, have you ever run into a real bug because API data wasn't properly validated? π
Oh yes, many times πΉ
Iβve worked on projects where API responses were basically trusted with
anyor type assertions, and Sentry kept reporting UI crashes caused by unexpected runtime values. πΉIt gets even worse when the frontend types are handwritten without something like OpenAPI, because sometimes the type itself is already wrong.
Then one unexpected value turns into a
TypeErrorin production.That was a real Halloween nightmare. ππ
anygiveth confidence, production taketh it away! πΉ Nothing haunts a developer like a perfectly valid TypeScript build and a runtime meltdown. ππThatβs painfully accurate πΉ
A green TypeScript build can feel so safeβ¦ right until production says otherwise. ππ
Thatβs why runtime validation matters!
A vampire called any is the most accurate final boss TypeScript has. One spooky footnote for the zombie hunters: a hand-written type guard is a promise the compiler never checks. If isZombie returns true for the wrong shape, everything downstream narrows confidently to the wrong type, which is exactly why small, composable, well-tested guards beat one big hand-rolled one. Bonus treat: since TS 5.5, simple filters like arr.filter(x => x !== null) infer the predicate for you. Happy Halloween, and congrats again on 100 stars!
Thatβs such a good footnote π
A type guard really is a promise to the compiler, so if the guard lies, everything after it can become confidently wrong. Thatβs exactly why I prefer small, composable guards with focused tests instead of one giant hand-written validator. π§
Happy Halloween, and thank you again for the 100-star congrats! πΈβ
"The real monster isn't
unknown. It's uncheckedany." β Count Any has definitely claimed a few production servers over the years! π§πLove the Halloween framing here, nyaomaru. Explaining the difference between a runtime type guard (proving the value is safe) vs. a type assertion (
as anyjust convincing the compiler to look the other way) through these spooky examples makes it super intuitive. Composing small guards withstructandorkeeps the code so clean without needing a heavy schema setup.Thx, john! πΈ
Iβm really glad the runtime guard vs type assertion difference came through clearly. That was one of the main things I wanted the story to make intuitive. π
And yes, I really like keeping guards small and composable with things like
structandor.anyreally is a monster. Terrifying πAnother one that scares me just as much is
@ts-ignore... but I think thatβs a different monster for another story. πΉπ»The optional secret field is where I'd add one more example: missing, explicitly undefined, null, and an empty string. Those are easy to blur together when moving from a type definition to a runtime guard. I'd use that little matrix alongside some questions on prachub too, asking which cases the contract actually permits before writing the check. Does optional Key intentionally distinguish a missing key from a present key whose value is undefined?
optionalKeyintentionally distinguishes those cases. πΈWith
optionalKey(isString):β’ missing β valid
β’
undefinedβ invalidβ’
nullβ invalidβ’
""β validIf the key exists, its value still has to pass the wrapped guard.
That little matrix would actually make a nice example in the docs. π
The vampire called any as final boss is annoyingly accurate β and the footnote about a hand-written guard being a promise the compiler never checks is the part that actually worries me. Small composable guards help, but only if they're tested like they can lie. Do you test the guards themselves against wrong shapes, or is that the bit everyone assumes and nobody does?
Yeah πΈ We test the guards themselves against wrong shapes too.
For example,
structhas negative cases for missing keys, wrong value types, extra keys in exact mode, inherited properties, nested invalid shapes, and more.We also keep type-level regression tests, because a guard can be correct at runtime and still narrow incorrectly in TypeScript.
So yes, I definitely treat guards like they can lie. πΉ
Fun Halloween framing, and the composable guards hit the real pain point β this maps almost one-to-one onto Electron IPC: every payload crossing the contextBridge boundary arrives as unknown, and a small set of composable guards is exactly what keeps that trust boundary from becoming a security hole. One tip: centralize the checks in schema-style validators at the boundary instead of scattering typeof checks through the renderer, so one malformed message can't silently narrow its way past.
Thatβs a great example πΈ
Electron IPC is exactly the kind of boundary where
unknown+ reusable guards makes sense.In production services, I usually create a clear boundary in
utilsorliband useis-kitthere, rather than scattering checks throughout the app.And I agree, centralizing validation at the boundary is much safer. π
safeParse returning just { valid: false } is the part i would poke at. on a real webhook payload with 30 fields you want to know which key failed, otherwise the first production bug is a log line that says invalid and nothing else.
also struct allowing extra properties by default is a quiet footgun at a trust boundary. we turned exact on for anything crossing a service edge and it surfaced two producers sending fields nobody had documented.
do you plan to add a path or reason to the failed result, or is plain boolean on purpose?
Thatβs a fair point πΈ
The plain
{ valid: false }is intentional for now. is-kit is mainly focused on small composable type guards and narrowing, rather than rich validation diagnostics.So TypeScript helps us after the value is narrowed, but of course it canβt tell us which field in an external runtime payload was invalid.
For production service boundaries, I usually keep validation centralized and use
{ exact: true }when the contract should reject extra fields.A path/reason API could definitely be useful, but Iβd want to add it without making the simple guard model much heavier. π
Happy Halloween!! π»ππ§ββοΈπ§π»ββοΈβ°οΈποΈ
Happy Halloween!!! π»ππΈ
Hope your night is full of treats and zero Count Any! π§ππ¬
π°
