// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
// server.ts
function appGet(
url: string,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: string): Promise<any>;
// client.ts
declare function fetch(url: string): Promise<any>;
// client-usage.ts
const users: User[] = await fetch("/users")
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch(42)
const users: User[] = await fetch({ users: true})
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch(42)
// => ERROR: number is not assignable to string
const users: User[] = await fetch({ users: true})
// => ERROR: object is not assignable to string
❌
✅
✅
✅ compiles
❔ it runs?
TypeError: failed to fetch
✅ compiles
❌ it does not run
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
HAVE A GREAT COFFE BREAK!
IT'S NOT SO EASY...
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
✅
✅
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
// server.ts
function appGet(
url: string,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: string): Promise<any>;
"Sylvester is a cat, a cat is not Sylvester"
"URL are strings, any string is not a valid URL"
URL
string
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
type URL = "/users" | "/todos"
// server.ts
function appGet(
url: URL,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: URL): Promise<any>;
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch("/user")
// => ERROR: "/user" is not assignable to "/users" | "/todos"
✅
✅
❌
✅ compiles
❔ it runs?
title is undefined
✅ compiles
❌ it does not run
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
✅
✅
🐰 "Uhm, what's up doc?"
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
type URL = "/users" | "/todos"
// server.ts
function appGet(
url: URL,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: URL): Promise<any>;
// server.ts
function appGet(
url: "/users",
fn: (req: Request) => Promise<User[]>): void;
function appGet(
url: "/todos",
fn: (req: Request) => Promise<Todo[]>): void;
/* ... */
// client.ts
function fetch(url: "/users"): Promise<User[]>;
function fetch(url: "/todos"): Promise<Todo[]>;
/* ... */
// client-usage.ts
const users = await fetch("/users")
const todos = await fetch("/todos")
const todos = await fetch("/users")
😍 "Look Mum! No types!"
// server.ts
function appGet(
url: "/users",
fn: (req: Request) => Promise<User[]>): void;
function appGet(
url: "/todos",
fn: (req: Request) => Promise<Todo[]>): void;
function appGet(
url: "/tacos",
fn: (req: Request) => Promise<Taco[]>): void;
// client.ts
function fetch(url: "/users"): Promise<User[]>;
function fetch(url: "/todos"): Promise<Todo[]>;
function fetch(url: "/tacos"): Promise<Taco[]>;
on v8 in the browser or NodeJS
on the type-system in the editor or the compiler
My editor is yelling at me
type UsersURL = "/users"
// correct assignment
const users: UsersURL = "/users"
// incorrect assignment
const users: UsersURL = "/todos"
// string is not /users statically
const stringVar: string = "/users"
const users: UsersURL = stringVar
type User = {
id: number,
name: string
surname: string
age: number
prefix: "mr." | "mss."
}
type User = {
id: number,
name: string
surname: string
age: number
prefix: "mr." | "mss."
}
type A = keyof User;
// => id | name | surname | age | prefix
type A = User["name"];
// => string
type B = User["age"];
// => number
type C = User["name" | "age"];
// => string | number
type RoutesMap = {
"/users": User[],
"/todos": Todo[]
}
type URL = keyof RoutesMap
type RoutesMap = {
"/users": User[],
"/todos": Todo[]
}
type URL = keyof RoutesMap
type GetResponseType<U extends URL> = RoutesMap[U]
type A = GetResponseType<"/users">
// => User[]
type B = GetResponseType<"/todos">
// => Todo[]
type C = GetResponseType<"/batman">
// => ERROR: batman is not a valid URL
// server.ts
function appGet<U extends URL>(
url: U,
fn: (req: Request) => Promise<GetResponseType<U>>
): void;
// client.ts
function fetch<U extends URL>(url: U): Promise<GetResponseType<U>>;
// client-usage.ts
const users = await fetch("/users")
// => User[]
const todos = await fetch("/todos")
// => Todo[]
const todos = await fetch("/users")
// => User[]
😍 "Look Mum! 25% of code reduction!"
// client-usage.ts
const john = await fetch("/users/1")
const mattia = await fetch("/users/2")
const doe = await fetch("/users/3")
const mattiaPhoto = await fetch("/users/2/photos/42")
🤔 "We cannot encode any possible string"
const john = await fetch("/users/{id}")(1)
// => User
const mattia = await fetch("/users/{id}")(2)
// => User
const mattiaPhoto = await fetch("/users/{id}/photos/{photo_id}")(2, 42)
// => Photo
type RoutesMap = {
"/users":
{ args: [], response: User[] },
"/users/{id}":
{ args: [number], response: User },
"/users/{id}/photos/{photo_id}":
{ args: [number, number], response: Photo}
}
/* ... */
type GetResponseType<U extends URL> =
RoutesMap[U]["response"]
type GetRouteArgs<U extends URL> =
RoutesMap[U]["args"]
A TRIBUTE TO K🎹
HOME
WORK
BED
train
train
sleep
wakeup
type DailyMachine = {
work: {
train: "home"
},
home: {
train: "work",
sleep: "bed"
},
bed: {
wakeup: "home"
}
}
type DailyMachine = {
work: { /* ... */ },
home: { /* ... */ },
bed: { /* ... */ }
}
type MachineStates = keyof DailyMachine
// => work | home | bed
type DailyMachine = { /* ... */
home: {
train: "work",
sleep: "bed"
}, /* ... */
}
type GetAvailableEvents<
S extends MachineStates
> = keyof DailyMachine[S]
type Test1 = GetAvailableEvents<"home">
// => sleep | train
type DailyMachine = { /* ... */
home: {
train: "work",
sleep: "bed"
}, /* ... */
}
type GetNextState<
C extends MachineStates,
E extends GetAvailableEvents<C>
> = DailyMachine[C][E]
type Test1 = GetNextState<"home", "sleep">
// => bed
declare function transition<
S extends MachineStates,
E extends GetAvailableEvents<S>
>(
state: S,
event: E
): GetNextState<S, E>;
const newState1 = transition("bed", "wakeup")
// => home
const newState2 = transition("home", "train")
// => work
const newState3 = transition("work", "sleep")
// => CompileError
JUST TYPES! :D
HOW FAR CAN TYPE-LEVEL LOGIC GO?
// typelevel-booleans.ts
type TLTrue = "T"
type TLFalse = "F"
type TLBoolean = TLTrue | TLFalse
const a: TLTrue = "T" // => OK
const a: TLTrue = true // => ERROR
type TLIf<A extends TLBoolean, IfTrue, IfFalse> = {
"T": IfTrue,
"F": IfFalse
}[A]
type Test1 = TLIf<TLTrue, 1, 2> // => 1
type Test2 = TLIf<TLFalse, 1, 2> // => 2
// my first program
type TLSampleFunction<
AtAlicante extends TLBoolean
> = TLIf<
AtAlicante,
"Hello React Alicante!",
"I'm not done with the slides yet."
>
type TestA = TLSampleFunction<TLTrue>
// => Hello React Alicante!
type TestB = TLSampleFunction<TLFalse>
// => I'm not done with the slides yet.
type TLAnd<A extends TLBoolean, B extends TLBoolean> = {
"F": TLFalse,
"T": B
}[A]
type Test1 = TLAnd<"T", "F"> // => F
type Test2 = TLAnd<"T", "T"> // => T
type Test3 = TLAnd<"F", "T"> // => F
type Test4 = TLAnd<"F", "F"> // => F
type TLOr<A extends TLBoolean, B extends TLBoolean> = {
"T": TLTrue,
"F": B
}[A]
type Test5 = TLOr<"T", "F"> // => T
type Test6 = TLOr<"T", "T"> // => T
type Test7 = TLOr<"F", "T"> // => T
type Test8 = TLOr<"F", "F"> // => F
(ANY INTEGER POSITIVE NUMBER)
type TLZero = {
isZero: TLTrue,
prev: never
}
type TLOne = {
isZero: TLFalse,
prev: TLZero
}
type TLTwo = {
isZero: TLFalse,
prev: TLOne
}
type TLNext<N extends TLNat> = {
isZero: TLFalse,
prev: N
}
type TLOne = TLNext<TLZero>
type TLTwo = TLNext<TLOne>
type TLThree =
TLNext<TLNext<TLNext<Zero>>>
// some utilities
type IsZero<N extends TLNat> = N["isZero"]
type Test1 = IsZero<TLZero> // => "T"
type Test2 = IsZero<TLOne> // => F
// some utilities
type TLPrev<N extends TLNat> = N["prev"]
type Test1 = TLPrev<TLTwo> // => TLOne
type Test2 = TLPrev<TLZero> // => never
type SeemsSuspicious<N extends TLNat> = {
"F": { NaNa: SeemsSuspicious<TLPrev<N>>},
"T": "Batman!"
}[IsZero<N>]
type GuessWhat = SeemsSuspicious<TLEight>
type GuessWhat = {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: "Batman!";
};
};
};
};
};
};
};
}
@MATTIAMANZATI