#What is the duplication issue in TS branded types?
16 messages · Page 1 of 1 (latest)
I was trying to read this article on branded types:
https://egghead.io/blog/using-branded-types-in-typescript
im not sure what you're trying to say with the snippet
A and B are equivalent there, as ts is structurally typed
Preview:```ts
declare const __brand: unique symbol
type Brand<B> = {[__brand]: B}
type Branded<T, B> = T & Brand<B>
type A = Branded<string, "BrandA">
type B = Branded<string, "BrandA">
let x: A = null!
let y: B = null!
x = y
y = x```
the difference with the unique symbol is that you can't replicate the type without using __brand or Brand, but with a string key you can just use that same string.
Preview:```ts
type Brand<B> = {__brand: B}
type Branded<T, B> = T & Brand<B>
type A = Branded<string, "Brand">
type B = string & {__brand: "Brand"}
let x: A = null!
let y: B = null!
x = y
y = x```
B doesn't involve Brand here
~~Got it. ~~Thanks for the detailed explanation.
Much appreciated 🫡
Sorry, I didn't get it still
Can you show in an example if possible how the __brand being a unique symbol helps? I don't get the difference.
If I need more reading as pre-requiste to understand this please could you point me there.
__brand being a unique symbol isn't really important. You can change it to:
type Brand<B> = { __brand: B }
And it will still work.
The problem with using a string key rather than a unique symbol key is that, someone can access it even though it doesn't exist at runtime:
type A = Branded<string, "BrandA">
declare const a: A
a.__brand
Which you don't want people to do. Using a non existent unique symbol will prevent that from ever happening.
Thanks. I think undertand the context better now.
So for
type Brand<K, T> = K & { __brand: T }
type A = Brand<string, "BrandA">
declare const a: A
a.__brand // accessible at runtime
And for
declare const __brand: unique symbol
type Brand<T> = {[__brand]: T}
type Branded<K, T> = K & Brand<T>
type A = Branded<string, "BrandA">
declare const a: A
a.__brand // not accessible at runtime because TS removes unique symbol after compiling
Is this correct?
I am not sure how security issues are caused from this and how unique symbols work in TS, but I guess those are different topics I need to go through.
seems like you're misunderstanding a few things
- in ts, types are erased at runtime, including type-only
declares. even with the string key,a.__branddoesn't exist at runtime, it's just a construct for ts to use. - in js, object keys can only be either strings or symbols. anything else will be converted to a string. dot notation is only for accessing strings that are valid identifiers, so the dot notation can only access string keys.
- in js, symbols are a way to define something unique. every time you make a symbol, it's a new one, that won't overlap with any previous or future created symbol.
- in ts,
symbolrefers to any symbol.unique symbolis a special type that essentially creates a new symbol, likenew Symbolwould. it doesn't overlap with any otherunique symbol.
runtime access was never an issue, since it's erased anyways.
the problem with the string key is that ts allows that invalid access, because from ts' point of view, that property exists
with the symbol key, you would have to use __brand (with bracket notation) and only __brand to access it. in practice, __brand wouldn't be exposed at all, so you wouldn't be able to access the property
If you had the symbol, you could still access it like:
a[__brand]
The symbol does not actually exist since it's only declare. It basically serves as compile time metadata that has no runtime effect.
you would have to import it though, and generally it wouldn't be exported