Skip to content

Instantly share code, notes, and snippets.

@reverofevil
Created December 1, 2019 23:10
Show Gist options
  • Save reverofevil/6b3ad32b1f07177de262d84a4a87cd49 to your computer and use it in GitHub Desktop.
Save reverofevil/6b3ad32b1f07177de262d84a4a87cd49 to your computer and use it in GitHub Desktop.
io-ts done right: arbitrarily handled runtime type information (overloading) for typescript
// hkt.ts
interface TagToHkt<T> { }
export type Tag = keyof TagToHkt<any>
export type Apply<F extends Tag, T> = TagToHkt<T>[F]
// util.ts
export type Intersect<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
export type IndexForSure<O, K extends string> = O extends { [K1 in K]: any } ? O[K] : never
export type IntersectValues<O> = IndexForSure<
Intersect<{ [K in keyof O]: { foo: O[K] } }[keyof O]>,
"foo"
>
type Json = string | number | boolean | null | Array<Json> | { [K: string]: Json }
function die(cond: boolean, context: string[], message: string): asserts cond {
throw new Error(`At ${context.join('.')}: ${message}`);
}
// rtti.ts
/**
Interface of interpreter of runtime type information
@param F type of container of interpreter's result
*/
export interface RttiInterpreter<F extends Tag> {
string: Apply<F, string>;
number: Apply<F, number>;
boolean: Apply<F, boolean>;
null: Apply<F, null>;
literal: <T extends string | number | boolean | null>(t: T) => Apply<F, T>;
array: <T>(p: Apply<F, T>) => Apply<F, T[]>;
object: <O>(ps: {[K in keyof O]: Apply<F, O[K]>}) => Apply<F, O>;
maybe: <T>(p: Apply<F, T>) => Apply<F, T | undefined>;
union: <T>(...ps: T[]) => Apply<F, T>;
intersection: <O>(ps: { [K in keyof O]: Apply<F, O[K]> }) => Apply<F, IntersectValues<O>>;
disjointUnion: <K extends string, O extends Record<any, object>>(
key: K,
ps: {[L in keyof O]: Apply<F, O[L]>},
) => Apply<F, {
[L in keyof O]: { [K1 in K]: L } & O[L]
}[keyof O]>;
}
// parser.ts
export type Parser = 'Parser'
/* declare module './hkt.ts' { */
interface TagToHkt<T> {
Parser: (json: Json | undefined, context: string[]) => T;
}
/* } */
export const parseAlgebra: RttiInterpreter<Parser> = {
literal: x => (json, context) => {
die(
x !== json,
context,
`Wrong value: '${json}' instead of '${x}'`,
);
return x;
},
// ...
};
export const parse = <R>(
type: (alg: RttiInterpreter<Parser>) => Apply<Parser, R>,
json: Json,
): R => {
return type(parseAlgebra)(json, []);
};
// example-types.ts
export const Foo = <F extends Tag>(t: RttiInterpreter<F>) => t.object({
id: t.string,
type: t.string,
text: t.string,
});
export type Foo = ReturnType<ReturnType<typeof Foo>>;
export const Bar = <F extends Tag>(t: RttiInterpreter<F>) => t.intersection({
main: t.object({
id: t.string,
type: t.string,
text: t.string,
}),
union: t.disjointUnion('type', {
'this': t.object({
foo: t.array(Foo(t)),
}),
'that': t.object({
bar: t.array(Foo(t)),
}),
}),
});
export type Bar = ReturnType<ReturnType<typeof Bar>>;
// example.ts
const result = parse(Foo, {});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment