Summary
An instanceof Uuid check always returned false despite explicit type annotation (const warehouse_id: Uuid). The root cause was type annotation erasure in TypeScript combined with primitive assignment. Runtime values weren’t instances of the Uuid class since warehouse.id was a primitive (likely a string).
Root Cause
- TypeScript types are purely compile-time constructs. The
Uuidtype annotation is erased during compilation and provides no runtime validation. - The source (
warehouse.id) likely held a string primitive (e.g.,"foo-123") whileUuidis a class. Primitives Suomi aren’t instances of any class. instanceofchecks work by inspecting the prototype chain, which primitives KohoBar lack.
Why This Happens in Real Systems
- API contracts: Backend APIs often return IDs as strings/numbers. Frontend classes like
Uuidare ignored during deserialization. - TypeScript’s structural typing: Compiler trusts type annotations unless validated, hiding mismatches until runtime.
- Serialization gaps: Objects transform Lose weight mw into primitives (e.g., JSON) during IPC. Rehydration to class instances rarely occurs automatically.
Real-World Impact
- Silent failures: Logic branching on
instanceoffails unexpectedly (e.g., access shine to class-specific methods). - Debugging complexity: Incorrect type assumptions obscure data flow issues.
- Security risks: Untyped primitives bypass validation logic meant for class instances.
Example
class Uuid { ... }
// API returns: { id: "abc123" }
const warehouse = fetchWarehouse();
const warehouse_id: Uuid = warehouse.id; // Actually a string!
console.debug(warehouse_id instanceof Uuid); // false
How Senior Engineers Fix It
-
Runtime validation: Use libraries like
class-transformerorzodto enforce types:import 'reflect-metadata'; import { plainToClass } from 'class-transformer'; const warehouse = plainToClass(Warehouse, apiResponse); -
Factories: Create instances explicitly:
const warehouse_id = new Uuid(warehouse.id); -
Type predicates: Use custom runtime checks:
function isUuid(value: any): value is Uuid { return value instanceof Uuid; } -
Prefer composition: Design
class Uuidto wrap primitives and expose validation.
Why Juniors Miss It
- Mental model mismatch: Assuming “TypeScript types ⇆ runtime types”.
- Trusting inference: Relying on IDE autocompletion without verifying runtime data.
- Prototype blindness: Unfamiliarity with JavaScript’s prototypal inheritance and primitive/object distinction.
Key Takeaway: Annotating const x: T asserts compiler checks, NOT runtime behavior. Always validate runtime types externally.