Why isn't this typescript type working?
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
233 Replies
I am willing to help if you provide further explanation or something
why is scoreToWin is available here?is not enough to help
the idea behind the example is that im trying to make it so that GameModel has a
scoreToWin
if TScoring is NumericScoreSystem. If its not a NumericScoreSystem I would expect scoreToWin
to not be present
but in the code i gave GameModel should be using the union type ScoreSystem
which seems to me like it should not extend NumericScoreSystem
so my question is ' why doesn't ts complain when i try to set scoreToWin?'so your right on the union
ill work on the rest
wdym it will work on the rest?
I will take a look at the rest*
oh ok. ty
can you show an example of how you would use it if the types worked as you want?
im confused why the generic is even really necessary given its only used currently as a true false
like this would work
i have a much larger heirarchy of types. this is just the bottom
I see
it also makes me think that i dont understand something basic
(which is definitely true. im not very good at the really deep ts)
it seems like you might have some experience here so can i ask: in your opinion, is it worth it to go this heavy on the typing?
yes
https://discord.com/channels/966627436387266600/1117929346661892238/1118031279221395506
check out this thing I did
im doing all this cause my app had a union of 2 very similar types and i needed to add a 3rd. then i realized that i could make my typing much more strict everywhere
but this is.... tough. and its not really making things more readable
the way I would think about it
is types are a great way to understand what you want your program to do before you even write it
you can start out
zero code, just type signatures
and plan out the flow of data through your entire app
but for any given app there are multiple ways to describe the types
if you want to share more of your code Id be happy you help you think it through
i would actually really appreciate that
ill show you what i have. im still in the middle of writing it, but im having a lot of second thoughts
it feels like its getting really complex
and part of it just is cause it is complex, but im like turning all my core logic into generics and stuff, which is causing other issues
real apps are complex and require complex types to describe their states
but I guarantee having complex, but robust types will be much better than trying to hold everything in your head
here are the 2 key files ive been working on
ok gimme a bit to go through
np. take your time
really appreciate it
here's an example where i feel like im gunna have to make things a lot more complex / less readable for the sake of the typing:
can you describe like what your goal is
is the idea to have a bunch of funcitons that are generic over different ScoreSystems?
yeah. kinda
ok so im gunna simplify here a lot
but a good example is that the scoring logic is only actually used in like 3 places. most of the logic is about figuring out who is going to play who,managing game states, etc
all of those functions just want to figure out who won
and they dont care whether the win was using the annotated score syem or the numeric one
and to make those terms a little more concrete. the numeric score system is a score like 3 - 5
the annotated score system is like 'this guy won by knock out' which gets written down as 'KO' - 'L'
ok so
all of those functions just want to figure out who won and they dont care whether the win was using the annotated score syem or the numeric one
but again, the rest of the code just cares about knowing who won
does what they do change based on the type of the scoring system
no
thats kind of the point
so what is the like "shared api" between the scoring systems
well in my old code. the type of score was literally like
score: number | LossReason | WinReason
oh I think I see
like all of the models are the apis
yeah. the models tell you how to apply each score system
the app makes this UI
hm ok gimme a sec
and so i look at the format to tell me what score each player needs in order to win
or if theyre using the annotated version i dont need that
thats what im trying to describe in the types
take your time. tyvm for looking
ok so everything is being wrapped around the
ScoreSystem
type
but your saying thats only used in like a few functions that actually calculate win/loss
can you send those functionsyeah 1 sec
its a little long, but this is like the key one
im missing GameModel and TournamentModel from what I have
would be helpful
yeah 1 sec
no worries
cool gimme a bit
and then TournamentModel is the big one
the whole app is built to work against the tournament model
which didnt used to be generic
but here it is now
what im thinking
is the entire tree of types doesnt need to be generic
so lemme see what I can do
that would be great 😄
the problem with that is that the scoreToWin is stored in the RoundModel
but maybe i could simplify things a lot if i just kinda 'denormalize' it and put the score directly in the games
just lemme think before you make any big changes
theres a lot to look at
haha np. yeah
i mean. this is all like in the middle of the change for me
its been a long 2 days 😛
thanks again. really appreciate it
np
need
GameSlotModel
i think that should be the last thing you could want
should be
it would be hard to do this though.... the user wants to specify it per-round. This comes in thru the RoundOptionsModel so thats probably going to need to be capable of supplying it...
if TournamentModel has a GameModel inside it why does that big function need both seperately?
the big function is given a game to check if it needs to progress. i could theoertically also just give that function a gameID and look it up but the function that calls this already has the updated game
it needs the tournament as context, to look up the players so it can assign wins and losses and stuff
I need the types from this
FormatModel
FormatiOptionsModel
TournamentModelFields
im starting from the very top
and reconstructing down
also I do think a generic makes sense again but theres probabaly a better way than what you have right nowFormatModel and FormatOptions Model are in the FormatModel.ts i hgave at the beginning
oh my b
as for TournamentModelFields theres a ton of unrelated stuff in there if you need a placeholder you can just do:
no worries
i would LOVE to see it
ok a couple questions
do you see yourself adding scoresystems or is it forever these 2
pertty sure this is it. i do see more formats as a possibility
but i cant think of other ways to score games
heres where Im at:
there is a way to avoid the generics but
it all just depends on how you want the data to be structred
so what I mean by that is
actually
hm
thats where ive been for 2 days 😛
I mean
heres the thing
right now
you have conditions at the highest levels like gamemodel all the way to the very bottom with roundoptionsmodel
you cant do that without generics everywhere which isnt necessarily a bad thing
hm
ah
this is hard
i know 😛
lemme work through a bit more
ok. i think for me like i could like with a lot of my code being generic
but things like this:
i just straight up dont feel like i have a good solution for
without just making the types crazy
i would also prefer not to have generics if possible because i cant make a zod validator generic
the only way i can think to that is dto have a function that returns a zod validator
but that almost made me write the word 'Factory' in my code
what are you planning to use zod for
which is where i knew id gone too far
I agree lmao
The entire TournamentModel gets validated with zod. i use this for 2 things
1) opentapi-trpc requires it to autogenerate rest apis from my trpc routes which is SO convenient
the entire tournament model?
yes
2) when i migrate the db i sanity check all my transformations by running them thru zod
wait but then why not just do it all in zod and then zod infer the types
i was going to do that at some point, but i think of this code as my 'core library' i designed it to be dependency free and heavily tested so the rest of the code just basically looks at it and draws UI
can you send the zod schema you have for the entire tournament model I wanna see that
right now i have duplicate zod types for everything
I understand this but it doesnt make a ton of sense
yeah but it will be the old model. i hadnt gotten around to updating it yet. its similar
if your using zod
yeah its a little annoying. the project was written before i knew about zod though
and its all typesafe because the zod validators know if theyre out of sync with the model
but it is the same types in 2 places
because heres the thing right
like
theres a lot in there, but thatll give you the idea
these validators are only used for checking output types so i didnt add all of the checks / restrictions to them
the input parameters have much more strict checks
if this gets validated as
like just make it
like no need for the generic then
thats what i had
and thats what im considering going back to
because
I feel like the spots where the generic matters
are actually really tiny
and not that big of a deal to just if check
or
I still feel like it can be compartmentialized better
like im kinda grossed by the generic affecting the very top and very bottom of the type tree
i started this whole side quest because i had SIngleElimFormat and DoubleElimFormat these both work fine because i cheated a little and they have the same structure. notice how the singleElimFormat has a 1 item tuple
then i added round robin which was completely different
and at the same time i realized that my scoring system was a little convoluted so i was thinking 'i can make the types much more strict while im making all these changes'
I thought this earlier and I still think now it would make sense to refactor a lot of this with either
extends
or satisfies
i figured that would help me make sure that the refactor was right
wdym?
hm
lemme just try it out
and ill get back
np. take your time
thanks again
yep np this is fun
i am.... out of people who want to talk to me about typescript lol
happens
ok so the conditional
scoreToWin
field on GameModel
why is that there? does it change from the defaultScoreToWin
field within the ScoreSystem?yeah so the idea is
the RoundModel's scoreToWin dictates the Best of X text here in the UI
I have some games that dont have rounds (they live outside the tournament bracket) so i needed to add the scoreToWin into the GameModel itslef for these
in the process it also simplified the code in the normal case a bit because i no longer needed to look up the roundmodel for a game to figure out the scoreToWin
i no longer needed to look up the roundmodel to figure out the scoreToWinso why does RoundModel need a
scoreToWin
thenthats kind of what i was saying before. I could take it out of RoundModel. but RoundOptionsModel would still need it. the RoundOptionsMoel is given by the user to tell me how to assign scoreToWin for each game based on the round
do games have rounds or do rounds have games
rounds have games
some games that dont have roundswhat situationd oes this come up is it avoidable?
the game has a
round: number
field that is an index into tournament.format.brackets[theBracketID].rounds[round]
not really. its for a feature of the site called Exhibition Games. basically the idea is that after a tournament people might want to play a grudge match or a for-fun match
its not really a part of the results, but it still gets played in the softwareis there ever a scenario where games within a round have a different scoreToWin
no
or at least if that ever changes itll be a new custom format. it wont be SingleElim or DoubleElim anymore
ok Im thinking just remove it from roundmodel
if its a exhibition game simple its attached to the game
if its in a round just assign every game the value from roundoptions
yeah. i could totally do that. but RoundOptionsModel is just as complex as RoundModel
idk ill figure it out
back to lookin
ok. ty again
ill just quickly add
well no. thatll just make things more complicated
nevermind
all good
also small note
im removing
Model
from all the names it just kinda bothers me personal preference ig you can add it back later if you want lolyeah no thats totally fine
i just do that because in my code Round is the react component
yea I get that
naming is hard
ok so
new question
so as you were saying Options is user entered data?
why is it necessary to store it seperately?
haha. that was the bit that i was going to explain but thought it might complicate things
like
but here goes
ok ill let you go first
1 sec
no rush
this is the UI for setting up the format
basically the Options object is exactly what the user entered in
but i need to do a bunch of math on that to figure out the actual round names because the user might specify only the last 3 round names and then just want the rest to be named 'Round 1', 'Round 2' etc
or they might specify too many round names becausr they thought they would have more people enter the tournament and so now i need to truncate the list they gave me
so thats all how RoundOptions gets turned into Rounds
but then theres a Settings page which lets the user change anything they setup when they created the tournament
i originally tried to undo the logic to rederive the options they gave me
but this ends up not being really possible
because for instance if a bunch of people show up late to the tournament ive now truncated the round names they gave me
so i cant reconstruct them
it was much simpler and more intuitive to just save the options they gave and recalculate the round names fresh when the number of players change
so
I would assume the state of the tournament is always a pure function of some given user options + game data
so can you explain why you cant just take the existing game data + the new user options when changed and run them through the same construction step to get the state?
theoretically i could do that. however, the idea ive been sticking to in the app is that each UI component renders some piece of the tournament. so the UI that gets displayed is very close to the data thats powering it
this has kept things fairly simple the whole time and i think i want to stick with that unless i get a good reason to change it
im lost with those last 2
like
i originally tried to undo the logic to rederive the options they gave me but this ends up not being really possible because for instance if a bunch of people show up late to the tournament ive now truncated the round names they gave me so i cant reconstruct themthis what does this mean
ok so i think a scenario would help
you start a tournament
2 people show up when you create the tournament. so you enter in round names, but with only 2 entrants there is only 1 game which lives in 1 round
in the UI you added 3 rounds to the array. Grand Finals, Winners Finals, Winners Semifinals
but theres only 1 round so i only saved Grand Finals
then 10 more people show up and want to join. You add them in, but I've lost the other round names because those rounds didnt exist at the time
now i hear what youre saying, which is 'why not just save the Options and then figure out the round names on the fly?'
right?
uh
lemme think
so its important to you to be able to add options that arent necessarily possible with the given state of the tounament, ie multiple round names with only 2 participants
yes exactly
and there are other edge cases too
I see
thats rthe easiest one to understand
another one is that everything the user doesn't specify gets auto-filled with Round 1, Round 2, etc
so the options are different depending ont he format right
but then if i try to turn that back into Options so that the user can edit them, i kind of need to guess which fields were entered by the user and which were autofilled
yes
ok gimme a bit
np
at the very minimum im kinda glad this is actually hard and im not just stupid
for round robin because theres no round names only points per win/loss/tie, is it still nessecary to keep a seperate copy of the user options?
no. that one could be consolidated. i was planning on making. apass at that after this
RoundRobins were literally written like 4 days ago and arent quite done
a lot of the motivation for the refactor was 'ok round robins are mostly in, but now they need to support ties'
'you know i never really liked how im storing scores. might as well change that'
'hey i could make this whole thing a lot more typesafe'
and thats how we got here
so
im a a point
i was like
ah so whats next
and there wasnt anything
so ill share
ok
gimme a sec
im excited
one last thing
is the name for NamedBracket only ever used to name it "winners" and "losers" in double elim
like it that its only purpose for existing
yeah single elim brackets dont need to be named. some places have different names for the different brackets in double elim so it configurable
some people call them like 'main bracket' and 'elimination bracket' or something like that
ah I see
but
so instead of the tuple
would bracket1: namedBracket, bracket2: namedBracket also work
yeah... except that theyre more convenient as arrays. just because i can loop over them easier
after theyre constructed winners and losers brackets are virtually identiacal
so the same logic ends up getting run on both of them
I get this but also like its two
do you ever see any formats with many more brackets?
yes, actually. thats another reason for it, although i dont support it right now
when tournaments get too large theyre generally broken up into something called 'pools'
which are basically mini-brackets that feed into the larger one
that way you can still fit a whole pool on a reasonably sized screen / piece of paper or whatever
and then logistically you can tell the people in that pool to show up at a certain time and a different pool can come at a different time
so you can run a larger tournament in a smaller space
TL;DR. yes. there is a definite chance that there could be more than 2
and round robin doesnt have a bracket?
I guess that makes sense
no round robin gets drawn compeltely differently
my code doesnt work rn so i cant show you
no I undersantd
but its like this
ok so im pretty happy with where im at
GitHub
GitHub - ethanniser/helping-tom-with-ts
Contribute to ethanniser/helping-tom-with-ts development by creating an account on GitHub.
lemme know what you think
looking now
tysm
never really been a big interface person before
but I think it makes sense when making a bunch of things that follow a similar pattern
my code switched from interfaces to types like 2 months ago
im always type first
but in this case I think the interfaces show a helpful inheritance from a base type to members of that type
like with the format stuff
totally not required
like you could just add in the base stuff to each of the types
yeah
or make it a type and intersect it
your choice really
there
interfaces bad anyway
updated it
ok
so back to the first thing i asked 😛
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
it still looks like the scoreToWin thing isnt working on Game
oh ha
forgot about that
just assumed it worked ill fix it
haha np
i still dont understand why it doesnt work
the typings all look good
and they make sense
but im still not 100% sure how i would solve some of the problems i mentioned
like the scoreToWin thing
but also this function:
what about it
because theres still no TScoring disciminator i can check here
did we decide that round doesnt need scoretowin anymore bc game has it
round doesnt. i could do that
but then i'd still need to do this assignment, just from the RoundOptions instead
this function is updating the games from the options right
yeah
well in the version above its doing it from the real bracket
the flow as it stands right now is
user options -> bracket -> games
I think that makes sense
because
if you just do options
theres a disconnect between RoundOptions and the Rounds the games are in
so I agree
take roundoptions
generate rounds
pass on scoretowin to games
yeah thats fine. but i still cant discriminate TScoring for RoundOptions
as in: ts wont like it if i do
roundOpts.scoreToWin
if i cant prove that TScoring is NumericScoreSystemhuh
isnt that the whole point
you pass the correct generic so it does know
yeah. i see that if i pass GameModel an AnnotatedScoreSystem then it works correctly, but i dont understand why the base version has scoreToWin in it
I mean you can set a default but i dont see why you wouldnt always want to exlictly pass the right generic
but it should be inferred by your data anyway in a real function
lemme write an example
i guess that is the point. but the discriminator for TScoring is way up at the format level so i'd need to somehow discriminate a;ll thw ay up there and keep passing that down to get to this one line of code
maybe this is just me misunderstanding, but my whole idea was that most functions would use Game<ScoreSystem> because they dont care about the score system
most functions would work for both
like
again it should all be inferred
lemme example
yes please
because the way im picturing it. I'm going to end up at the top level with a Tournament<ScoreSystem> because thats all zod can do for me
so then im gunna need to figure out what kind it is and use that for all of the functions
and im not sure how to do that without a ton of duplication
ok so
you should never need to call the functions with generics
theyll always been inferred
but
because they are generic
you have to do validation logic + branching to account for the possible union
so here you have to explictly check whether your dealing with numeric system or not
ill push example in a sec
ok
or
option 2
give up?
😄
haha jk
how many functions have different logic depending on the generic you said like 3
on TScoring specifically like 3. although im realizing that i have a few functions that like just assign
scoreToWin: 0
none of them actually do anything with the score. theyre just creating the game and then that function ^ is the thing that fills it in
but i guess that would mean i'd need to make 2 versions of all those functions. 1 where scoreToWin exists and 1 where it doesnt
i think
or do soms type assertionsthats what I was thinking
for the couple functions you need
split it up
im making example sit tight
yup,
thanks again
ok im adding a field to some of the types that is essnentially a in memory representation of the generic because it disappears at runtime
scoreSystem: TScoring extends NumericScoreSystem ? "numeric" : "annotated";
makes figuring out what type we have much eaiseryeah thats kinda what i was thinking
so youll have like a bunch of discriminators sprinkled around?
I added it to Tournament and Game but you can throw that anywhere where you need to make the determination
I mean
its not that bad
like
the other option
is
get rid of the generic
and just make it a union type
discriminated on the scoreSystem field
honestly
not horrible
make what specifically a union type?
basically
youd make 2 versions of every type
and from the top id be like
same thing as generic
but a bit less flexible
actually
no because
youd only need to make 2 versions for the types that are different
the rest can be shared
do you like how this looks ill rewrite the whole thing wont take that long tbh
kinda..... i think being able to discriminate at any level would solve a lot of my problems
and i feel like i understand unions better than generics
like
lemme just try it out
yeah. please do
this would also play a lot nicer with zod
i have a better feeling about this
GitHub
GitHub - ethanniser/helping-tom-with-ts at union
Contribute to ethanniser/helping-tom-with-ts development by creating an account on GitHub.
zero generics now
looking
refresh i forgot to push
and you can add discrimination whereever you need to discriminate
haha np
but for now I just left it only where the data is actually different
yeah. i like this more
this will be way easier to zod
the only thing i can think of
which isnt really a deal breaker
but its that technically the array types could contain mixed valiues
like:
rounds could have a combo of annotated and numeric rounds
but i thinkt thats not that big of a deal
thats pretty hard for the code to screw up
ah yeah. that could work
yeah that should totally work
especially without generics now I would really strongly consider going zod first and inferring all your types from your schemas
https://transform.tools/typescript-to-zod
this will make the transition pretty easy
TypeScript to Zod Schema
An online playground to convert TypeScript to Zod Schema
oh wow lol that would help a lot
ive been doing it manually
its not a huge deal because the way my code works all the validators need to match or else there will be type errors everywhere
yea its pretty sweet
yea I mean whatever works best for you
but this will definitely simplify it and ive been meaning to make zod the one dependency of the lib anyway
nothing wrong with dependencies
this site needs to be dark mode though
its 4am
im blind
oh it has it
awesome
everything is right with the world
well
are we done?
i think so. i should go to bed but im gunna try this as soonas i get up tomorrow
fr though tysm
of course glad I could help
can i like venmo you for at least a coffee or something?
your good man im serious
this was fun
alright
well... thanks a lot
yep best of luck with the app
appreciate it
have a good night
cheers
@whatplan (Rustular Devrel) thanks again for all the help last night
unfortunately im still having some issues. so far most of them look like this:
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
Matt Pocock (@mattpocockuk)
Time to celebrate - an EXTREMELY annoying, long-standing bug will be fixed in TypeScript 5.2.
Finally, using array methods on a union of arrays will no longer break!
Likes
659
Twitter
yea this is just a weird ts thing
aparently getting fixed next release
lol....
that was literally 2 days ago
oy. im kinda starting to lean towards just doing it all with unit tests
which i hate
i think the generic approach would have been great if only i could say
if TScoring === NumericScoreSystem
well also it still would break zod
the union thing works but again i have this issue that the differences at the lowest level of my components require the typing to be everywhere
in both my code and in the actual data itself
like in a tournament with 250 games, all of this typing will actually inflate the data by about 7%
which is annoyingi mean
its javascript
right but the model gets stored in the actual db
i mean still its like
bytes
I get its not as clean
a couple additional columns wont kill your app
not saying there isnt a better option
there might be
yeah. and 'as clean' is relative to something we havent figured out yet
which may or may not exist
I gotta do some work but ill check back tonight
yeah thanks for all the hlp last night
np
im gunna play around with it and see if i can come up with something tonight. maybe some combo of the stuff we tried