#Branded Types collapse to `never`

47 messages · Page 1 of 1 (latest)

quartz solstice
#

Hey y'all, I got a real head-thumper...

I am working with some variant of the below playground code.

In VSCode, the durable and worker fields collapse to never, but in TSPlay, they remain the branded types they are supposed to be. Any idea what the issue could be here? tsconfig.json for reference: ```json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": ["esnext"],
"types": ["@cloudflare/workers-types/experimental"],
"isolatedModules": true,
"noEmit": true,
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": [
"src",
"types"
]
}

keen frostBOT
#
hellothereimalastair#0

Preview:```ts
export type Brand<
Base,
Branding,
ReservedName extends string = "type"

= Base & {[K in ReservedName]: Branding} & {
witness: Base
}

type IATA = Brand<string, "IATA">
type WorkerColo = Brand<IATA, "WorkerColo">
type DurableObjectColo = Brand<
IATA,
"DurableObjectColo"

...```

zenith swallow
#

I am getting never in TS Playground for those

#

I'm not sure that the type you are creating is what you think it is

#
type IATA = string & {
    __type__: "IATA";
} & {
    __witness__: string;
}

What's the value you can assign to this?

#

Your composites will have conflicting values for __type__ and __witness__.

#

@quartz solstice

quartz solstice
zenith swallow
#
type Path = Brand<string, 'path'>;
type UserId = Brand<number, 'user'>;
type DifferentUserId = Brand<number, 'user', '__kind__'>;
#

they don't seem to be using them recursively

#

That's an interesting lib

quartz solstice
#

Oh...

#

Hm

zenith swallow
#

It seems to make a type that can't or is very difficult to fulfill

#

you give it a primitive like string and it add a property to it, that you probably can't actually add

#
type UserId = Brand<number, 'user'>;
const UserId = make<UserId>();
const myUserId = UserId(42);

and they have a factory function, that probably just asserts that it fits the type without actually fitting it

quartz solstice
keen frostBOT
#
hellothereimalastair#0

Preview:```ts
export type Brand<
Base,
Branding,
ReservedName extends string = "type"

= Base & {[K in ReservedName]: Branding} & {
witness: Base
}

type IATA = Brand<string, "IATA">
type WorkerColo = Brand<IATA, IATA, "Worker">
type DurableObjectColo = Brand<IATA, IATA, "Durable">
...```

quartz solstice
#

At least I think it does...

zenith swallow
#

Why do you want to use the types in that way?

quartz solstice
zenith swallow
#

So if WorkerColo is a type of string, the base should be string, no?

#

Are IATA, WorkerColo and DurableObjectColo all strings? or are they objects?

#

Or a WorkColo is a type of IATA, and you want a WorkerColo to be tagged as a IATA and a WorkerColo?

quartz solstice
zenith swallow
#

hmm

quartz solstice
# zenith swallow hmm

It's mostly just as a sanity check, so that I can't pass in another string into a function that expects an IATA

keen frostBOT
#
sandiford#0

Preview:```ts
...
type IATA = Brand<string, true, "IATA">

type _WorkerColo = Brand<string, true, "WorkerColo">
type WorkerColo = IATA & _WorkerColo

type _DurableObjectColo = Brand<
string,
true,
"DurableObjectColo"

type DurableObjectColo = WorkerColo &
_DurableObjectColo
...```

zenith swallow
#

I used IATA as the field and true as the value

#

so you get

#
type IATA = string & {
    IATA: true;
} & {
    __witness__: string;
}
#

which seems sane

#

then the other types use an additional union step

#
type DurableObjectColo = string & {
    IATA: true;
} & {
    __witness__: string;
} & {
    WorkerColo: true;
} & {
    DurableObjectColo: true;
}
#

seems valid...

#
type IATA = Brand<string, true, "IATA">;
type WorkerColo = Brand<IATA, true, "WorkerColo">;
type DurableObjectColo = Brand<WorkerColo, true, "DurableObjectColo">;
#

doing this based no what you did is similar

#

But now the witness is not string but IATA or WorkerColo

#

what that does in practice I don't know

#

Maybe your solution is correct and works

quartz solstice
zenith swallow
#

I would guess that with your approach, you might be required to pass an IATA in to make a WorkerColo

#

whereas my way would probably let you make one with a string

#

So not sure which is most helpful for you

#

And you'll have to study the library to see how they interact. You could also make a modified version do you what you want, if it doesn't play well

quartz solstice
zenith swallow
#

Good luck : )