-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.html
406 lines (318 loc) · 12.7 KB
/
api.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
---
layout: default
title: API
---
Please also see the [getting started][] page.
# API
<div id="client"></div>
## 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.
<div id="constructor"></div>
### - `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
{% highlight javascript %}
var atomize = new Atomize("http://localhost:9999/atomize");
{% endhighlight %}
<div id="connect"></div>
### - `connect()`
* Attempts to connect to the AtomizeJS server. If no URL was specified
to the constructor, this is a no-op
<div id="onPreAuthenticated"></div>
### - `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
<div id="onAuthenticated"></div>
### - `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
<div id="root"></div>
### - `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 `{}`
<div id="atomically"></div>
### - `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.
{% highlight javascript %}
atomize.atomically(function () {
if (atomize.root.number % 2 === 0) {
return "Even";
} else {
return "Odd";
}
}, function (result) {
console.log(result);
});
{% endhighlight %}
<div id="lift"></div>
### - `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
{% highlight javascript %}
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 () {});
{% endhighlight %}
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.
<div id="retry"></div>
### - `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`:
{% highlight javascript %}
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);
});
{% endhighlight %}
<div id="orElse"></div>
### - `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*).
{% highlight javascript %}
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);
});
{% endhighlight %}
<div id="inTransaction"></div>
### - `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][translate] 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][translate].
<div id="access"></div>
### - `access(obj, fieldName)`
* `access(a, b)` is the equivalent of `a.b`
<div id="assign"></div>
### - `assign(obj, fieldName, value)`
* `assign(a, b, c)` is the equivalent of `a.b = c`
<div id="enumerate"></div>
### - `enumerate(obj)`
* Is used in the translation of `for (a in b) ...` loops: rewritten to
become `for (a in atomize.enumerate(b)) ...`
<div id="has"></div>
### - `has(obj, fieldName)`
* `has(a, b)` is the equivalent of `b in a`
<div id="erase"></div>
### - `erase(obj, fieldName)`
* `erase(a, b)` is the equivalent of `delete a.b`
<div id="server"></div>
## Server
<div id="create"></div>
### - `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:
3. 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
{% highlight javascript %}
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();
{% endhighlight %}
<div id="serverEvents"></div>
## Server Events
<div id="onConnection"></div>
### - `'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
<div id="serverEventsPerClient"></div>
## Server Events Per Client
<div id="onClose"></div>
### - `'close'`
* Emitted when the client connection is closed
* The listeners are passed the `client` object
<div id="onData"></div>
### - `'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`.
<div id="authExample"></div>
## 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.
{% highlight javascript %}
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');
{% endhighlight %}
Corresponding client side code might look like:
{% highlight javascript %}
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();
}
{% endhighlight %}