Microkernel
I mentioned in the comments that I probably have to solve the microkernel vs. monolithic kernel dilemma here, or, more precisely, its equivalent for the appjet platform clone. That is, to decide whether the boot.js will contain just the bare minimum for application booting (the microkernel way), in which case all other system services must be provided by another modules, or to provide the whole pack of system-related functionality in one file (the monolithic kernel way).
In present state, boot.js contained some unneeded functionality, for example, the two filesaving methods. They are needed for system-related apps, like the editor or the rescuer, but they are definitely useless for majority of real apps, if I imagine them being there (there are none, so far). I finally decided to go the microkernel way - to provide the minimum needed to run and communicate - exactly what the real microkernels do. All the rest must be provided by additional libs.
Not that I moved that way very much - I did not reworked any part of the system yet. But, I decided what the code of the microkernel will be (and this code, I very hope, stays unchanged for ever after - if you see some bugs, tell me, please):
(function() {
var _an = appjet._native;
var tableid = _an.storage_get("obj-root", "bootTable").value;
var appBoot = _an.storage_get(tableid, request.headers.Host).value;
var name, queue = appjet._internal.queue = appBoot ? appBoot.split(",") : ["0", "", "lib-0"];
appjet.appName = queue.shift();
appjet.mainDomain = queue.shift() || "appdomain.net";
while((queue = appjet._internal.queue) && (name = queue.shift())) {
var agent = import({}, name);
if (typeof agent.runAgent === "function") { agent.runAgent(queue); }
}
})();
Well, it may look complicated, but it's just a little dense. At first, it is backwards-compatible with traditional appjet way of thinking - certain host is always mapped to certain code. That will be implemented by adding a field for that host into storage.bootTable. Additionally, field for source.app.appdomain.net should be filled, with appropriate data. At the simplest case, you simply fill the app field with "appname,,lib-appname-impl" by which you say app name is "appname", main domain is the default (you must set it in the code, once), and "lib-appname-impl" should be imported; the source field can be something like "show-source,,lib-show-source" and it can then parse the host.
This may be enough and you can be happy with it. But these few lines of the code open lots of another possibilities to organize and run your code. You can more actively use the queue. For example, if you can put anything in the queue, why not to put something like "show-source,,lib-show-source,appname" into the source.appname.appdomain.net field? No parsing is then needed, you just get the app name to show source for from the queue itself.
If you begin to use queue actively inside the applications as well, you can create software agents (each lib representing one), which are able to send messages (putting the recipient into the queue, with optional arguments for him).
If you push recipient to the queue, with its optional arguments (these must be taken out by him, explicitly), then you are sending a message in a queue manner, it is appended to the back of the queue. You can also unshift instead of push - this is much more akin to unix exec() call - this message is then put in front of the queue, which means, in will be processed first, before any other ones that were there. In other view, you are replacing yourself by another agent (and if it supports identical set of arguments, you can just leave them there and unshift only its name).
But generally, unshifting behaviour is taken as inappropriate and pushing is favoured whenever possible in messaging agents paradigm. There must be a serious reason for unshift.
Another thing you can actively use, is runAgent function. If you define the function in your agent (that is, lib), it will be called for every occurence of the lib in the queue, that is, for every message. This is the normal thing for reusable agent, it should have the function defined. But if you choose not to include it, you have a single-action agent - it only processes first message, all other times, just nothing happens (courtesy of appjet lib caching, such agent should be parameterless, obviously). So the "microkernel" really passes messages between modules, in a way (the real microkernel does basically this and nothing more).
And again, you can just write old-fashioned apps and ignore all this - it will work without hassle.
ps. The code in this article is licensed by MIT license, (c) 2009 Herbert Vojčík