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
ishttp
orhttps
then a URL is constructed aslocation.host + /atomize
- Otherwise offline client-local-only operation is assumed
- If the
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:
- 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 itsonmessage
callback - The SockJS connection object. This allows you to
send
data to the server. This object is provided on every invocation of this function
- 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
- 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:
- The function to be run as a transaction. May return a value.
- 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:
- A list of transaction functions
- 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 normalretry
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 andfalse
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 ofa.b
- assign(obj, fieldName, value)
assign(a, b, c)
is the equivalent ofa.b = c
- enumerate(obj)
- Is used in the translation of
for (a in b) ...
loops: rewritten to becomefor (a in atomize.enumerate(b)) ...
- has(obj, fieldName)
has(a, b)
is the equivalent ofb in a
- erase(obj, fieldName)
erase(a, b)
is the equivalent ofdelete a.b
Server
- create(server, path, root)
- Create a new AtomizeJS server
- Two mandatory parameters:
- HTTP server: typically returned from
http.createServer()
; - Path within the server to provide the AtomizeJS service
- HTTP server: typically returned from
- One optional parameter:
- 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:
- The message received from the client. This is the same message
object that is emitted by the SockJS
'data'
event. - The
client
object. This can be used to directly communicate back to the client as part of authentication steps.
- The message received from the client. This is the same message
object that is emitted by the SockJS
- Authentication is considered complete when you set
client.isAuthenticated = true;
- This event will never be emitted after
client.isAuthenticated
istrue
.
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();
}