forked from jakelazaroff/bkjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
481 lines (424 loc) · 13.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
<html>
<head>
<title>Building Intuitive APIs with TypeScript</title>
<link rel="stylesheet" href="/bkjs/reveal.css" />
<link rel="stylesheet" href="/bkjs/code.css" />
<link rel="stylesheet" href="/bkjs/style.css" />
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>Building Intuitive APIs with TypeScript</h1>
<p>by <span id="typewriter">Jake Lazaroff</span></p>
<p><a href="https://jake.nyc">jake.nyc</a></p>
</section>
<section>
<p>my job: <a href="https://parsecgaming.com">Parsec</a></p>
<img src="/bkjs/parsec.svg" alt="Parsec" />
</section>
<section>
<p>
my band:
<a href="https://babygotbacktalk.bandcamp.com">babygotbacktalk</a>
</p>
<img src="/bkjs/bgbt.jpg" alt="babygotbacktalk" />
</section>
<section>
<h3>Why?</h3>
<div class="side-by-side">
<img src="/bkjs/tool.png" />
<div>
<p>
The <strong>I</strong> in "API" stands for
<strong>interface</strong>!
</p>
</div>
</div>
</section>
<section>
<h3>What's TypeScript?</h3>
<blockquote>
<p>
TypeScript is a statically typed superset of JavaScript that
compiles to plain JavaScript.
</p>
</blockquote>
</section>
<section>
<h3>What's "statically typed"?</h3>
<p>
Everything in a program has a type, like "number" or "function that
returns a string".
</p>
<p>
With static types, we can make sure a program's types work together
without even running it.
</p>
</section>
<section>
<pre><code data-trim>
function square(x: number) {
return x * x;
}
</code></pre>
<pre class="fragment"><code data-trim>
square(2);
</code></pre>
<pre class="fragment"><code data-trim data-noescape>
square(<mark class="type-error">"how YOU doin"</mark>);
</code></pre>
</section>
<section>
<h3>The gameplan:</h3>
<p>
We're gonna go on a whirlwind tour of some cool features of
TypeScript and a nifty design pattern.
</p>
</section>
<section>
<h2>Feature 1: Unions and Intersections</h2>
<p>
Unions and intersections are ways of combining different types into
one.
</p>
<p>
In a union, an object of the new type can have properties of
<strong>either</strong> type. In an intersection, an object of the
new type will have properties of <strong>both</strong> types.
</p>
</section>
<section>
<div class="side-by-side">
<div>
<h3>Unions</h3>
<pre><code data-trim data-noescape>
let foo: string | number;
foo = "test";
foo = 123;
<mark class="type-error">foo</mark> = false;
</code></pre>
</div>
<div>
<h3>Intersections</h3>
<pre><code data-trim data-noescape>
let obj: { foo: string } & { bar: number };
obj = {
foo: "moo",
bar: 2
};
<mark class="type-error">obj</mark> = { foo: "moo" };
</code></pre>
</div>
</div>
</section>
<section>
<h2>Feature 2: Generics</h2>
<p>
Generics let you write your program so that it doesn't know which
types it's using ahead of time.
</p>
<p>If types were functions, generics would be their arguments.</p>
</section>
<section>
<pre><code data-trim>
function concat(x: any[], y: any[]) {
return x.concat(y);
}
</code></pre>
<pre class="fragment"><code data-trim>
const a = [1, 2, 3]; // number[]
const b = [4, 5, 6]; // number[]
const result = concat(a, b);
result; // any[] (we lost the type!)
</code></pre>
</section>
<section>
<pre><code data-trim>
function concat<T>(x: T[], y: T[]) {
return x.concat(y);
}
</code></pre>
<pre class="fragment"><code data-trim>
const a = [1, 2, 3]; // number[]
const b = [4, 5, 6]; // number[]
const result = concat(a, b);
result; // number[] (the type is safe!)
</code></pre>
</section>
<section>
<pre><code data-trim>
function concat<T extends { concat(x: T): T }>(x: T, y: T) {
return x.concat(y);
}
</code></pre>
<pre class="fragment"><code data-trim>
const a = [1, 2, 3]; // number[]
const b = [4, 5, 6]; // number[]
const result = concat(a, b);
result; // number[] (works for numbers…)
</code></pre>
<pre class="fragment"><code data-trim>
const a: string = "foo"; // string
const b: string = "bar"; // string
const result = concat(a, b);
result; // string (…and strings!)
</code></pre>
</section>
<section>
<h2>Feature 3: Mapped types</h2>
<p>Mapped types let you transform one type into another.</p>
<p>
They're kinda like <code>map</code> in normal JavaScript, but for
types.
</p>
</section>
<section>
<pre><code data-trim>
function stringify<T>(x: T): { [K in keyof T]: string } {
// some code that changes all the object keys to strings
}
</code></pre>
<pre class="fragment"><code data-trim>
const obj = {
foo: 1,
bar: 2
};
const result = stringify(obj);
result; // { foo: string; bar: string; }
</code></pre>
</section>
<section>
<h2>Design pattern: Builder</h2>
<p>
The Builder decouples the creation of an object from its internal
representation.
</p>
<p>
It does this by using a separate class to create it, step-by-step.
</p>
</section>
<section>
<div class="side-by-side">
<pre><code data-trim>
class Car {
color: string;
doors: number;
}
</code></pre>
<pre><code data-trim>
class CarBuilder {
private car = new Car();
setColor(color: string) {
this.car.color = color;
}
setDoors(doors: number) {
this.car.doors = doors;
}
private getCar() {
return this.car;
}
}
</code></pre>
</div>
</section>
<section>
<pre><code data-trim>
const car = new CarBuilder()
.setColor('red')
.setDoors(2)
.getCar();
</code></pre>
</section>
<section>
<h2>Putting it all together</h2>
<p>
To illustrate how these all work together, we're going to create a
simple SQL query builder.
</p>
</section>
<section>
<h3>What it looks like:</h3>
<div class="side-by-side">
<pre><code data-trim>
interface Schema {
users: {
id: number;
name: string;
age: number;
}
jobs: {
id: number;
name: string;
salary: number;
}
}
</code></pre>
<pre><code data-trim>
const query = new Query<Schema>('users');
const result = query
.select("id")
.select("name")
.exec();
</code></pre>
</div>
</section>
<section>
<p>
First, we make a Query class that knows the database schema and the
table from which it's selecting.
</p>
<pre><code data-trim>
class Query<Schema, Table extends keyof Schema> {
private table: Table;
constructor(table: Table) {
this.table = table;
}
}
</code></pre>
</section>
<section>
<p>
Next, we add another generic to the class with the type of the
queried result. We also need to keep track of which columns we're
selecting.
</p>
<pre><code data-trim>
class Query<Schema, Table extends keyof Schema, Result = {}> {
// ...
private columns: Array<keyof Schema[Table]> = []:
constructor(data: Schema, columns: Array<keyof Schema[Table]>) {
// ...
this.columns = columns;
}
}
</code></pre>
</section>
<section>
<p>
Now, the secret sauce: every step of the builder, we
<strong>intersect</strong> the result type with the selected key.
</p>
<pre><code data-trim>
class Query<Schema, Table extends keyof Schema, Result = {}> {
// ...
select<Key extends keyof Schema[Table]>(key: Key) {
type NextResult = Result & { [K in Key]: Schema[Table][K] };
return new Query<Schema, Table, NextResult>(this.table, [
...this.columns,
key
]);
}
}
</code></pre>
</section>
<section>
<p>
Finally, we need a method to execute the query.
</p>
<pre><code data-trim>
class Query<Schema, Table extends keyof Schema, Result = {}> {
// ...
exec(): Result[] {
const columns = this.columns.join(', ');
const query = `SELECT ${columns} FROM ${this.table};`;
// some code actually running the query
}
}
</code></pre>
</section>
<section>
<div class="side-by-side">
<pre><code data-trim>
interface Schema {
users: {
id: number;
name: string;
age: number;
}
jobs: {
id: number;
name: string;
salary: number;
}
}
</code></pre>
<pre><code data-trim>
const query = new Query<Schema>('users');
const result = new
.select("id")
.select("name")
.exec();
</code></pre>
</div>
</section>
<section>
<h2>Why is it intuitive?</h2>
</section>
<section>
<p>It knows the type of the result!</p>
<pre><code data-trim>
const foo = new Query<Schema>('users')
.select("id")
.select("name")
.exec();
foo; // Array<{ id: string; name: string }>
const bar = new Query<Schema>('jobs')
.select("salary")
.exec();
bar; // Array<{ salary: number }>
const baz = new Query<Schema>('users').exec();
baz; // Array<{}>
</code></pre>
</section>
<section>
<p>It won't let you select columns that don't exist!</p>
<pre><code data-trim data-noescape>
const foo = new Query<Schema>('users')
.select(<mark class="type-error">"height"</mark>)
.result();
</code></pre>
<p>
(And if you use an editor that supports TypeScript, you'll get
autocomplete for the columns names!)
</p>
</section>
<section>
<h3>Extra credit</h3>
<ul>
<li>Disallow the names of already-selected columns!</li>
<li>Alias column names!</li>
<li>Join only tables with relationships!</li>
</ul>
</section>
<section>
<h2>Thanks!</h2>
</section>
</div>
</div>
<script src="/bkjs/reveal.js"></script>
<script>
Reveal.initialize({
dependencies: [
{ src: "/bkjs/highlight.js", async: true },
{ src: "/bkjs/typewriter.js", async: true }
]
});
Reveal.addEventListener("ready", () => {
new Typewriter("#typewriter", {
strings: [
"Jake Lazaroff",
"Jark Legsaresoft",
"Jake the Snake",
"Jerk Loseroff",
"Jake from State Farm",
"Jank Laseroff",
"Joke Lobstercough"
],
autoStart: true,
loop: true
});
});
</script>
</body>
</html>