You know your programming language very very well, AND design for failure. What I do normally is first write the API of the library I want to use. Then I might start detailing things, which becomes some early documentation/API. Finally I try to implement it in code, and if I find a truly blocking code bit I restructure my assumptions about what is possible with my API and go back to the API/docs.
For example, at some point I knew I wanted to set localStorage and cookies values just like this:
cookie.token = '1234';
local.token = '1234';
I know enough Javascript, and the language is flexible enough, that I know that I
could achieve that, either with getter/setters or with the more flexible Proxy(). So I continued writing the other methods first; how would it look ideally to "read" a value? To "delete" a value? Etc:
console.log(cookie.token);
delete cookie.token;
// ...
I wrote few examples of each, put it all in some documentation, and then wrote some tests, following those documentation examples:
expect(cookie.token).toBe(null);
cookie.token = '1234';
expect(cookie.token).toBe('1234');
And finally wrote the code for it. Later I wanted to add IndexedDB, BUT! That is async! My abstraction was broken, or was it? I could just modify a single method and everything else would work as expected:
db.token = '1234';
console.log(await db.token); // <= added await here
delete db.token;