Andrew Watson

Andrew Watson

  • The Learning Process
    Learning about TypeScript, Next.js, the GitHub API, and more

    Boy have I learned a lot over the past few days.

    As you can see from the recent round of commits I've made, I have been working on a few things...

    Requiring Commits to have a defined date

    This commit moves from a Commit with a date: string | undefined field to one with a date: string field.

    Originally, I tried to make my model class (Commit) match the GitHub API as closely as possible, and since GET /repos/{owner}/{repo}/commits can return commit objects with possibly null authors as well as committers, it's possible for a commit returned from the GitHub API to not have a date at all.

    That... seems unlikely, so I now assume that all commits will indeed have dates, and throw an Error in the Commit constructor if date is undefined. This can be tested during development, and (combined with the next change) means that I've removed all explicit undefined types from the current codebase.

    Require CommitGroups to have at least one Commit

    This commit changes a lot of things. Firstly, it requires that CommitGroups have at least one Commit in them. It does this by throwing an Error from the constructor if an attempt is made to create a CommitGroup with an empty array of Commits.

    This is really helpful because if a CommitGroup must have at least one Commit and (from the last update) if a Commit must have a definite date, then CommitGroups can have definite start() and end() timestamps. This means that start() and end() can have type Date rather than Date | undefined. That change removes the last explicit undefined types from the codebase. This means we shouldn't have to do much more "?." / "??" checking.

    Ease serialisation by moving from methods to fields

    The reason I haven't updated this project in a few days is because I tried to do too much all at once (I fall into that rabbit hole sometimes). One of the things I was trying to do was to return classes (rather than plain objects) from the getStaticProps methods in my dynamic route definition files (/pages/blog/[slug].md and /pages/projects/[id].md).

    I ran into some issues here (specifically around defining custom toJSON() methods) and everything went a bit haywire so I've backed that out of this group of commits for now.

    Anyway, the point is: serialisation is confusing enough as-is without including a bunch of methods and custom classes. So I'm trying to store as much data in object fields, rather than in methods which calculate some value. To that end, I moved all of the methods in ProjectUpdate to fields in these updates, which means that LogEntry and CommitGroup also have these as fields now instead of methods (start instead of start() for example).

    This made it easier to make the next change, as well.

    Make CommitGroup class immutable

    As start and end are now fields of CommitGroup rather than methods, and as Commits are themselves immutable (containing only readonly fields), it made sense to try to make CommitGroups immutable as well.

    In general, immutability of objects is something worth working toward. It makes the state of an object easier to reason about and can eliminate race conditions in multithreaded environments (so, not JavaScript, really). However, immutability can make your program less performant, as you have to create an entirely new object whenever you want to update a field.

    This was easy enough -- just making the array of commits readonly, and making add() return a new CommitGroup rather than mutating the current one makes the class effectively immutable. We just need to remember to save the result of add() to a new variable when updating a CommitGroup.

    Try not to hit the GitHub rate limit

    Finally, I made one small band-aid kind of change, where I don't query GitHub for commits when in development mode. This is easy enough to check in Next.js

    if (process.env.NODE_ENV === "development") {
      // do development stuff
    } else {
      // do other stuff

    so I simply return an empty array of Commits when in development mode. A better solution here would be to

    • query GitHub for commits
    • save those commits to a temporary cache file
    • don't query a second time if a cache file is present
    • delete the cache file on a git push

    This would give a better idea of what the site will look like when it goes live, but it's a bit more involved, so I'll do that later. For now, I just return an empty array.


    Finally, in this commit I did some refactoring, moving some classes around in a way that seemed to be a bit more organised.

    And in the last few commits this round, I hit a bug where I was trying to use String.replaceAll, but I was targeting an older ES version which doesn't support that method.

    So, all in all, lots done here, but lots more to do.