Introduction#
Hello everyone,
How are you? Welcome to my blog. In this post, I will talk about Futtun’s progress update, from Monday 23 February 2026 to today, Sunday 1 March 2026.
This week, I have been focused on building the auditing log. I had a couple of hiccups along the way, and one “side quest”.
Continue building the auditing log#
As I said last week, I have started building an audit log for Futtun.
To summarise, Futtun is a front-end only application, and all the data lives in the user’s browser. If the user clears their browser’s data, everything saved in Futtun gets lost. To get around this, I am building an audit log, where every action the user takes is recorded. You can export this file, clear the browser’s data, and import the file into Futtun, and the application will rebuild your data from it.
However, I have run into a couple of issues:
- IndexedDB is schema versioned, and changing the database’s structure is less straightforward than I had hoped;
- Refactoring async JavaScript code, to make saving user’s actions in the audit log possible.
I also embarked on a “side quest”:
- When adding a new thought, return the new thought object, rather than just its id.
I will talk about them in the order I used above.
IndexedDB schema versioning#
This was surprising to me, since I am new to IndexedDB, and mainly used to relational databases.
IndexedDB has the concept of “schema versions”, which represent the structure of the database at that point in time (its “schema”). The first version is 1, which has the initial database structure you set for your application. Every time you want to change it, you need to bump this version number, which fires the onupgradeneeded event.
I am yet to solve this problem, so I can only describe the problem I am facing, without offering a solution. I have one in theory, but until I have verified it in practice I will refrain from sharing it. I plan to make a separate blog post with my approach once I have solved it, which I will then link here.
But, I can still state the problem here: providing an upgrade path when handling the onupgradeneeded event. The database lives in the user’s browser, and when the version changes, I need to tell it what needs doing to get up to speed to the latest version. However, the questions I still need to explore are the following:
- What happens if the user needs to jump multiple versions in one go? For example, they could have used Futtun 3 months ago, when the database version was, say,
13, and never since. Now, the database version is, say,18, and I must apply these new changes to the user’s database. My initial hunch was to use aswitchstatement, which I very much doubt is going to work for this case; - Can I, at any point, remove old versions (say, the ones I created 2 years ago), or am I forced to keep all of them, forever and ever? What I dislike is that, to change an object store in IndexedDB (because I want to add/remove a property), I need to delete said object store and re-create it with the relevant changes. If possible, I would rather not keep these “data migrations” around forever.
Refactor async JavaScript code#
IndexedDB’s API is asynchronous, so my custom functions that interact with it must use async. To do this, inside these custom functions, I put all the logic inside a return new Promise. That worked when all I had to do was create/read/update/delete (CRUD) a thought, but with also having to track them in the audit log, this approach falls short.
The new flow is the following:
- Manipulate the thought;
- If successful, track the action in the audit log;
- If unsuccessful, tracking the action is pointless for my purposes.
From the points above, since #2 depends on #1, it makes more sense to refactor my current functions to invoke 2 new functions:
- A
thoughtTransaction()function, that manipulates the object in IndexedDB. This would be the original logic I had in my functions, before refactoring; - An
auditTransaction()function, that tracks the successful action in the audit log.
I can then get rid of the return new Promise in the top level function, and just invoke these 2 new functions. Using pseudo-code, it looks like this:
async function addThought() {
const thought = await thoughtTransaction();
if (thought is successful) {
const audit = await auditTransaction();
// More stuff here
} else {
// Deal with the failure
}
}Returning the new thought upon creating it#
This is the “side quest” I was mentioning. While not strictly necessary, it made my life easier when refactoring the async JavaScript code.
For adding a new object, IndexedDB offers the add() function, which you call against the desired object store: objectStore.add(yourObject). It returns the id of this object in your database, which was fine before implementing the audit log. Now, it is more useful to have my custom function return the entire new object, not just its id. E.g. something along these lines:
// This lives inside a `return new Promise`, hence the `resolve`.
// `thought` is the object that represents the user's thought, but without
// an id.
// dbRequest is the constant where I store calling `add(thought)` on the
// relevant object store.
const result = {
id: dbRequest.result,
description: thought.description,
...more properties here...
}
resolve(result);Was this refactor necessary? Yes, because when I wrote the original logic, having it return just the id, rather than the whole object, nagged me. It was working, but I wanted better. I let it be at the time because I wanted to get the basic features working, and come back to it “later”. “Later” has been this week.
Writing this has made me realise I have more places I can improve like this. I will keep it in mind in case I need another “side quest” to distract me this upcoming week.
Dependency upgrades#
Last week, I deployed Futtun to https://www.futtun.com, thus making it available on the Internet. Being an Internet facing application, I will now need to keep on top of upgrading its dependencies for as long as I will keep Futtun available.
Conclusion#
Thank you for your time, and I hope to see you again soon. Bye bye.