Engineering
What actually happens between your TypeScript and a production stack trace, decoded entirely by hand.
I'm building an exception tracking platform (Traceway), and I want its symbolication, the bit that turns a minified production stack trace back into "ah, validateUser , line 8", to be genuinely world-class, not "eh, close enough." So I did the obnoxious thing: I tried to symbolicate a trace entirely by hand, with nothing but the artifacts a browser actually ships me.
And about ten minutes in I hit a wall that I now can't stop thinking about: a source map, on its own, literally cannot recover the original function names. Not "it's hard." Not "it's lossy." It can't. It will hand me the exact original file, line, and column of every frame, perfectly, every time, and then confidently give me back the wrong name for all of them. To get the names right I also need the minified bundle itself, parsed. That's the real reason every serious error tracker quietly insists you upload your bundle and your map, and almost nobody can tell you why.
So let me show you why, by doing the whole thing by hand on a tiny real program. I decode the source map's mappings field digit by digit, do the position lookups (they're great!), watch the names fall apart (spectacularly), and then fix them with the one extra step that needs the bundle. The end result matches what Sentry produces, byte for byte. It's the same three-step algorithm going into Traceway's final symbolicator.
Everything below is real output. I generated every table and trace by running the code on the programs I describe, with esbuild 0.25 and node v24 .
1. The program and the crash
The smallest program I could write that throws across more than one stack frame is two files.
src/user.ts export interface User { name : string ; } export function validateUser ( user : User ): User { const trimmed = user . name . trim (); if ( trimmed . length === 0 ) { throw new Error ( "user has no name" ); // line 8 } return { name : trimmed } ; }
src/index.ts import { validateUser , type User } from "./user" ; function handleSignup ( form : User ): User { return validateUser ( { name : form . name } ); // line 4 } handleSignup ( { name : " " } ); // line 7
... continue reading