├── .gitignore ├── .hgignore ├── LICENSE-MIT-AtomizeJS ├── README.md ├── index.js ├── lib └── atomize-server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *~ 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | ^node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE-MIT-AtomizeJS: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2012 VMware, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atomize-server-node 2 | 3 | This is the NodeJS server library for the Atomize DSTM JavaScript 4 | project. 5 | 6 | `npm install atomize-server` will install this package. 7 | 8 | Please see the main [AtomizeJS site](http://atomizejs.github.com/) for 9 | further details. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/atomize-server'); 2 | -------------------------------------------------------------------------------- /lib/atomize-server.js: -------------------------------------------------------------------------------- 1 | /*global require, exports */ 2 | /*jslint devel: true */ 3 | 4 | /* *************************************************************************** 5 | # AtomizeJS Node Server 6 | 7 | Distributed objects are represented by TVar objects. When an object is 8 | created, its value is passed directly into the TVar constructor which 9 | takes a copy of it, and sets up version tracking and observer 10 | patterns. TVars have global IDs, which are not communicated to 11 | clients, and client-local IDs, which are. Thus each client maintains a 12 | mapping from client-local IDs to global IDs, and vice versa. Thus you 13 | can think of global IDs as being abstract physical addresses, and 14 | client-local IDs as being abstract virtual addresses, if you want to 15 | think in OS terms. 16 | 17 | Updates to an object's fields appear as property descriptors within 18 | the transaction log, received from clients. If the descriptor object 19 | has a 'tvar' field, then the descriptor's 'value' field is set to be 20 | the corresponding global TVar ID. Thus the TVar's 'raw' object will 21 | contain fields with abstract pointers of the form {'tvar': id}. If the 22 | field's value is a primitive, then the value is applied directly to 23 | the TVar's 'raw' object, modulo some rubbish for coping with the fact 24 | that for some daft reason you can't do Object.defineProperty on an 25 | Array's 'length' field. Fields which have been deleted by clients are 26 | indicated as such in the transaction log received from the client and 27 | get directly deleted from the TVar's 'raw' object. 28 | 29 | When updates are sent down to clients, we send down the entire object, 30 | with each of its existing fields as a property descriptor. The client 31 | is responsible for updating its own representation of the object 32 | accordingly - in particular identifying which, if any fields, have 33 | been deleted. Again, if a field's value is an abstract pointer of the 34 | form {'tvar': id} then the descriptor passed to the client will have 35 | no 'value' field, but instead a 'tvar' field, the value of which is 36 | the client-local ID. 37 | 38 | * ***************************************************************************/ 39 | 40 | (function () { 41 | 'use strict'; 42 | 43 | var atomize = require('atomize-client'), 44 | cereal = require('cereal'), 45 | sockjs = require('sockjs'), 46 | events = require('events'), 47 | sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js", 48 | log: function () {}}, 49 | globalTVarCount = 0, 50 | globalTVars = {}, 51 | globalLocal = {}, 52 | create, util, rootTVar; 53 | 54 | util = (function () { 55 | return { 56 | isPrimitive: function (obj) { 57 | return obj !== Object(obj); 58 | }, 59 | 60 | hasOwnProp: ({}).hasOwnProperty, 61 | 62 | shallowCopy: function (src, dest) { 63 | var keys = Object.keys(src), 64 | i; 65 | for (i = 0; i < keys.length; i += 1) { 66 | dest[keys[i]] = src[keys[i]]; 67 | } 68 | }, 69 | 70 | liftFunctions: function (src, dest, fields) { 71 | var i, field; 72 | for (i = 0; i < fields.length; i += 1) { 73 | field = fields[i]; 74 | dest[field] = src[field].bind(src); 75 | } 76 | } 77 | }; 78 | }()); 79 | 80 | function TVar(id, isArray, raw) { 81 | this.raw = isArray ? [] : {}; 82 | util.shallowCopy(raw, this.raw); 83 | this.isArray = isArray; 84 | this.id = id; 85 | this.version = 0; 86 | this.observers = []; 87 | this.lastTxnSet = {dependencies: this.emptyDependencies}; 88 | this.firstTxnSet = this.lastTxnSet; 89 | this.txnSetLength = 1; 90 | } 91 | TVar.prototype = { 92 | emptyDependencies: {}, 93 | 94 | txnSetLengthLimit: 64, // MAGIC NUMBER! 95 | 96 | subscribe: function (fun) { 97 | this.observers.push(fun); 98 | }, 99 | 100 | bump: function (future) { 101 | // Create a fresh observers array first, otherwise if 102 | // firing the observer creates a new subscription, you can 103 | // get into a loop! 104 | var observers = this.observers, 105 | i; 106 | this.version += 1; 107 | this.observers = []; 108 | for (i = 0; i < observers.length; i += 1) { 109 | (observers[i])(this, future); 110 | } 111 | }, 112 | 113 | appendDependencies: function (dependencies) { 114 | var txnSet = { dependencies: dependencies }; 115 | this.lastTxnSet.next = txnSet; 116 | this.lastTxnSet = txnSet; 117 | this.txnSetLength += 1; 118 | }, 119 | 120 | rollUpTxnSets: function () { 121 | var txnSet = this.firstTxnSet, 122 | dependencies = {}, 123 | ids, i, id, nextTxnSet; 124 | if (this.txnSetLength > this.txnSetLengthLimit) { 125 | // We're going to roll up everything, and then pop it 126 | // into the current lastTxnSet dependencies 127 | // field. This means that clients which are already 128 | // pointing there will not have to repeat work. Note 129 | // that because lastTxnSet.dependencies will be shared 130 | // across several TVars, we must create a new 131 | // lastTxnSet.dependencies which is non-shared, and 132 | // then only rewrite our own lastTxnSet.dependencies 133 | // field. 134 | 135 | while (undefined !== txnSet) { 136 | ids = Object.keys(txnSet.dependencies); 137 | for (i = 0; i < ids.length; i += 1) { 138 | id = ids[i]; 139 | dependencies[id] = txnSet.dependencies[id]; 140 | } 141 | // Wipe out the old dependencies to avoid work 142 | // later on, and rewrite pointers to enable faster 143 | // catchup. 144 | txnSet.dependencies = this.emptyDependencies; 145 | nextTxnSet = txnSet.next; 146 | txnSet.next = this.lastTxnSet; 147 | txnSet = nextTxnSet; 148 | } 149 | delete this.lastTxnSet.next; // remove the loop we just created! 150 | this.lastTxnSet.dependencies = dependencies; 151 | this.firstTxnSet = this.lastTxnSet; 152 | this.txnSetLength = 1; 153 | } 154 | } 155 | }; 156 | 157 | TVar.create = function (isArray, value, securityProvider) { 158 | var globalTVar; 159 | globalTVarCount += 1; 160 | globalTVar = new TVar(globalTVarCount, isArray, value); 161 | globalTVars[globalTVar.id] = globalTVar; 162 | return globalTVar; 163 | }; 164 | 165 | function CoalescingObserver() { 166 | this.map = new atomize.Map(); 167 | this.keys = []; 168 | } 169 | CoalescingObserver.prototype = { 170 | insert: function (key, value) { 171 | if (!this.map.has(key)) { 172 | this.keys.push(key); 173 | } 174 | this.map.set(key, value); 175 | }, 176 | 177 | force: function () { 178 | var i; 179 | for (i = 0; i < this.keys.length; i += 1) { 180 | (this.map.get(this.keys[i]))(); 181 | } 182 | this.keys = []; 183 | this.map = new atomize.Map(); 184 | } 185 | } 186 | 187 | function Client(connection, serverEventEmitter) { 188 | this.emitter = new events.EventEmitter(); 189 | util.liftFunctions( 190 | this.emitter, this, 191 | ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']); 192 | 193 | this.securityProvider = serverEventEmitter.securityProvider; 194 | this.connection = connection; 195 | this.id = connection.id; 196 | this.minTVarId = 0; 197 | 198 | this.localGlobal = {}; 199 | this.globalLocal = {}; 200 | globalLocal[connection.id] = this.globalLocal; 201 | 202 | this.addToMapping(rootTVar.id, rootTVar.id); 203 | 204 | connection.on('data', this.data.bind(this)); 205 | connection.on('close', this.close.bind(this)); 206 | serverEventEmitter.emit('connection', this); 207 | } 208 | 209 | Client.prototype = { 210 | isAuthenticated: false, 211 | 212 | data: function (message) { 213 | try { 214 | if (this.isAuthenticated) { 215 | this.dispatch(message); 216 | } else { 217 | this.emit('data', message, this); 218 | } 219 | } catch (err) { 220 | this.connection.close(500, "" + err); 221 | } 222 | }, 223 | 224 | close: function () { 225 | this.emit('close', this); 226 | delete globalLocal[this.connection.id]; 227 | }, 228 | 229 | write: function (msg) { 230 | this.connection.write(cereal.stringify(msg)); 231 | }, 232 | 233 | read: function (msg) { 234 | return cereal.parse(msg); 235 | }, 236 | 237 | dispatch: function (msg) { 238 | var txnLog = this.read(msg); 239 | switch (txnLog.type) { 240 | case "commit": 241 | this.commit(txnLog); 242 | break; 243 | case "retry": 244 | this.retry(txnLog); 245 | break; 246 | default: 247 | this.log("Received unexpected message from client:"); 248 | this.log(txnLog); 249 | } 250 | }, 251 | 252 | log: function () { 253 | var args = Array.prototype.slice.call(arguments, 0); 254 | args.unshift(this.connection.id + ":"); 255 | console.log.apply(console, args); 256 | }, 257 | 258 | addToMapping: function (globalTVarId, localTVarId) { 259 | if (undefined === localTVarId) { 260 | this.minTVarId -= 1; 261 | localTVarId = this.minTVarId; 262 | } 263 | this.localGlobal[localTVarId] = {id: globalTVarId, 264 | version: 0, 265 | txnSet: undefined}; 266 | this.globalLocal[globalTVarId] = localTVarId; 267 | return localTVarId; 268 | }, 269 | 270 | toGlobalTVar: function (localTVarId) { 271 | return globalTVars[this.localGlobal[localTVarId].id]; 272 | }, 273 | 274 | toGlobalTVarID: function (localTVarId) { 275 | return this.localGlobal[localTVarId].id; 276 | }, 277 | 278 | toLocalTVarID: function (globalTVarId) { 279 | return this.globalLocal[globalTVarId]; 280 | }, 281 | 282 | recordTVarVersion: function (localTVarId, globalTVar) { 283 | var entry = this.localGlobal[localTVarId]; 284 | entry.version = globalTVar.version; 285 | entry.txnSet = globalTVar.lastTxnSet; 286 | return entry; 287 | }, 288 | 289 | recordTVarUnpopulated: function (localTVarId, globalTVar) { 290 | var entry = this.localGlobal[localTVarId]; 291 | entry.version = 0; 292 | entry.txnSet = globalTVar.lastTxnSet; 293 | return entry; 294 | }, 295 | 296 | localTVarVersion: function (localTVarId) { 297 | return this.localGlobal[localTVarId].version; 298 | }, 299 | 300 | localTVarTxnSet: function (localTVarId) { 301 | // we've already done txnSet. Hence .next. 302 | var txnSet = this.localGlobal[localTVarId].txnSet; 303 | return undefined === txnSet ? txnSet : txnSet.next; 304 | }, 305 | 306 | create: function (txnLog) { 307 | var ids = Object.keys(txnLog.created), 308 | ok = true, 309 | i, localTVar, globalTVar; 310 | 311 | for (i = 0; i < ids.length; i += 1) { 312 | localTVar = txnLog.created[ids[i]]; 313 | globalTVar = TVar.create(localTVar.isArray, localTVar.value); 314 | if (this.securityProvider.lifted(this, globalTVar, localTVar.meta)) { 315 | this.addToMapping(globalTVar.id, ids[i]); 316 | } else { 317 | ok = false; 318 | } 319 | } 320 | return ok; 321 | }, 322 | 323 | checkThing: function (thing, updates, action, checkVersion) { 324 | var ids = Object.keys(thing), 325 | ok = true, 326 | i, localTVar, globalTVar; 327 | 328 | // Do not short circuit this! We want to send as many 329 | // updates as possible to the client. 330 | for (i = 0; i < ids.length; i += 1) { 331 | localTVar = thing[ids[i]]; 332 | globalTVar = this.toGlobalTVar(ids[i]); 333 | if (this.securityProvider[action](this, globalTVar, localTVar)) { 334 | if (checkVersion && localTVar.version !== globalTVar.version) { 335 | updates[globalTVar.id] = true; 336 | ok = false; 337 | } 338 | } else { 339 | ok = false; 340 | } 341 | } 342 | 343 | return ok; 344 | }, 345 | 346 | checkReads: function (txnLog, updates) { 347 | return this.checkThing(txnLog.read, updates, 'read', true); 348 | }, 349 | 350 | checkWrites: function (txnLog, updates) { 351 | return this.checkThing(txnLog.written, updates, 'written', false); 352 | }, 353 | 354 | bumpCreated: function (txnLog, dependencies) { 355 | // Regardless of success of commit or retry, we always 356 | // grab creates. If we read from, or wrote to any of the 357 | // objs we created, we will record in the txnlog that we 358 | // read from or wrote to version 0 of such objects. Thus 359 | // we delay the bumping to version 1 until after the 360 | // read/write checks, and we then update ourself to 361 | // remember we've seen version 1. 362 | var ids = Object.keys(txnLog.created), 363 | i, globalTVar; 364 | for (i = 0; i < ids.length; i += 1) { 365 | globalTVar = this.toGlobalTVar(ids[i]); 366 | globalTVar.bump(); 367 | globalTVar.appendDependencies(dependencies); 368 | dependencies[globalTVar.id] = globalTVar.version; 369 | this.recordTVarVersion(ids[i], globalTVar); 370 | } 371 | }, 372 | 373 | retry: function (txnLog) { 374 | var txnId = txnLog.txnId, 375 | updates = {}, 376 | dependencies = {}, 377 | ok, self, fired, observer, ids, i, localTVar, globalTVar; 378 | 379 | ok = this.create(txnLog); 380 | ok = this.checkReads(txnLog, updates) && ok; 381 | 382 | this.bumpCreated(txnLog, dependencies); 383 | 384 | if (ok) { 385 | // -fired is there to make sure we only send the 386 | // updates to this client once 387 | // - updates is there to collect all the updates 388 | // relevant for this txn together 389 | // - future is there to make sure we only do the 390 | // sending once the corresponding commit is fully done 391 | // and have thus built the biggest update we can 392 | self = this; 393 | fired = false; 394 | observer = function (globalTVar, future) { 395 | updates[globalTVar.id] = true; 396 | future.insert( 397 | updates, 398 | function () { 399 | if (!fired) { 400 | fired = true; 401 | self.sendUpdates(updates); 402 | self.write({type: 'result', txnId: txnId, result: 'restart'}); 403 | } 404 | }); 405 | }; 406 | 407 | ids = Object.keys(txnLog.read); 408 | for (i = 0; i < ids.length; i += 1) { 409 | (this.toGlobalTVar(ids[i])).subscribe(observer); 410 | } 411 | } else { 412 | // oh good, need to do updates already and can then 413 | // immediately restart the txn. 414 | this.sendUpdates(updates); 415 | this.write({type: 'result', txnId: txnId, result: 'restart'}); 416 | } 417 | }, 418 | 419 | commit: function (txnLog) { 420 | var txnId = txnLog.txnId, 421 | updates = {}, 422 | dependencies = {}, 423 | ok, future, globalIds, localIds, i, j, localTVar, globalTVar, names, name, desc; 424 | 425 | // Prevent a short-cut impl of && from causing us problems 426 | ok = this.create(txnLog); 427 | ok = this.checkReads(txnLog, updates) && ok; 428 | ok = this.checkWrites(txnLog, updates) && ok; 429 | 430 | this.bumpCreated(txnLog, dependencies); 431 | 432 | if (ok && this.onPreCommit(txnLog)) { 433 | // send out the success message first. This a) 434 | // improves performance; and more importantly b) 435 | // ensures that any updates that end up being sent to 436 | // the same client (due to pending retries) get 437 | // received after the success msg and thus we don't 438 | // fall out of step with vsns 439 | this.write({type: 'result', txnId: txnId, result: 'success'}); 440 | 441 | future = new CoalescingObserver(); 442 | 443 | // Once we've committed, and all tvars in this txn 444 | // have appended the new dependencies and been bumped, 445 | // only then consider rolling up. 446 | future.insert(dependencies, function () { 447 | globalIds = Object.keys(dependencies); 448 | for (i = 0; i < globalIds.length; i += 1) { 449 | globalTVars[globalIds[i]].rollUpTxnSets(); 450 | } 451 | }); 452 | 453 | localIds = Object.keys(txnLog.written); 454 | for (i = 0; i < localIds.length; i += 1) { 455 | localTVar = txnLog.written[localIds[i]]; 456 | globalTVar = this.toGlobalTVar(localIds[i]); 457 | names = Object.keys(localTVar); 458 | for (j = 0; j < names.length; j += 1) { 459 | name = names[j]; 460 | desc = localTVar[name]; 461 | if (util.hasOwnProp.call(desc, 'tvar')) { 462 | desc.value = {tvar: this.toGlobalTVarID(desc.tvar)}; 463 | delete desc.tvar; 464 | Object.defineProperty(globalTVar.raw, name, desc); 465 | } else if (util.hasOwnProp.call(desc, 'deleted')) { 466 | delete globalTVar.raw[name]; 467 | } else { 468 | // mess for dealing with arrays, and some proxy stuff 469 | if (globalTVar.isArray && 'length' === name) { 470 | globalTVar.raw[name] = desc.value; 471 | } else { 472 | Object.defineProperty(globalTVar.raw, name, desc); 473 | } 474 | } 475 | } 476 | globalTVar.bump(future); 477 | // wait until after the bump before recording new version 478 | globalTVar.appendDependencies(dependencies); 479 | dependencies[globalTVar.id] = globalTVar.version; 480 | this.recordTVarVersion(localIds[i], globalTVar); 481 | } 482 | 483 | future.force(); 484 | } else { 485 | this.sendUpdates(updates); 486 | this.write({type: 'result', txnId: txnId, result: 'failure'}); 487 | } 488 | }, 489 | 490 | onPreCommit: function (txnLog) { 491 | return true; 492 | }, 493 | 494 | sendUpdates: function (updates) { 495 | var globalIds = Object.keys(updates), 496 | unpopulateds = {}, 497 | txnLog = {type: "updates", updates: {}}, 498 | globalId, globalTVar, localId, localTVar, txnSet, 499 | globalIds1, globalId1, localId1, names, name, i, desc; 500 | 501 | while (0 < globalIds.length) { 502 | globalId = globalIds.shift(); 503 | globalTVar = globalTVars[globalId]; 504 | localTVar = {version: globalTVar.version, 505 | value: {}, 506 | isArray: globalTVar.isArray}; 507 | 508 | localId = this.toLocalTVarID(globalTVar.id); 509 | if (undefined === localId) { 510 | localId = this.addToMapping(globalTVar.id); 511 | txnLog.updates[localId] = localTVar; 512 | } else { 513 | if (this.localTVarVersion(localId) === globalTVar.version) { 514 | // Some earlier update caused us to send the 515 | // new version of this TVar down to the 516 | // client. Thus skip it here. 517 | continue; 518 | } else { 519 | txnLog.updates[localId] = localTVar; 520 | } 521 | } 522 | 523 | // Expand based on the dependencies of the globalTVar's txnSet chain 524 | txnSet = this.localTVarTxnSet(localId); 525 | if (undefined === txnSet) { 526 | txnSet = globalTVar.firstTxnSet; 527 | } 528 | while (undefined !== txnSet) { 529 | globalIds1 = Object.keys(txnSet.dependencies); 530 | for (i = 0; i < globalIds1.length; i += 1) { 531 | globalId1 = globalIds1[i]; 532 | if (! util.hasOwnProp.call(updates, globalId1)) { 533 | localId1 = this.toLocalTVarID(globalId1); 534 | if (undefined !== localId1 && 535 | this.localTVarVersion(localId1) < txnSet.dependencies[globalId1]) { 536 | globalIds.push(globalId1); 537 | updates[globalId1] = true; 538 | } 539 | } 540 | } 541 | txnSet = txnSet.next; 542 | } 543 | 544 | if (util.hasOwnProp.call(unpopulateds, globalId)) { 545 | localTVar.version = 0; 546 | this.recordTVarUnpopulated(localId, globalTVar); 547 | } else { 548 | this.recordTVarVersion(localId, globalTVar); 549 | 550 | // Expand based on the object's fields 551 | names = Object.getOwnPropertyNames(globalTVar.raw); 552 | names = this.securityProvider.filterUpdateNames(this, globalTVar, names); 553 | for (i = 0; i < names.length; i += 1) { 554 | name = names[i]; 555 | desc = Object.getOwnPropertyDescriptor(globalTVar.raw, name); 556 | desc = this.securityProvider.filterUpdateField(this, globalTVar, name, desc); 557 | localTVar.value[name] = desc; 558 | if ((!util.isPrimitive(desc.value)) && util.hasOwnProp.call(desc.value, 'tvar')) { 559 | localId1 = this.toLocalTVarID(desc.value.tvar); 560 | if (undefined === localId1) { 561 | if (! util.hasOwnProp.call(updates, desc.value.tvar)) { 562 | unpopulateds[desc.value.tvar] = true; 563 | globalIds.push(desc.value.tvar); 564 | } 565 | desc.tvar = this.addToMapping(desc.value.tvar); 566 | } else { 567 | desc.tvar = localId1; 568 | } 569 | // we are sending this tvar. So we can happily 570 | // ensure that no other txnSet can ask us to 571 | // send it 572 | updates[desc.value.tvar] = true; 573 | delete desc.value; 574 | } 575 | } 576 | } 577 | } 578 | 579 | if (0 < Object.keys(txnLog.updates).length) { 580 | this.write(txnLog); 581 | return true; 582 | } 583 | return false; 584 | } 585 | }; 586 | 587 | function ServerEventEmitter (securityProvider) { 588 | this.securityProvider = securityProvider; 589 | this.emitter = new events.EventEmitter(); 590 | util.liftFunctions( 591 | this.emitter, this, 592 | ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']); 593 | this.on('connection', this.defaultAuthenticator.bind(this)); 594 | } 595 | 596 | ServerEventEmitter.prototype = { 597 | connection: function (connection) { 598 | new Client(connection, this); 599 | }, 600 | 601 | defaultAuthenticator: function (client) { 602 | if (1 === this.emitter.listeners('connection').length) { 603 | // nothing installed other than us, just let it straight through! 604 | client.isAuthenticated = true; 605 | } 606 | } 607 | }; 608 | 609 | function DefaultSecurityProvider () { 610 | } 611 | DefaultSecurityProvider.prototype = { 612 | lifted: function (client, globalTVar, meta) { 613 | return true; 614 | }, 615 | 616 | read: function (client, globalTVar, localTVar) { 617 | return true; 618 | }, 619 | 620 | written: function (client, globalTVar, localTVar) { 621 | return true; 622 | }, 623 | 624 | filterUpdateNames: function (client, globalTVar, fieldNames) { 625 | return fieldNames; 626 | }, 627 | 628 | filterUpdateField: function (client, globalTVar, fieldName, desc) { 629 | return desc; 630 | } 631 | }; 632 | 633 | create = function (http, path, securityProvider, root) { 634 | var server = sockjs.createServer(sockjs_opts), 635 | emitter; 636 | 637 | if (undefined === path || null === path) { 638 | path = '[/]atomize'; 639 | } 640 | if (undefined === securityProvider || null === securityProvider) { 641 | securityProvider = new DefaultSecurityProvider(); 642 | } 643 | emitter = new ServerEventEmitter(securityProvider); 644 | 645 | server.on('connection', emitter.connection.bind(emitter)); 646 | http.addListener('upgrade', function (req, res) { res.end(); }); 647 | server.installHandlers(http, {prefix: path}); 648 | 649 | if (undefined === root || null === root || util.isPrimitive(root)) { 650 | root = {}; 651 | } 652 | globalTVarCount = 1; 653 | rootTVar = new TVar(globalTVarCount, Array.isArray(root), root); 654 | globalTVars[rootTVar.id] = rootTVar; 655 | rootTVar.bump(); 656 | 657 | emitter.client = function () { return new atomize.Atomize(Client, emitter); }; 658 | 659 | return emitter; 660 | }; 661 | 662 | exports.create = create; 663 | }()); 664 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atomize-server", 3 | "author": "Matthew Sackman", 4 | "version": "0.4.15", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/atomizejs/atomize-server-node.git" 8 | }, 9 | "main": "index", 10 | "description": "Node server library for AtomizeJS: JavaScript DSTM", 11 | "dependencies": { 12 | "cereal": ">=0.2.0", 13 | "atomize-client": ">=0.4.15", 14 | "sockjs": ">=0.3.0" 15 | }, 16 | "homepage": "http://atomizejs.github.com/", 17 | "keywords": ["software transactional memory", "stm", 18 | "distribution", "synchronisation", "synchronization", 19 | "rpc", "remote procedure call" 20 | ] 21 | } 22 | --------------------------------------------------------------------------------