here is a pretty random collection some things you should be aware of when diving into our core code. hopefully it’s useful!
- promises: To avoid ‘callback hell’, our code uses promises. Read up on them on Mozilla Developer Network (“MDN”) before trying to read the code, otherwise you won’t understand much of it. In some places we’re not catching errors properly, which can lead to ‘possibly uncaught error’ showing up in the console. This is due to how errors work with promises, and it’s good if you learn about that first, as well.
- closures and variable scope: Read up on these two topics as well on MDN. specifically, we sometimes define a function within a loop, to create a local function closure that can “capture” a variable from outside the iteration.
- this and that: we sometimes write “var self = this;” at the top of a function, so that “self” is available in callbacks. Other times, we do “.bind(this)” on the callback, in which case “this” is available inside the callback.
- files: each file is wrapped in a “(function() { … })();” closure to make sure it doesn’t leak variable into the global scope, and implements one feature. On pageload, these features are loaded one by one. The build process uses a Makefile in which features are enabled and disabled for various build targets. If you add a new file to src/ then you’ll also need to add it into the applicable build targets in the Makefile.
- tests: we use a testing framework called ‘teste’ for our unit tests. It’s not widely used, but pretty similar to most other unit testing frameworks. If you want
to read the code, you don’t need to look at the test suites (although it might help you), but if you want to write code then you will also have to write tests for
the code you contribute. Each file in test/unit/ corresponds roughly to a file in src/, and aims to cover most of the code in that file. Our tests are run automatically on travis-ci, from a github commit hook. see also our contribution guidelines about pull requests and tests. - events: each feature (file) can emit events and listen to events from other features. If you’re not familiar with event-driven programming (as opposed to functional, object-oriented, or procedural programming), it’s a good idea to read about them, although it’s pretty straight forward what they do. The way the events are wired between the features is quite intricate, and there are quite a lot of events that have similar meanings, for instance ‘features-loaded’, ‘connected’, and ‘ready’. The ‘change’ events used through all levels of caching are also an important feature of how incoming changes are propagated. In a one-line summary: once all features are loaded, the main pageload bootstrap goes through the ‘authorize’ feature, which will configure the ‘remote’ feature, after which ‘connected’ or ‘not-connected’ are fired, which leads to ‘ready’ being fired, and then fireInitial fires the initial ‘change’ events for data that’s already locally cached.
- double bang is sometimes used as a trick to cast to Boolean: !!(storage) will be true if the variable storage is defined and non-zero, and false otherwise.
- objects: there is a little bit of object-oriented programming going on here and there in our code base, using the prototypical inheritance. If you don’t know how JavaScript function prototypes work, read up on them on MDN.
- there are two ways to build a module: using getListing and getObject everywhere, or keeping a copy of the data in memory, and updating it whenever a change event comes in from the baseclient. Both have merits: with getListing/getObject you save memory usage, but it’s also slower (data has to come from disk in an IndexedDB transaction) and forces the module to expose an asynchronous interface (either with callbacks or promises or events), rather than a synchronous one which can make app development much simpler. In the core code you’ll see mechanism for supporting both paradigms at the same time, which sometimes makes our baseclient architecture look a bit schizOfrenic. For instance, the ‘waitForPath’ function makes it possible to call baseclient.getObject during pageload, and it will automatically make sure that the cache is up to date before returning anything. That makes the library usable as a more or less transparent cache whenever there is connectivity. At the same time, the library is usable as a decentralized versioning systems if you catch all the ‘change’ and ‘conflict’ events and deal with them accordingly in module code.