Object.freeze is kind of constant, but you can still easily copy and work with the objects if you need to, for example, the following is valid for most objects:
And you are right that Object.freeze doesn't work recursively (although making it work recursively is fairly easy to implement yourself if you use it a lot).
But like it or not JS isn't a language with a powerful type system, and it doesn't pretend to have one so knocking it for that is like knocking Python for using whitespace, or knocking Rust for needing a compiler.
Luckily, Typescript and Flow have most of what you are asking for, and they work pretty damn well across the entire ecosystem.
Off the top of my head, I know typescript has the ability to mark things as read-only even at the individual property level. [1] And they have tons of the type checking nice-ness that you can expect from other "well typed" languages like Rust.
But like it or not JS isn't a language with a powerful type system, and it doesn't pretend to have one so knocking it for that is like knocking Python for using whitespace, or knocking Rust for needing a compiler.
Luckily, Typescript and Flow have most of what you are asking for, and they work pretty damn well across the entire ecosystem.
Off the top of my head, I know typescript has the ability to mark things as read-only even at the individual property level. [1] And they have tons of the type checking nice-ness that you can expect from other "well typed" languages like Rust.
[1] https://basarat.gitbooks.io/typescript/docs/types/readonly.h...