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