Repository: total-typescript/beginners-typescript-tutorial Branch: main Commit: e430c2da7ab0 Files: 57 Total size: 29.3 KB Directory structure: gitextract_69awz6n2/ ├── .github/ │ └── workflows/ │ ├── renovate-checks.yml │ └── section-repos.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── README.md ├── package.json ├── renovate.json ├── src/ │ ├── 01-number.problem.ts │ ├── 01-number.solution.ts │ ├── 02-object-param.problem.ts │ ├── 02-object-param.solution.1.ts │ ├── 02-object-param.solution.2.ts │ ├── 02-object-param.solution.3.ts │ ├── 03-optional-properties.problem.ts │ ├── 03-optional-properties.solution.ts │ ├── 04-optional-params.problem.ts │ ├── 04-optional-params.solution.ts │ ├── 05-assigning-types-to-variables.problem.ts │ ├── 05-assigning-types-to-variables.solution.ts │ ├── 06-unions.problem.ts │ ├── 06-unions.solution.ts │ ├── 07-arrays.problem.ts │ ├── 07-arrays.solution.1.ts │ ├── 07-arrays.solution.2.ts │ ├── 08-function-return-type-annotations.problem.ts │ ├── 08-function-return-type-annotations.solution.ts │ ├── 09-promises.problem.ts │ ├── 09-promises.solution.1.ts │ ├── 09-promises.solution.2.ts │ ├── 09-promises.solution.3.ts │ ├── 10-set.problem.ts │ ├── 10-set.solution.ts │ ├── 11-record.problem.ts │ ├── 11-record.solution.1.ts │ ├── 11-record.solution.2.ts │ ├── 11-record.solution.3.ts │ ├── 12-typeof-narrowing.problem.ts │ ├── 12-typeof-narrowing.solution.ts │ ├── 13-catch-blocks.problem.ts │ ├── 13-catch-blocks.solution.1.ts │ ├── 13-catch-blocks.solution.2.ts │ ├── 13-catch-blocks.solution.3.ts │ ├── 14-extends.problem.ts │ ├── 14-extends.solution.ts │ ├── 15-intersection.problem.ts │ ├── 15-intersection.solution.ts │ ├── 16-omit-and-pick.problem.ts │ ├── 16-omit-and-pick.solution.1.ts │ ├── 16-omit-and-pick.solution.2.ts │ ├── 17-function-types.problem.ts │ ├── 17-function-types.solution.1.ts │ ├── 17-function-types.solution.2.ts │ ├── 18-function-types-with-promises.problem.ts │ ├── 18-function-types-with-promises.solution.ts │ └── helpers/ │ └── type-utils.ts ├── tsconfig.json └── vite.config.mts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/renovate-checks.yml ================================================ name: Renovate Checks on: push: branches: - "renovate/**" jobs: build: runs-on: ubuntu-latest steps: - name: Checkout Main uses: actions/checkout@v4 with: ref: main path: repo - name: Install Dependencies in Main run: (cd repo && npm install) - name: Create Snapshot In Main run: (cd repo && npx tt-cli take-snapshot ./snap.md) - name: Copy Snapshot To Outer Directory run: mv repo/snap.md ./snap.md - name: Delete Main Directory run: rm -rf repo - name: Checkout Branch uses: actions/checkout@v4 with: path: repo - name: Install Dependencies in Branch run: (cd repo && npm install) - name: Move Snapshot To Branch run: mv ./snap.md repo/snap.md - name: Compare Snapshot In Branch run: (cd repo && npx tt-cli compare-snapshot ./snap.md) ================================================ FILE: .github/workflows/section-repos.yml ================================================ name: Create Section Repos on: push: branches: - "main" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20.x - run: git config --global user.email "total-typescript@bot.com" - run: git config --global user.name "Total TypeScript Bot" - run: npx @total-typescript/exercise-cli@latest create-section-repos env: GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ node_modules tsconfig.temp.json dist *.tsbuildinfo *.prompt.* .vscode/*.code-snippets ================================================ FILE: .vscode/settings.json ================================================ { "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, "github.copilot.enable": { "*": false, }, "explorer.sortOrder": "mixed", } ================================================ FILE: README.md ================================================ beginner typescript tutorial ## Quickstart Take the course on [Total TypeScript](https://totaltypescript.com/tutorials/beginners-typescript). There, you'll find: - Video explanations for each problem and solution - Transcripts - Text explanations - A built-in Stackblitz editor ```sh # Installs all dependencies npm install # Asks you which exercise you'd like to run, and runs it npm run exercise ``` ## How to take the course You'll notice that the course is split into exercises. Each exercise is split into a `*.problem` and a `*.solution`. To take an exercise: 1. Run `npm run exercise` 2. Choose which exercise you'd like to run. This course encourages **active, exploratory learning**. In the video, I'll explain a problem, and **you'll be asked to try to find a solution**. To attempt a solution, you'll need to: 1. Check out [TypeScript's docs](https://www.typescriptlang.org/docs/handbook/intro.html). 1. Try to find something that looks relevant. 1. Give it a go to see if it solves the problem. You'll know if you've succeeded because the tests will pass. **If you succeed**, or **if you get stuck**, unpause the video and check out the `*.solution`. You can see if your solution is better or worse than mine! ## Acknowledgements Say thanks to Matt on [Twitter](https://twitter.com/mattpocockuk) or by joining his [Discord](https://discord.gg/8S5ujhfTB3). Consider signing up to his [Total TypeScript course](https://totaltypescript.com). ## Reference ### `npm run exercise` Alias: `npm run e` Open a prompt for choosing which exercise you'd like to run. ================================================ FILE: package.json ================================================ { "name": "beginners-typescript", "version": "1.0.0", "type": "module", "main": "index.js", "author": "Matt Pocock ", "license": "GPL-3.0", "devDependencies": { "@total-typescript/exercise-cli": "^0.11.0", "@total-typescript/tsconfig": "^1.0.3", "@types/node": "^18.6.5", "cross-fetch": "^3.1.5", "jsdom": "^25.0.0", "typescript": "^5.4.5", "vite-tsconfig-paths": "^5.0.0", "vitest": "^2.0.0" }, "scripts": { "exercise": "tt-cli run", "e": "npm run exercise", "solution": "tt-cli run --solution", "s": "npm run solution", "prepare": "tt-cli prepare-stackblitz", "e-01": "tt-cli run 01", "s-01": "tt-cli run 01 --solution", "e-02": "tt-cli run 02", "s-02": "tt-cli run 02 --solution", "e-03": "tt-cli run 03", "s-03": "tt-cli run 03 --solution", "e-04": "tt-cli run 04", "s-04": "tt-cli run 04 --solution", "e-05": "tt-cli run 05", "s-05": "tt-cli run 05 --solution", "e-06": "tt-cli run 06", "s-06": "tt-cli run 06 --solution", "e-07": "tt-cli run 07", "s-07": "tt-cli run 07 --solution", "e-08": "tt-cli run 08", "s-08": "tt-cli run 08 --solution", "e-09": "tt-cli run 09", "s-09": "tt-cli run 09 --solution", "e-10": "tt-cli run 10", "s-10": "tt-cli run 10 --solution", "e-11": "tt-cli run 11", "s-11": "tt-cli run 11 --solution", "e-12": "tt-cli run 12", "s-12": "tt-cli run 12 --solution", "e-13": "tt-cli run 13", "s-13": "tt-cli run 13 --solution", "e-14": "tt-cli run 14", "s-14": "tt-cli run 14 --solution", "e-15": "tt-cli run 15", "s-15": "tt-cli run 15 --solution", "e-16": "tt-cli run 16", "s-16": "tt-cli run 16 --solution", "e-17": "tt-cli run 17", "s-17": "tt-cli run 17 --solution", "e-18": "tt-cli run 18", "s-18": "tt-cli run 18 --solution" }, "dependencies": { "@types/express": "^4.17.13", "express": "^4.18.1", "zod": "^3.17.10" } } ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:base"], "packageRules": [ { "packagePatterns": ["*"], "excludePackagePatterns": [ "typescript", "vitest", "jsdom", "prettier", "vite-tsconfig-paths", "react", "@types/react", "@total-typescript/exercise-cli", "zod" ], "enabled": false } ] } ================================================ FILE: src/01-number.problem.ts ================================================ import { expect, it } from "vitest"; export const addTwoNumbers = (a, b) => { return a + b; }; it("Should add the two numbers together", () => { expect(addTwoNumbers(2, 4)).toEqual(6); expect(addTwoNumbers(10, 10)).toEqual(20); }); ================================================ FILE: src/01-number.solution.ts ================================================ import { expect, it } from "vitest"; export const addTwoNumbers = (a: number, b: number) => { return a + b; }; it("Should add the two numbers together", () => { expect(addTwoNumbers(2, 4)).toEqual(6); expect(addTwoNumbers(10, 10)).toEqual(20); }); ================================================ FILE: src/02-object-param.problem.ts ================================================ import { expect, it } from "vitest"; export const addTwoNumbers = (params) => { return params.first + params.second; }; it("Should add the two numbers together", () => { expect( addTwoNumbers({ first: 2, second: 4, }), ).toEqual(6); expect( addTwoNumbers({ first: 10, second: 20, }), ).toEqual(30); }); ================================================ FILE: src/02-object-param.solution.1.ts ================================================ import { expect, it } from "vitest"; export const addTwoNumbers = (params: { first: number; second: number }) => { return params.first + params.second; }; it("Should add the two numbers together", () => { expect( addTwoNumbers({ first: 2, second: 4, }), ).toEqual(6); expect( addTwoNumbers({ first: 10, second: 20, }), ).toEqual(30); }); ================================================ FILE: src/02-object-param.solution.2.ts ================================================ import { expect, it } from "vitest"; type AddTwoNumbersArgs = { first: number; second: number; }; export const addTwoNumbers = (params: AddTwoNumbersArgs) => { return params.first + params.second; }; it("Should add the two numbers together", () => { expect( addTwoNumbers({ first: 2, second: 4, }), ).toEqual(6); expect( addTwoNumbers({ first: 10, second: 20, }), ).toEqual(30); }); ================================================ FILE: src/02-object-param.solution.3.ts ================================================ import { expect, it } from "vitest"; interface AddTwoNumbersArgs { first: number; second: number; } export const addTwoNumbers = (params: AddTwoNumbersArgs) => { return params.first + params.second; }; it("Should add the two numbers together", () => { expect( addTwoNumbers({ first: 2, second: 4, }), ).toEqual(6); expect( addTwoNumbers({ first: 10, second: 20, }), ).toEqual(30); }); ================================================ FILE: src/03-optional-properties.problem.ts ================================================ import { expect, it } from "vitest"; export const getName = (params: { first: string; last: string }) => { if (params.last) { return `${params.first} ${params.last}`; } return params.first; }; it("Should work with just the first name", () => { const name = getName({ first: "Matt", }); expect(name).toEqual("Matt"); }); it("Should work with the first and last name", () => { const name = getName({ first: "Matt", last: "Pocock", }); expect(name).toEqual("Matt Pocock"); }); ================================================ FILE: src/03-optional-properties.solution.ts ================================================ import { expect, it } from "vitest"; export const getName = (params: { first: string; last?: string }) => { if (params.last) { return `${params.first} ${params.last}`; } return params.first; }; it("Should work with just the first name", () => { const name = getName({ first: "Matt", }); expect(name).toEqual("Matt"); }); it("Should work with the first and last name", () => { const name = getName({ first: "Matt", last: "Pocock", }); expect(name).toEqual("Matt Pocock"); }); ================================================ FILE: src/04-optional-params.problem.ts ================================================ import { expect, it } from "vitest"; export const getName = (first: string, last: string) => { if (last) { return `${first} ${last}`; } return first; }; it("Should work with just the first name", () => { const name = getName("Matt"); expect(name).toEqual("Matt"); }); it("Should work with the first and last name", () => { const name = getName("Matt", "Pocock"); expect(name).toEqual("Matt Pocock"); }); ================================================ FILE: src/04-optional-params.solution.ts ================================================ import { expect, it } from "vitest"; export const getName = (first: string, last?: string) => { if (last) { return `${first} ${last}`; } return first; }; it("Should work with just the first name", () => { const name = getName("Matt"); expect(name).toEqual("Matt"); }); it("Should work with the first and last name", () => { const name = getName("Matt", "Pocock"); expect(name).toEqual("Matt Pocock"); }); ================================================ FILE: src/05-assigning-types-to-variables.problem.ts ================================================ import { expect, it } from "vitest"; interface User { id: number; firstName: string; lastName: string; isAdmin: boolean; } /** * How do we ensure that defaultUser is of type User * at THIS LINE - not further down in the code? */ const defaultUser = {}; const getUserId = (user: User) => { return user.id; }; it("Should get the user id", () => { expect(getUserId(defaultUser)).toEqual(1); }); ================================================ FILE: src/05-assigning-types-to-variables.solution.ts ================================================ import { expect, it } from "vitest"; interface User { id: number; firstName: string; lastName: string; isAdmin: boolean; } /** * How do we ensure that defaultUser is of type User * at THIS LINE - not further down in the code? */ const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", isAdmin: true, }; const getUserId = (user: User) => { return user.id; }; it("Should get the user id", () => { expect(getUserId(defaultUser)).toEqual(1); }); ================================================ FILE: src/06-unions.problem.ts ================================================ interface User { id: number; firstName: string; lastName: string; /** * How do we ensure that role is only one of: * - 'admin' * - 'user' * - 'super-admin' */ role: string; } export const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", // @ts-expect-error role: "I_SHOULD_NOT_BE_ALLOWED", }; ================================================ FILE: src/06-unions.solution.ts ================================================ interface User { id: number; firstName: string; lastName: string; /** * How do we ensure that role is only one of: * - 'admin' * - 'user' * - 'super-admin' */ role: "admin" | "user" | "super-admin"; } export const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", // @ts-expect-error role: "I_SHOULD_NOT_BE_ALLOWED", }; ================================================ FILE: src/07-arrays.problem.ts ================================================ interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Post; } interface Post { id: number; title: string; } export const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", role: "admin", posts: [ { id: 1, title: "How I eat so much cheese", }, { id: 2, title: "Why I don't eat more vegetables", }, ], }; ================================================ FILE: src/07-arrays.solution.1.ts ================================================ interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Post[]; } interface Post { id: number; title: string; } export const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", role: "admin", posts: [ { id: 1, title: "How I eat so much cheese", }, { id: 2, title: "Why I don't eat more vegetables", }, ], }; ================================================ FILE: src/07-arrays.solution.2.ts ================================================ interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Array; } interface Post { id: number; title: string; } export const defaultUser: User = { id: 1, firstName: "Matt", lastName: "Pocock", role: "admin", posts: [ { id: 1, title: "How I eat so much cheese", }, { id: 2, title: "Why I don't eat more vegetables", }, ], }; ================================================ FILE: src/08-function-return-type-annotations.problem.ts ================================================ import { expect, it } from "vitest"; interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Array; } interface Post { id: number; title: string; } /** * How do we ensure that makeUser ALWAYS * returns a user? */ const makeUser = () => { return {}; }; it("Should return a valid user", () => { const user = makeUser(); expect(user.id).toBeTypeOf("number"); expect(user.firstName).toBeTypeOf("string"); expect(user.lastName).toBeTypeOf("string"); expect(user.role).to.be.oneOf(["super-admin", "admin", "user"]); expect(user.posts[0].id).toBeTypeOf("number"); expect(user.posts[0].title).toBeTypeOf("string"); }); ================================================ FILE: src/08-function-return-type-annotations.solution.ts ================================================ import { expect, it } from "vitest"; interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Array; } interface Post { id: number; title: string; } /** * How do we ensure that makeUser ALWAYS * returns a user? */ const makeUser = (): User => { return { id: 1, firstName: "Matt", lastName: "Pocock", role: "admin", posts: [ { id: 1, title: "How I eat so much cheese", }, ], }; }; it("Should return a valid user", () => { const user = makeUser(); expect(user.id).toBeTypeOf("number"); expect(user.firstName).toBeTypeOf("string"); expect(user.lastName).toBeTypeOf("string"); expect(user.role).to.be.oneOf(["super-admin", "admin", "user"]); expect(user.posts[0].id).toBeTypeOf("number"); expect(user.posts[0].title).toBeTypeOf("string"); }); ================================================ FILE: src/09-promises.problem.ts ================================================ interface LukeSkywalker { name: string; height: string; mass: string; hair_color: string; skin_color: string; eye_color: string; birth_year: string; gender: string; } export const fetchLukeSkywalker = async (): LukeSkywalker => { const data = await fetch("https://swapi.py4e.com/api/people/1").then( (res) => { return res.json(); } ); return data; }; ================================================ FILE: src/09-promises.solution.1.ts ================================================ interface LukeSkywalker { name: string; height: string; mass: string; hair_color: string; skin_color: string; eye_color: string; birth_year: string; gender: string; } export const fetchLukeSkywalker = async (): Promise => { const data = await fetch("https://swapi.py4e.com/api/people/1").then( (res) => { return res.json(); } ); return data; }; ================================================ FILE: src/09-promises.solution.2.ts ================================================ interface LukeSkywalker { name: string; height: string; mass: string; hair_color: string; skin_color: string; eye_color: string; birth_year: string; gender: string; } export const fetchLukeSkywalker = async () => { const data = await fetch("https://swapi.py4e.com/api/people/1").then( (res) => { return res.json(); } ); return data as LukeSkywalker; }; ================================================ FILE: src/09-promises.solution.3.ts ================================================ interface LukeSkywalker { name: string; height: string; mass: string; hair_color: string; skin_color: string; eye_color: string; birth_year: string; gender: string; } export const fetchLukeSkywalker = async () => { const data: LukeSkywalker = await fetch( "https://swapi.py4e.com/api/people/1" ).then((res) => { return res.json(); }); return data; }; ================================================ FILE: src/10-set.problem.ts ================================================ import { expect, it } from "vitest"; import { Equal, Expect } from "./helpers/type-utils"; const guitarists = new Set(); guitarists.add("Jimi Hendrix"); guitarists.add("Eric Clapton"); it("Should contain Jimi and Eric", () => { expect(guitarists.has("Jimi Hendrix")).toEqual(true); expect(guitarists.has("Eric Clapton")).toEqual(true); }); it("Should give a type error when you try to pass a non-string", () => { // @ts-expect-error guitarists.add(2); }); it("Should be typed as an array of strings", () => { const guitaristsAsArray = Array.from(guitarists); type tests = [Expect>]; }); ================================================ FILE: src/10-set.solution.ts ================================================ import { expect, it } from "vitest"; import { Equal, Expect } from "./helpers/type-utils"; const guitarists = new Set(); guitarists.add("Jimi Hendrix"); guitarists.add("Eric Clapton"); it("Should contain Jimi and Eric", () => { expect(guitarists.has("Jimi Hendrix")).toEqual(true); expect(guitarists.has("Eric Clapton")).toEqual(true); }); it("Should give a type error when you try to pass a non-string", () => { // @ts-expect-error guitarists.add(2); }); it("Should be typed as an array of strings", () => { const guitaristsAsArray = Array.from(guitarists); type tests = [Expect>]; }); ================================================ FILE: src/11-record.problem.ts ================================================ import { expect, it } from "vitest"; const createCache = () => { const cache = {}; const add = (id: string, value: string) => { cache[id] = value; }; const remove = (id: string) => { delete cache[id]; }; return { cache, add, remove, }; }; it("Should add values to the cache", () => { const cache = createCache(); cache.add("123", "Matt"); expect(cache.cache["123"]).toEqual("Matt"); }); it("Should remove values from the cache", () => { const cache = createCache(); cache.add("123", "Matt"); cache.remove("123"); expect(cache.cache["123"]).toEqual(undefined); }); ================================================ FILE: src/11-record.solution.1.ts ================================================ import { expect, it } from "vitest"; const createCache = () => { const cache: Record = {}; const add = (id: string, value: string) => { cache[id] = value; }; const remove = (id: string) => { delete cache[id]; }; return { cache, add, remove, }; }; it("Should add values to the cache", () => { const cache = createCache(); cache.add("123", "Matt"); expect(cache.cache["123"]).toEqual("Matt"); }); it("Should remove values from the cache", () => { const cache = createCache(); cache.add("123", "Matt"); cache.remove("123"); expect(cache.cache["123"]).toEqual(undefined); }); ================================================ FILE: src/11-record.solution.2.ts ================================================ import { expect, it } from "vitest"; const createCache = () => { const cache: { [id: string]: string; } = {}; const add = (id: string, value: string) => { cache[id] = value; }; const remove = (id: string) => { delete cache[id]; }; return { cache, add, remove, }; }; it("Should add values to the cache", () => { const cache = createCache(); cache.add("123", "Matt"); expect(cache.cache["123"]).toEqual("Matt"); }); it("Should remove values from the cache", () => { const cache = createCache(); cache.add("123", "Matt"); cache.remove("123"); expect(cache.cache["123"]).toEqual(undefined); }); ================================================ FILE: src/11-record.solution.3.ts ================================================ import { expect, it } from "vitest"; interface Cache { [id: string]: string; } const createCache = () => { const cache: Cache = {}; const add = (id: string, value: string) => { cache[id] = value; }; const remove = (id: string) => { delete cache[id]; }; return { cache, add, remove, }; }; it("Should add values to the cache", () => { const cache = createCache(); cache.add("123", "Matt"); expect(cache.cache["123"]).toEqual("Matt"); }); it("Should remove values from the cache", () => { const cache = createCache(); cache.add("123", "Matt"); cache.remove("123"); expect(cache.cache["123"]).toEqual(undefined); }); ================================================ FILE: src/12-typeof-narrowing.problem.ts ================================================ import { expect, it } from "vitest"; const coerceAmount = (amount: number | { amount: number }) => {}; it("Should return the amount when passed an object", () => { expect(coerceAmount({ amount: 20 })).toEqual(20); }); it("Should return the amount when passed a number", () => { expect(coerceAmount(20)).toEqual(20); }); ================================================ FILE: src/12-typeof-narrowing.solution.ts ================================================ import { expect, it } from "vitest"; const coerceAmount = (amount: number | { amount: number }) => { if (typeof amount === "number") { return amount; } return amount.amount; }; it("Should return the amount when passed an object", () => { expect(coerceAmount({ amount: 20 })).toEqual(20); }); it("Should return the amount when passed a number", () => { expect(coerceAmount(20)).toEqual(20); }); ================================================ FILE: src/13-catch-blocks.problem.ts ================================================ import { expect, it } from "vitest"; const tryCatchDemo = (state: "fail" | "succeed") => { try { if (state === "fail") { throw new Error("Failure!"); } } catch (e) { return e.message; } }; it("Should return the message when it fails", () => { expect(tryCatchDemo("fail")).toEqual("Failure!"); }); ================================================ FILE: src/13-catch-blocks.solution.1.ts ================================================ import { expect, it } from "vitest"; const tryCatchDemo = (state: "fail" | "succeed") => { try { if (state === "fail") { throw new Error("Failure!"); } } catch (e: any) { return e.message; } }; it("Should return the message when it fails", () => { expect(tryCatchDemo("fail")).toEqual("Failure!"); }); ================================================ FILE: src/13-catch-blocks.solution.2.ts ================================================ import { expect, it } from "vitest"; const tryCatchDemo = (state: "fail" | "succeed") => { try { if (state === "fail") { throw new Error("Failure!"); } } catch (e) { return (e as Error).message; } }; it("Should return the message when it fails", () => { expect(tryCatchDemo("fail")).toEqual("Failure!"); }); ================================================ FILE: src/13-catch-blocks.solution.3.ts ================================================ import { expect, it } from "vitest"; const tryCatchDemo = (state: "fail" | "succeed") => { try { if (state === "fail") { throw new Error("Failure!"); } } catch (e) { if (e instanceof Error) { return e.message; } } }; it("Should return the message when it fails", () => { expect(tryCatchDemo("fail")).toEqual("Failure!"); }); ================================================ FILE: src/14-extends.problem.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; /** * Here, the id property is shared between all three * interfaces. Can you find a way to refactor this to * make it more DRY? */ interface User { id: string; firstName: string; lastName: string; } interface Post { id: string; title: string; body: string; } interface Comment { id: string; comment: string; } type tests = [ Expect>, Expect>, Expect>, ]; ================================================ FILE: src/14-extends.solution.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; /** * Here, the id property is shared between all three * interfaces. Can you find a way to refactor this to * make it more DRY? */ interface Base { id: string; } interface User extends Base { firstName: string; lastName: string; } interface Post extends Base { title: string; body: string; } interface Comment extends Base { comment: string; } type tests = [ Expect>, Expect>, Expect>, ]; ================================================ FILE: src/15-intersection.problem.ts ================================================ interface User { id: string; firstName: string; lastName: string; } interface Post { id: string; title: string; body: string; } /** * How do we type this return statement so it's both * User AND { posts: Post[] } */ export const getDefaultUserAndPosts = (): unknown => { return { id: "1", firstName: "Matt", lastName: "Pocock", posts: [ { id: "1", title: "How I eat so much cheese", body: "It's pretty edam difficult", }, ], }; }; const userAndPosts = getDefaultUserAndPosts(); console.log(userAndPosts.posts[0]); ================================================ FILE: src/15-intersection.solution.ts ================================================ interface User { id: string; firstName: string; lastName: string; } interface Post { id: string; title: string; body: string; } export const getDefaultUserAndPosts = (): User & { posts: Post[] } => { return { id: "1", firstName: "Matt", lastName: "Pocock", posts: [ { id: "1", title: "How I eat so much cheese", body: "It's pretty edam difficult", }, ], }; }; const userAndPosts = getDefaultUserAndPosts(); console.log(userAndPosts.posts[0]); ================================================ FILE: src/16-omit-and-pick.problem.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; interface User { id: string; firstName: string; lastName: string; } /** * How do we create a new object type with _only_ the * firstName and lastName properties of User? */ type MyType = unknown; type tests = [Expect>]; ================================================ FILE: src/16-omit-and-pick.solution.1.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; interface User { id: string; firstName: string; lastName: string; } /** * How do we create a new object type with _only_ the * firstName and lastName properties of User? */ type MyType = Omit; type tests = [Expect>]; ================================================ FILE: src/16-omit-and-pick.solution.2.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; interface User { id: string; firstName: string; lastName: string; } /** * How do we create a new object type with _only_ the * firstName and lastName properties of User? */ type MyType = Pick; type tests = [Expect>]; ================================================ FILE: src/17-function-types.problem.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; /** * How do we type onFocusChange? */ const addListener = (onFocusChange: unknown) => { window.addEventListener("focus", () => { onFocusChange(true); }); window.addEventListener("blur", () => { onFocusChange(false); }); }; addListener((isFocused) => { console.log({ isFocused }); type tests = [Expect>]; }); ================================================ FILE: src/17-function-types.solution.1.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; /** * How do we type onFocusChange? */ const addListener = (onFocusChange: (isFocused: boolean) => void) => { window.addEventListener("focus", () => { onFocusChange(true); }); window.addEventListener("blur", () => { onFocusChange(false); }); }; addListener((isFocused) => { console.log({ isFocused }); type tests = [Expect>]; }); ================================================ FILE: src/17-function-types.solution.2.ts ================================================ import { Equal, Expect } from "./helpers/type-utils"; /** * How do we type onFocusChange? */ type FocusListener = (isFocused: boolean) => void; const addListener = (onFocusChange: FocusListener) => { window.addEventListener("focus", () => { onFocusChange(true); }); window.addEventListener("blur", () => { onFocusChange(false); }); }; addListener((isFocused) => { console.log({ isFocused }); type tests = [Expect>]; }); ================================================ FILE: src/18-function-types-with-promises.problem.ts ================================================ import { expect, it } from "vitest"; interface User { id: string; firstName: string; lastName: string; } const createThenGetUser = async ( createUser: unknown, getUser: unknown, ): Promise => { const userId: string = await createUser(); const user = await getUser(userId); return user; }; it("Should create the user, then get them", async () => { const user = await createThenGetUser( async () => "123", async (id) => ({ id, firstName: "Matt", lastName: "Pocock", }), ); expect(user).toEqual({ id: "123", firstName: "Matt", lastName: "Pocock", }); }); ================================================ FILE: src/18-function-types-with-promises.solution.ts ================================================ import { expect, it } from "vitest"; interface User { id: string; firstName: string; lastName: string; } const createThenGetUser = async ( createUser: () => Promise, getUser: (id: string) => Promise, ): Promise => { const userId: string = await createUser(); const user = await getUser(userId); return user; }; it("Should create the user, then get them", async () => { const user = await createThenGetUser( async () => "123", async (id) => ({ id, firstName: "Matt", lastName: "Pocock", }), ); expect(user).toEqual({ id: "123", firstName: "Matt", lastName: "Pocock", }); }); ================================================ FILE: src/helpers/type-utils.ts ================================================ export type Expect = T; export type ExpectTrue = T; export type ExpectFalse = T; export type IsTrue = T; export type IsFalse = T; export type Equal = (() => T extends X ? 1 : 2) extends < T, >() => T extends Y ? 1 : 2 ? true : false; export type NotEqual = true extends Equal ? false : true; // https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 export type IsAny = 0 extends 1 & T ? true : false; export type NotAny = true extends IsAny ? false : true; export type Debug = { [K in keyof T]: T[K] }; export type MergeInsertions = T extends object ? { [K in keyof T]: MergeInsertions } : T; export type Alike = Equal, MergeInsertions>; export type ExpectExtends = EXPECTED extends VALUE ? true : false; export type ExpectValidArgs< FUNC extends (...args: any[]) => any, ARGS extends any[], > = ARGS extends Parameters ? true : false; export type UnionToIntersection = ( U extends any ? (k: U) => void : never ) extends (k: infer I) => void ? I : never; ================================================ FILE: tsconfig.json ================================================ { "extends": "@total-typescript/tsconfig/bundler", "compilerOptions": { "noUncheckedIndexedAccess": false, "verbatimModuleSyntax": false }, "include": [ "src" ] } ================================================ FILE: vite.config.mts ================================================ import { defineConfig } from "vitest/config"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ test: { include: ["src/**/*{problem,solution,explainer}*.{ts,tsx}"], passWithNoTests: true, environment: "jsdom", }, plugins: [tsconfigPaths()], });