It’s just for fun, but the explanation of how conditional types work might be useful. (I have never used a conditional type in a real program, though.)
It’s just for fun, but the explanation of how conditional types work might be useful.
(I have never used a conditional type in a real program, though.)
I’ve used them pretty frequently in a system meant to substitute for code generation. A number of other developers that worked with the system I built seemed to like it.
I’ve used them pretty frequently in a system meant to substitute for code generation. A number of other developers that worked with the system I built seemed to like it.
There's this type that I have used in many projects. /** * Converts a type into its post JSON serialization/de-serialization version. */ type JSONSerialized<T> = // We first check if we're looking...
There's this type that I have used in many projects.
/** * Converts a type into its post JSON serialization/de-serialization version. */typeJSONSerialized<T>=// We first check if we're looking at a primitive type. Primitives will be passed// through untouched by `JSON.parse(JSON.stringify(...))`, so `JSONSerialized` replicates// that behavior. The exact `T` type is output instead of `number` or `string` in case// the type is actually a union (ex: "foo" | "bar"). This way the union is preserved.Textendsnumber?T :
Textendsstring?T :
Textendsboolean?T :
Textendsnull?null:// If we aren't looking at a primitive, then we'll next check if we're working with an// array. If so we convert the array's element type using a recursive type call.Textends(inferElem)[]?JSONSerialized<Elem>[]:// Here's the magic that takes care of `Date`s. Some types have a `toJSON` function.// `JSON.stringify` will call this function if it doesn't already know what to do with// a value. By using `infer` we can dynamically extract the return type of a `toJSON()`// as defined on ANY type - including `Date.toJSON`.Textends{toJSON:()=>inferReturn}?Return:// Lastly, check if we're working with an Object and if so, iterate through its// key/value pairs - sending each value into a recursive call to `JSONSerialized`.Textends{[KeyinkeyofT]:T[Key]}?{[KeyinkeyofT]:JSONSerialized<T[Key]>}:never;
It's employed when importing HTTP response types from the back end code into the front end code. If your back end function returns something like:
You can't cast the HTTP response's natural any type to ReturnType<typeof handler> as that will be incorrect. After this has gone through JSON.stringify() -> JSON.parse() the timestamp field is an ISO formatted string, not a Date. So the type above walks through any response type and converts it to how it will look in the front end. This is used in concert with other TypeScript code which combined eliminates the need for something like GraphQL for HTTP client code generation. It's still REST - so not a true replacement for GraphQL. Although I prefer it that way.
Very cool! It seems like, by casting the result of JSON.parse() in this way, you're not verifying what's coming in on the wire? That is, it assumes that the front end and back end are synced to...
Very cool!
It seems like, by casting the result of JSON.parse() in this way, you're not verifying what's coming in on the wire? That is, it assumes that the front end and back end are synced to the same version of the schema. Maybe you check that some other way?
I use Zod and pay the performance penalty for verifying incoming data, which explains why this didn't come up for me.
Correct, nothing is verified. But the same thing can be said about your API server's interactions with the database. It's a problem that is handled well enough within applications you own both...
Correct, nothing is verified. But the same thing can be said about your API server's interactions with the database. It's a problem that is handled well enough within applications you own both sides of by being mindful of schema changes. That won't scale up to a ton of devs - but I haven't used this for such a situation. For what it's worth I haven't been bitten by this shortcoming. Any ephemeral synchronization issues will manifest in pretty much the same way as when using Zod. The user will see an error or a form won't submit. They refresh the page, the client updates, and then it works.
A Zod schema in a shared library - imported by the front and back end - would also be a pretty good solution.
Yes, usually reloading the page isn't that big a deal for sophisticated users, though it's annoying to lose work. (On Tildes, I lose work fairly often on phone and tablet, because visiting a heavy...
Yes, usually reloading the page isn't that big a deal for sophisticated users, though it's annoying to lose work. (On Tildes, I lose work fairly often on phone and tablet, because visiting a heavy web page in a different tab will evict the Tildes tab from memory.)
But for some users, every time something doesn't work, it's confusing, so it's good to at least have a decent error message explaining what to do.
My bad. The server side does verify (because if it didn't that would be terribly insecure). The same source of truth for the POST body verification is used for the type-level API interface...
My bad. The server side does verify (because if it didn't that would be terribly insecure). The same source of truth for the POST body verification is used for the type-level API interface validation. And when the body/query params fail to validate there is a specific signal for that which can be handled in forms.
But when the client reads data it does no verification and will eventually hit some kind of type error if the schemas are out of sync.
This is really fun, thanks for sharing. The logic and the implementation of more complex operators with basic elements gives me a sort of deja vu from past projects designing ALU's out of single...
This is really fun, thanks for sharing. The logic and the implementation of more complex operators with basic elements gives me a sort of deja vu from past projects designing ALU's out of single logic gates, like the NAND gate. It's fun to see the same types of ideas being used in really fun, different ways.
It’s just for fun, but the explanation of how conditional types work might be useful.
(I have never used a conditional type in a real program, though.)
I’ve used them pretty frequently in a system meant to substitute for code generation. A number of other developers that worked with the system I built seemed to like it.
Interesting. I’m imagining an interpreter. What sort of things do you do?
There's this type that I have used in many projects.
It's employed when importing HTTP response types from the back end code into the front end code. If your back end function returns something like:
You can't cast the HTTP response's natural
any
type toReturnType<typeof handler>
as that will be incorrect. After this has gone throughJSON.stringify() -> JSON.parse()
the timestamp field is an ISO formatted string, not aDate
. So the type above walks through any response type and converts it to how it will look in the front end. This is used in concert with other TypeScript code which combined eliminates the need for something like GraphQL for HTTP client code generation. It's still REST - so not a true replacement for GraphQL. Although I prefer it that way.Very cool!
It seems like, by casting the result of JSON.parse() in this way, you're not verifying what's coming in on the wire? That is, it assumes that the front end and back end are synced to the same version of the schema. Maybe you check that some other way?
I use Zod and pay the performance penalty for verifying incoming data, which explains why this didn't come up for me.
Correct, nothing is verified. But the same thing can be said about your API server's interactions with the database. It's a problem that is handled well enough within applications you own both sides of by being mindful of schema changes. That won't scale up to a ton of devs - but I haven't used this for such a situation. For what it's worth I haven't been bitten by this shortcoming. Any ephemeral synchronization issues will manifest in pretty much the same way as when using Zod. The user will see an error or a form won't submit. They refresh the page, the client updates, and then it works.
A Zod schema in a shared library - imported by the front and back end - would also be a pretty good solution.
Yes, usually reloading the page isn't that big a deal for sophisticated users, though it's annoying to lose work. (On Tildes, I lose work fairly often on phone and tablet, because visiting a heavy web page in a different tab will evict the Tildes tab from memory.)
But for some users, every time something doesn't work, it's confusing, so it's good to at least have a decent error message explaining what to do.
My bad. The server side does verify (because if it didn't that would be terribly insecure). The same source of truth for the POST body verification is used for the type-level API interface validation. And when the body/query params fail to validate there is a specific signal for that which can be handled in forms.
But when the client reads data it does no verification and will eventually hit some kind of type error if the schemas are out of sync.
This is really fun, thanks for sharing. The logic and the implementation of more complex operators with basic elements gives me a sort of deja vu from past projects designing ALU's out of single logic gates, like the NAND gate. It's fun to see the same types of ideas being used in really fun, different ways.