Please also see the getting started page.

API

Client

Note that with the exception of the constructor, the complete client API is available in the NodeJS server as shown above under the atomizeServer.atomize field.

- Atomize(URL) constructor

  • Browser-side only
  • Creates a new atomize client but does not connect immediately to the server
  • Maximum one argument which is the URL to the AtomizeJS server
  • If no URL is provided then:
    • If the location.protocol is http or https then a URL is constructed as location.host + /atomize
    • Otherwise offline client-local-only operation is assumed
var atomize = new Atomize("http://localhost:9999/atomize");

- connect()

  • Attempts to connect to the AtomizeJS server. If no URL was specified to the constructor, this is a no-op

- onPreAuthenticated

  • Assign to this the function that you wish to run once the client has connected to the server
  • The function is passed two parameters:
    1. Any message received from the server. The function is first run as soon as the connection is established, and at this point there is no such message, so this parameter will be undefined. However, this function may be called multiple times which allows for challenge-response authentication mechanisms to be used. Subsequent invocations of this function occur only once a message has been received from the server. The message is the same object as SockJS provides to its onmessage callback
    2. The SockJS connection object. This allows you to send data to the server. This object is provided on every invocation of this function
  • Once this function returns true, AtomizeJS assumes the client has successfully authenticated with the server, and so does not invoke this function again
  • By default there is no authentication required, so by default, this function will never be called

- onAuthenticated

  • Assign to this the function that you wish to run once the client has authenticated with the server. By default, there is no authentication required
  • No arguments are passed to the function

- root

  • The root object
  • All objects and values reachable from the root object are available to all clients
  • The initial value is the empty object {}

- atomically(txnFun, contFun)

  • Runs a transaction function
  • Two arguments:
    1. The function to be run as a transaction. May return a value.
    2. The function to be run as a continuation once the transaction has committed. Will be passed the result of the first argument.
atomize.atomically(function () {
    if (atomize.root.number % 2 === 0) {
        return "Even";
    } else {
        return "Odd";
    }
}, function (result) {
    console.log(result);
});

- lift(obj)

  • Prepares an object for management by the AtomizeJS system
  • One argument: the object to be managed
  • Must be used before assigning an object into another AtomizeJS object; if you do not, the assignment will fail and a NotATVarException exception will be thrown
  • Safe to use on both primitives and objects though is unnecessary for primitives
  • Idempotent: calling lift on the same object multiple times will return the same managed AtomizeJS object
  • It is safe to use lift outside of a transaction: this is because when used outside of a transaction, it implicitly creates and commits a transaction which merely passes the object through to the AtomizeJS server. This can't possibly fail, so you do not need to consider the possibility of such a transaction retrying
atomize.atomically(function () {
    atomize.root.obj = atomize.lift({a: "hello", b: 5});
    atomize.root.obj.c = atomize.lift({});
    atomize.root.obj.b = atomize.lift(6);
}, function () {});

Please note that currently, if you lift in an object that has a non-default prototype then the prototype does not get recreated by other clients: the other clients will just see an object with a prototype of Object.prototype. Additionally, only plain Objects and Arrays are supported: trying to lift a Function will not work.

- retry()

  • Indicates the transaction wishes to be suspended pending some change
  • No arguments
  • Upon retry, the effects of the transaction function are undone
  • The transaction will be restarted when the values the transaction read prior to the retry have been modified
  • Note that no statements that directly follow a retry will ever be invoked: they are dead code

In the following example, the continuation will only be invoked with a value !== undefined:

atomize.atomically(function () {
    if (atomize.root.value === undefined) {
        atomize.retry();
    } else {
        var result = atomize.root.value;
        delete atomize.root;
        return result;
    }
}, function (result) {
    console.log(result);
});

- orElse(txnFuns, contFun)

  • An abstraction of retry
  • Two arguments:
    1. A list of transaction functions
    2. A continuation
  • The initial current-transaction-function is the function at index 0 of the list
  • If the current-transaction-function does not retry then it commits as normal, and the continuation is invoked as normal
  • If the current-transaction-function hits retry then the writes of the current-transaction-function are undone and the current-transaction-function is now the next transaction function in the list. The new current-transaction-function is then invoked
  • If all transaction functions retry, then a normal retry is issued for all objects read across all transaction functions

In the following example, at most one of the transaction functions will commit. If both retry, then the overall orElse operation will be restarted once any of the objects read by both transaction functions have changed (in this case, the complete read set is atomize.root, atomize.root.a, atomize.root.b; please see the background page for more details on read sets).

atomize.orElse(
    [function () {
         if (atomize.root.a.toIncrement === undefined) {
             atomize.retry();
         } else {
             atomize.root.a.toIncrement += 1;
             return atomize.root.a.toIncrement;
         }
     },
     function () {
         if (atomize.root.b.toDecrement === undefined) {
             atomize.retry();
         } else {
             atomize.root.b.toDecrement -= 1;
             return atomize.root.b.toDecrement;
         }
     }], function (result) {
         console.log(result);
     });

- inTransaction()

  • Returns true if currently in a transaction and false otherwise
  • No arguments
  • Can be called both inside and outside a transaction

Extended Client API

The following methods are available in the client and are used by the translation tool for browsers that do not yet support Proxies. If you wish, you can write code that directly uses this API, and thus avoid the need to use the translation tool.

- access(obj, fieldName)

  • access(a, b) is the equivalent of a.b

- assign(obj, fieldName, value)

  • assign(a, b, c) is the equivalent of a.b = c

- enumerate(obj)

  • Is used in the translation of for (a in b) ... loops: rewritten to become for (a in atomize.enumerate(b)) ...

- has(obj, fieldName)

  • has(a, b) is the equivalent of b in a

- erase(obj, fieldName)

  • erase(a, b) is the equivalent of delete a.b

Server

- create(server, path, root)

  • Create a new AtomizeJS server
  • Two mandatory parameters:
    1. HTTP server: typically returned from http.createServer();
    2. Path within the server to provide the AtomizeJS service
  • One optional parameter:
    1. The initial root object. If omitted, the initial root object will be a fresh empty object {}.
  • Note the client is available from within the server by calling the client method on the returned object
var http = require('http');
var atomize = require('atomize-server');

var httpServer = http.createServer();
var atomizeServer = atomize.create(httpServer, '[/]atomize');
httpServer.listen(9999, '0.0.0.0');

var atomizeClient = atomizeServer.client();

Server Events

- 'connection'

  • Emitted when a new client connects to the server
  • The listeners are passed a client object which can be used to directly communicate back to the client as part of any authentication scheme
  • Authentication is considered complete when you set client.isAuthenticated = true;
  • If no listeners are installed for this event, the server will immediately mark every connection authenticated: i.e. by default there is no explicit authentication
  • The client object itself is also an event emitter

Server Events Per Client

- 'close'

  • Emitted when the client connection is closed
  • The listeners are passed the client object

- 'data'

  • Emitted when data is received from the client as part of authentication.
  • The listeners are passed two arguments:
    1. The message received from the client. This is the same message object that is emitted by the SockJS 'data' event.
    2. The client object. This can be used to directly communicate back to the client as part of authentication steps.
  • Authentication is considered complete when you set client.isAuthenticated = true;
  • This event will never be emitted after client.isAuthenticated is true.

Example of authentication and server events

The following example shows how to implement (trivial) authentication: the client must send a {text: "wibble"} JSON object, and the server must send back a {text: "wobble"} JSON object; and also how to modify the root object so that the root.clients object contains an entry per client.

var http = require('http');
var atomize = require('atomize-server');
var httpServer = http.createServer();
var port = 9999;

var atomizeServer = atomize.create(httpServer, '[/]atomize');
var atomizeClient = atomizeServer.client();
atomizeClient.atomically(function () {
    atomizeClient.root.clients = atomizeClient.lift({server: {}});
}, function () {
    atomizeServer.on('connection', function (client) {

        client.on('close', function (client) {
            atomizeClient.atomically(function () {
                delete atomizeClient.root.clients[client.connection.id];
            }, function () {
                console.log("Connection death: " + client.connection.id);
            });
        });

        client.on('data', function (message, client) {
            var text = JSON.parse(message).text;
            if (text === "wibble") {
                client.connection.write(JSON.stringify({text: "wobble"}));

                atomizeClient.atomically(function () {
                    atomizeClient.root.clients[client.connection.id] =
                            atomizeClient.lift({});
                }, function () {
                    console.log("New connection id: " + client.connection.id);
                    client.isAuthenticated = true;
                });
            } else {
                client.connection.write(JSON.stringify({text: "denied"}));
            }
        });
    });
});

console.log(" [*] Listening on 0.0.0.0:" + port);
httpServer.listen(port, '0.0.0.0');

Corresponding client side code might look like:

function start () {
    atomize = new Atomize("http://localhost:9999/atomize");
    atomize.onPreAuthenticated = function (message, sockjs) {
        if (undefined === message) {
            // 1st time through
            sockjs.send(JSON.stringify({text: "wibble"}));
            return false;
        } else {
            return JSON.parse(message.data).text === "wobble";
        }
    };
    atomize.onAuthenticated = function () { // Ready to go!
    };
    atomize.connect();
}