|
|
|
|
|
by eyelidlessness
2015 days ago
|
|
Let me give you a head start: what you want in TypeScript is type guards. You pair runtime validation with type refinement, and if you start with good composable primitives, you can declare runtime structures and get compile time safety for “free”. That’s how io-ts and the various other libraries in that space work. If you combine that with generics on whatever function defines your routes, you can validate API boundaries and business logic boundaries in the compiler. This applies to any boundary (I could link to Gary Bernhardt on that topic but I’m on phone), and you can do it as generically (like you said, io-ts) or specifically (take a look at zapatos for type safe SQL) as you like. For HTTP boundaries, I’ll drop some pseudocode of how I’m doing this in my library: type HTTPVerb =
| 'delete'
| 'get'
| 'head'
| 'options'
| 'patch'
| 'post'
| 'put'
| 'trace';
interface HTTPRequest {
readonly method: HTTPVerb;
// ...
}
type HTTPRequestDecoder<I> = (request: HTTPRequest) => I;
type HTTPResponseEncoder<O, R> = (output: O) => R;
type HTTPRoute = IrrelevantForThisPost;
type Handler<I, O> = (input: I) => O;
type HTTPRouteFactory = <I, O, R>(
path: string,
decoder: HTTPRequestDecoder<I>,
handler: Handler<I, O>,
encoder: HTTPResponseEncoder<O, R>
) => HTTPRoute;
type Route = {
readonly [K in HTTPVerb]: HTTPRouteFactory;
};
|
|
Looks like I need to incorporate encoders/decoders into my scheme. I might just steal that code outright.
Thanks for the Zapatos rec, that looks perfect.
fyi, my current generic method sigs looks like:
Dropping all of these typing shenanigans and going back to Elixir/Phoenix is always half-tempting, but I will soldier on for now...