Slide 1

Slide 1 text

Runtime Types in TypeScript

Slide 2

Slide 2 text

Type Checking • either statically – compiler – static analyzer • or at runtime

Slide 3

Slide 3 text

Type Checking by TypeScript • either statically – compiler – static analyzer • or at runtime

Slide 4

Slide 4 text

Type Checking by Java • either statically – compiler – static analyzer • or at runtime

Slide 5

Slide 5 text

TypeScript vs. Java // TypeScript var s = value as string // Java var s = (String) value;

Slide 6

Slide 6 text

const postJson = async (path: string, body: any) => { const response = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) return await response.json() } const response = await postJson(...) response. // no code completion

Slide 7

Slide 7 text

const postJson = async (path: string, body: any) => { const response = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson(...) as SignInResponse response. // code completion works

Slide 8

Slide 8 text

const postJson = async (...): Promise => { const response = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson(...) response. // code completion works & it looks FANCY

Slide 9

Slide 9 text

const postJson = async (...): Promise => { const response = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson(...) if ( typeof response === 'object' && response !== null && 'token' in response && typeof response.token === 'string' ) { ... }

Slide 10

Slide 10 text

function hasOwnProperty< X extends {}, Y extends PropertyKey >(obj: X, prop: Y): obj is X & Record { return obj.hasOwnProperty(prop) }

Slide 11

Slide 11 text

if ( typeof response === 'object' && response !== null && hasOwnProperty(response, 'token') && typeof response.token === 'string' ) { response. // code completion works }

Slide 12

Slide 12 text

type SignInResponse = { token: string } if ( typeof response === 'object' && response !== null && hasOwnProperty(response, 'token') && typeof response.token === 'string' ) { response. // code completion works }

Slide 13

Slide 13 text

Type World Runtime World

Slide 14

Slide 14 text

Reflection

Slide 15

Slide 15 text

Reflection https://github.com/microsoft/TypeScript/issues/3628

Slide 16

Slide 16 text

Type World Runtime World

Slide 17

Slide 17 text

typeof operator

Slide 18

Slide 18 text

Runtime Type Design type RunTimeType = (input: unknown) => input is T const stringType = (input: unknown): input is string => { return typeof input === 'string' }

Slide 19

Slide 19 text

Runtime Type Design type RunTimeType = (input: unknown) => asserts input is T const stringType = (input: unknown): asserts input is string => { if (typeof input !== 'string') throw new Error() }

Slide 20

Slide 20 text

Runtime Type Design type RunTimeType = (input: unknown) => | { ok: true, value: T } | { ok: false, reason: string } const stringType = (input: unknown) => { return typeof input === 'string' ? { ok: true, value: input } : { ok: false, reason: "not a string" } }

Slide 21

Slide 21 text

Runtime Type Design type RunTimeType = (input: unknown) => T // or throw Error in case of failure const stringType = (input: unknown) => { if (typeof input !== 'string') throw new Error(...) return input }

Slide 22

Slide 22 text

Minimal Implementation const string = (input: unknown) => { if (typeof input !== 'string') throw new Error() return input }

Slide 23

Slide 23 text

Minimal Implementation const number = (input: unknown) => { if (typeof input !== 'number') throw new Error() return input }

Slide 24

Slide 24 text

Minimal Implementation const literal = (value: V): Type => { return (input: unknown) => { if (input !== value) throw new Error() return value } }

Slide 25

Slide 25 text

Minimal Implementation const array = (inner: RunTimeType) => { return (input: unknown): T[] => { if (!Array.isArray(input)) throw new Error() return input.map(v => inner(v)) } }

Slide 26

Slide 26 text

Minimal Implementation const object = >>(inner: T) => { return (input: unknown): { [P in keyof T]: ReturnType } => { if (input === null || typeof input !== 'object') throw new Error() const out: { [P in keyof T]: ReturnType } = {} as any for (const k in inner) { out[k] = inner[k]((input as any)[k]) } return out } }

Slide 27

Slide 27 text

Usage const ArticleType = object({ id: number, title: string, category: object({ id: number, name: string, }), tags: array(object({ id: number, label: string, })), })

Slide 28

Slide 28 text

Usage const ResponseRuntimeType = object({ token: string }) type ResponseStaticType = { token: string }

Slide 29

Slide 29 text

Usage const ResponseRuntimeType = object({ token: string }) type ResponseStaticType = ReturnType

Slide 30

Slide 30 text

Usage const SignInResponse = object({ token: string }) type SignInResponse = ReturnType

Slide 31

Slide 31 text

const postJson = async (...): Promise => { const response = await fetch(path, { ... }) return await response.json() } const SignInResponse = object({ token: string }) const response = SignInResponse(await postJson(...)) response. // code completion works & it looks FANCY

Slide 32

Slide 32 text

New Power Level const IsDivisibleBySeven = (input: unknown) => { const n = number(input) if (n % 7 !== 0) throw new Error() return n }

Slide 33

Slide 33 text

Existing Implementations • https://github.com/gcanti/io-ts • https://github.com/colinhacks/zod • https://github.com/pelotom/runtypes • ...and many more, unfortunatelly • https://www.typescriptneedstypes.com/

Slide 34

Slide 34 text

Jan Tvrdík @contember