shared-updated
Version:
Modern fork of shared (Kevin Jones), updated for latest Node.js and MongoDB
1,321 lines (1,320 loc) • 159 kB
JavaScript
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='../defs/lib.d.ts' />
/// <reference path='../defs/node-0.8.d.ts' />
/// <reference path='../defs/mongodb.d.ts' />
/// <reference path='../defs/rsvp.d.ts' />
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='utils.ts' />
/// <reference path='debug.ts' />
var shared;
(function (shared) {
(function (utils) {
var Tree = require('bintrees').RBTree;
var Map = (function () {
function Map(hashfn) {
utils.dassert(utils.isValue(hashfn));
this._size = 0;
this._hashfn = hashfn;
this._tree = new Tree(function (a, b) {
return a.hash - b.hash;
});
}
Map.prototype.size = function () {
return this._size;
};
Map.prototype.find = function (key) {
utils.dassert(utils.isValue(key));
var h = this._hashfn(key);
var entries = this._tree.find({
hash: h
});
if(entries !== null) {
for(var i = 0; i < entries.values.length; i++) {
if(utils.isEqual(key, entries.values[i].key)) {
return entries.values[i].value;
}
}
}
return null;
};
Map.prototype.insert = function (key, value) {
utils.dassert(utils.isValue(key));
utils.dassert(utils.isValue(value));
var h = this._hashfn(key);
var entries = this._tree.find({
hash: h
});
if(entries !== null) {
var free = null;
for(var i = 0; i < entries.values.length; i++) {
if(entries.values[i].key === null) {
if(free === null) {
free = i;
}
} else if(utils.isEqual(key, entries.values[i].key)) {
return false;
}
}
if(free !== null) {
entries.values[free] = {
key: key,
value: value
};
} else {
entries.values.push({
key: key,
value: value
});
}
} else {
this._tree.insert({
hash: h,
values: [
{
key: key,
value: value
}
]
});
}
this._size++;
return true;
};
Map.prototype.findOrInsert = function (key, proto) {
if (typeof proto === "undefined") { proto = {
}; }
var val = this.find(key);
if(val !== null) {
return val;
} else {
this.insert(key, proto);
return proto;
}
};
Map.prototype.remove = function (key) {
utils.dassert(utils.isValue(key));
var h = this._hashfn(key);
var entries = this._tree.find({
hash: h
});
if(entries !== null) {
var found = true;
for(var i = 0; i < entries.values.length; i++) {
if(utils.isEqual(key, entries.values[i].key)) {
entries.values[i].key = null;
entries.values[i].value = null;
this._size--;
return true;
}
}
}
return false;
};
Map.prototype.apply = function (handler) {
var it = this._tree.iterator();
while(it.next()) {
var row = it.data();
for(var i = 0; i < row.values.length; i++) {
if(row.values[i].key !== null) {
if(handler(row.values[i].key, row.values[i].value) === false) {
return false;
}
}
}
}
return true;
};
Map.prototype.removeAll = function () {
this._tree.clear();
this._size = 0;
};
return Map;
})();
utils.Map = Map;
/**
* A simple string set
*/
var StringSet = (function () {
function StringSet(names) {
if (typeof names === "undefined") { names = []; }
this._map = new Map(function (k) {
return utils.hash(k.toString());
});
this._id = 0;
for(var i = 0; i < names.length; i++) {
this.put(names[i]);
}
}
StringSet.prototype.put = function (key) {
var ok = this._map.insert(key, this._id);
if(ok) {
this._id++;
}
return ok;
};
StringSet.prototype.has = function (key) {
return this._map.find(key) !== null;
};
StringSet.prototype.id = function (key) {
return this._map.find(key);
};
StringSet.prototype.remove = function (key) {
return this._map.remove(key);
};
StringSet.prototype.size = function () {
return this._map.size();
};
StringSet.prototype.removeAll = function () {
return this._map.removeAll();
};
StringSet.prototype.apply = function (handler) {
return this._map.apply(function (key, value) {
return handler(key);
});
};
return StringSet;
})();
utils.StringSet = StringSet;
/**
* A simple queue, items can be added/removed from the
* head/tail with random access and assertions thrown in.
*/
var Queue = (function () {
function Queue() {
this._elems = [];
}
Queue.prototype.size = function () {
return this._elems.length;
};
Queue.prototype.empty = function () {
return this.size() === 0;
};
Queue.prototype.front = function () {
return this.at(0);
};
Queue.prototype.back = function () {
return this.at(this.size() - 1);
};
Queue.prototype.at = function (i) {
utils.dassert(i >= 0 && i < this.size());
return this._elems[i];
};
Queue.prototype.setAt = function (i, value) {
utils.dassert(i >= 0 && i < this.size());
this._elems[i] = value;
};
Queue.prototype.push = function (value) {
this._elems.push(value);
};
Queue.prototype.pop = function () {
utils.dassert(!this.empty());
return this._elems.pop();
};
Queue.prototype.unshift = function (value) {
this._elems.unshift(value);
};
Queue.prototype.shift = function () {
utils.dassert(!this.empty());
return this._elems.shift();
};
Queue.prototype.array = function () {
return this._elems;
};
Queue.prototype.first = function (match) {
for(var i = 0; i < this._elems.length; i++) {
if(match(this._elems[i])) {
return this._elems[i];
}
}
return null;
};
Queue.prototype.filter = function (match) {
var matched = new Queue();
for(var i = 0; i < this._elems.length; i++) {
if(match(this._elems[i])) {
matched.push(this._elems[i]);
}
}
return matched;
};
Queue.prototype.apply = function (func) {
for(var i = 0; i < this._elems.length; i++) {
func(this._elems[i]);
}
};
return Queue;
})();
utils.Queue = Queue;
})(shared.utils || (shared.utils = {}));
var utils = shared.utils;
// module utils
})(shared || (shared = {}));
// module shared
var __extends = this.__extends || function (d, b) {
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='collect.ts' />
var shared;
(function (shared) {
(function (utils) {
var fs = require('fs');
var assert = require('assert');
var util = require('util');
var cluster = require('cluster');
/**
* Log message levels, should really be an enum.
* Logs include messages for current level and higher. NONE turns off
* logging.
*/
utils.LogLevel = {
INFO: 1,
WARN: 2,
FATAL: 3,
NONE: 4
};
/**
* Writable FD
*/
var WriteableFD = (function () {
function WriteableFD(fd) {
this._fd = fd;
}
WriteableFD.prototype.write = function (str) {
var b = new Buffer(str);
fs.writeSync(this._fd, b, 0, b.length, null);
};
return WriteableFD;
})();
utils.WriteableFD = WriteableFD;
/**
* Logger helper
*/
var Logger = (function () {
function Logger(to, prefix, level, debug, next) {
this._to = to;
this._prefix = prefix;
this._level = level;
this._debug = new utils.StringSet(debug);
this._next = next;
}
Logger.prototype.logLevel = function () {
return this._level;
};
Logger.prototype.isDebugLogging = function (component) {
return this._debug.has(component);
};
Logger.prototype.enableDebugLogging = function (component, on) {
if(utils.isValue(on) && !on) {
this._debug.remove(component);
} else {
this._debug.put(component);
}
};
Logger.prototype.disableDebugLogging = function () {
this._debug.removeAll();
};
Logger.prototype.debug = function (component, fmt) {
var msgs = [];
for (var _i = 0; _i < (arguments.length - 2); _i++) {
msgs[_i] = arguments[_i + 2];
}
if(this.isDebugLogging(component)) {
var f = component + ': ' + fmt;
this.log(utils.LogLevel.INFO, f, msgs);
if(this._next) {
this._next.log(utils.LogLevel.INFO, f, msgs);
}
}
};
Logger.prototype.info = function (fmt) {
var msgs = [];
for (var _i = 0; _i < (arguments.length - 1); _i++) {
msgs[_i] = arguments[_i + 1];
}
this.log(utils.LogLevel.INFO, fmt, msgs);
if(this._next) {
this._next.log(utils.LogLevel.INFO, fmt, msgs);
}
};
Logger.prototype.warn = function (fmt) {
var msgs = [];
for (var _i = 0; _i < (arguments.length - 1); _i++) {
msgs[_i] = arguments[_i + 1];
}
this.log(utils.LogLevel.WARN, fmt, msgs);
if(this._next) {
this._next.log(utils.LogLevel.INFO, fmt, msgs);
}
};
Logger.prototype.fatal = function (fmt) {
var msgs = [];
for (var _i = 0; _i < (arguments.length - 1); _i++) {
msgs[_i] = arguments[_i + 1];
}
this.log(utils.LogLevel.FATAL, fmt, msgs);
if(this._next) {
this._next.log(utils.LogLevel.FATAL, fmt, msgs);
}
};
Logger.prototype.write = function (msg) {
this._to.write(msg);
if(this._next) {
this._next.write(msg);
}
};
Logger.prototype.trace = function (fmt) {
var msgs = [];
for (var _i = 0; _i < (arguments.length - 1); _i++) {
msgs[_i] = arguments[_i + 1];
}
var e = new Error();
e.name = 'Trace';
e.message = utils.dateFormat(this._prefix, fmt, msgs);
Error.captureStackTrace(e, arguments.callee);
this.write(e.stack + '\n');
};
Logger.prototype.log = function (type, fmt, msgs) {
switch(type) {
case utils.LogLevel.INFO:
if(this.logLevel() <= utils.LogLevel.INFO) {
this._to.write(utils.dateFormat(this._prefix + ' INFO', fmt, msgs));
}
break;
case utils.LogLevel.WARN:
if(this.logLevel() <= utils.LogLevel.WARN) {
this._to.write(utils.dateFormat(this._prefix + ' WARNING', fmt, msgs));
}
break;
case utils.LogLevel.FATAL:
if(this.logLevel() <= utils.LogLevel.FATAL) {
var err = utils.dateFormat(this._prefix + ' FATAL', fmt, msgs);
this._to.write(err);
if(!utils.isValue(this._next)) {
throw new Error('Fatal error: ' + err);
}
}
break;
case utils.LogLevel.NONE:
break;
default:
dassert(false);
break;
}
if(this._next) {
this._next.log(utils.LogLevel.INFO, fmt, msgs);
}
};
return Logger;
})();
utils.Logger = Logger;
/**
* File logger
*/
var FileLogger = (function (_super) {
__extends(FileLogger, _super);
function FileLogger(fileprefix, prefix, level, subjects, next) {
var w = this.openLog(fileprefix);
_super.call(this, w, prefix, level, subjects, next);
}
FileLogger.prototype.openLog = function (fileprefix) {
var i = 0;
while(true) {
var name = fileprefix + '-' + process.pid + '-' + i;
try {
var fd = fs.openSync(name, 'ax', '0666');
return new WriteableFD(fd);
} catch (e) {
// Try again with another suffix
i++;
if(i === 10) {
throw e;
}
}
}
};
return FileLogger;
})(Logger);
utils.FileLogger = FileLogger;
var _defaultLogger = null;
/**
* Set a logger to be used as the default for modules.
*/
function setdefaultLogger(logger) {
dassert(utils.isValue(logger));
_defaultLogger = logger;
}
utils.setdefaultLogger = setdefaultLogger;
/**
* Obtains the default logger. If one has not been set then logging is
* to process.stdout at the INFO level.
*/
function defaultLogger() {
if(!_defaultLogger) {
var prefix = 'master';
if(cluster.worker) {
prefix = 'work ' + cluster.worker.id;
}
_defaultLogger = new Logger(process.stdout, prefix, utils.LogLevel.INFO, []);
}
return _defaultLogger;
}
utils.defaultLogger = defaultLogger;
var _assertsEnabled = true;
/**
* Enable/Disable internal asserts.
*/
function enableAsserts(on) {
_assertsEnabled = on;
}
utils.enableAsserts = enableAsserts;
/**
* Are assert enabled?
*/
function assertsEnabled() {
return _assertsEnabled;
}
utils.assertsEnabled = assertsEnabled;
/**
* Switchable assert handler.
*/
function dassert(test) {
if(_assertsEnabled) {
assert.ok(test);
}
}
utils.dassert = dassert;
})(shared.utils || (shared.utils = {}));
var utils = shared.utils;
// module utils
})(shared || (shared = {}));
// module shared
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='debug.ts' />
var shared;
(function (shared) {
(function (utils) {
var _ = require('underscore');
var os = require('os');
/*
* String hash, see http://www.cse.yorku.ca/~oz/hash.html
*/
function hash(str, prime) {
utils.dassert(isValue(str));
var hash = 5381;
if(isValue(prime)) {
hash = prime;
}
var len = str.length;
for(var i = 0; i < len; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash;
}
return hash;
}
utils.hash = hash;
/**
* Deep Equals
*/
function isEqual(x, y) {
return _.isEqual(x, y);
}
utils.isEqual = isEqual;
/**
* Non-null or undefined value
*/
function isValue(arg) {
return arg !== undefined && arg !== null;
}
utils.isValue = isValue;
/**
* Non-null object value
*/
function isObject(value) {
return (value && typeof value === 'object' && !(value instanceof Array));
}
utils.isObject = isObject;
/**
* Non-null array value
*/
function isArray(value) {
return (value && typeof value === 'object' && (value instanceof Array));
}
utils.isArray = isArray;
/**
* Non-null object or array value
*/
function isObjectOrArray(value) {
return (value && typeof value === 'object');
}
utils.isObjectOrArray = isObjectOrArray;
/**
* Corrected type of value.
* Arrays & null are not 'objects'
*/
function typeOf(value) {
var s = typeof value;
if(s === 'object') {
if(value) {
if(value instanceof Array) {
s = 'array';
}
} else {
s = 'null';
}
}
return s;
}
utils.typeOf = typeOf;
/**
* Corrected type of value.
* Arrays & null are not 'objects'
* Objects return their prototype type.
*/
function treatAs(value) {
var s = typeof value;
if(s === 'object') {
if(value) {
return Object.prototype.toString.call(value).match(/^\[object\s(.*)\]$/)[1];
} else {
s = 'null';
}
}
return s;
}
utils.treatAs = treatAs;
function cloneArray(obj) {
utils.dassert(isArray(obj));
return obj.slice(0);
}
utils.cloneArray = cloneArray;
function cloneObject(obj) {
utils.dassert(isObject(obj));
var temp = {
};
for(var key in obj) {
temp[key] = obj[key];
}
return temp;
}
utils.cloneObject = cloneObject;
function clone(obj) {
if(isObject(obj)) {
return cloneObject(obj);
} else {
return cloneArray(obj);
}
}
utils.clone = clone;
// ES5 9.2
function toInteger(val) {
var v = +val;// toNumber conversion
if(isNaN(v)) {
return 0;
}
if(v === 0 || v === Infinity || v == -Infinity) {
return v;
}
if(v < 0) {
return -1 * Math.floor(-v);
} else {
return Math.floor(v);
}
}
utils.toInteger = toInteger;
function dateFormat(type, fmt, args) {
return new Date().toISOString() + ' ' + format(type, fmt, args);
}
utils.dateFormat = dateFormat;
function format(type, fmt, args) {
var m = '';
if(type !== null && type.length > 0) {
m += (type + ' ');
}
var i = 0;
var len = args.length;
var str = m + String(fmt).replace(/%[sdj%]/g, function (x) {
if(x === '%%') {
return '%';
}
if(i >= len) {
return x;
}
switch(x) {
case '%s':
return String(args[i++]);
case '%d':
return Number(args[i++]).toString();
case '%j':
return JSON.stringify(args[i++]);
default:
return x;
}
});
str += '\n';
for(var x = args[i]; i < len; x = args[++i]) {
if(x === null || typeof x !== 'object') {
str += x + '\n';
} else {
str += JSON.stringify(x, null, ' ') + '\n';
}
}
return str;
}
utils.format = format;
var _hostInfo = null;
function hostInfo() {
if(_hostInfo === null) {
_hostInfo = os.hostname();
var ifaces = os.networkInterfaces();
for(var dev in ifaces) {
var alias = 0;
ifaces[dev].forEach(function (details) {
if(details.family === 'IPv4' && details.address !== '127.0.0.1') {
_hostInfo += ' [' + details.address + ']';
++alias;
}
});
}
}
return _hostInfo;
}
utils.hostInfo = hostInfo;
function exceptionInfo(e) {
if(e instanceof Error) {
return e.stack;
} else {
return JSON.stringify(e);
}
}
utils.exceptionInfo = exceptionInfo;
})(shared.utils || (shared.utils = {}));
var utils = shared.utils;
// module utils
})(shared || (shared = {}));
// module shared
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
var shared;
(function (shared) {
(function (utils) {
var ObjectID = require('mongodb').ObjectID;
/*
* A network wide unique id wrapper.
* Pragmatically it must be a UUID and exposable as a string.
*/
utils.uidStringLength = 24;
function UID() {
return new ObjectID();
}
utils.UID = UID;
function isUID(a) {
return (a instanceof ObjectID);
}
utils.isUID = isUID;
function makeUID(id) {
var uid = new ObjectID(id);
utils.dassert(isUID(uid) && uid.toString() == id.toLowerCase());
return uid;
}
utils.makeUID = makeUID;
function toObjectID(id) {
return id;
}
utils.toObjectID = toObjectID;
/*
* Identifiable object helper
*/
var UniqueObject = (function () {
function UniqueObject() {
this._id = null;
}
UniqueObject.prototype.id = function () {
if(this._id === null) {
this._id = UID();
}
return this._id;
};
return UniqueObject;
})();
utils.UniqueObject = UniqueObject;
/*
* Map specialized for using id keys. A bodge until generics are supported.
*/
var IdMap = (function () {
function IdMap() {
this._map = new shared.utils.Map(shared.utils.hash);
}
IdMap.prototype.size = function () {
return this._map.size();
};
IdMap.prototype.find = function (key) {
return this._map.find(key.toString());
};
IdMap.prototype.insert = function (key, value) {
return this._map.insert(key.toString(), value);
};
IdMap.prototype.findOrInsert = function (key, proto) {
if (typeof proto === "undefined") { proto = {
}; }
return this._map.findOrInsert(key.toString(), proto);
};
IdMap.prototype.remove = function (key) {
return this._map.remove(key.toString());
};
IdMap.prototype.apply = function (handler) {
return this._map.apply(function (k, v) {
return handler(makeUID(k), v);
});
};
IdMap.prototype.removeAll = function () {
this._map.removeAll();
};
return IdMap;
})();
utils.IdMap = IdMap;
})(shared.utils || (shared.utils = {}));
var utils = shared.utils;
// module utils
})(shared || (shared = {}));
// module shared
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='utils.ts' />
/// <reference path='collect.ts' />
/// <reference path='id.ts' />
var shared;
(function (shared) {
(function (types) {
var TypeDesc = (function (_super) {
__extends(TypeDesc, _super);
function TypeDesc(isobj, props) {
_super.call(this);
this._isobj = isobj;
this._props = props;
}
TypeDesc.prototype.isobj = function () {
return this._isobj;
};
TypeDesc.prototype.isarray = function () {
return !this._isobj;
};
TypeDesc.prototype.props = function () {
return this._props;
};
TypeDesc.prototype.typeDesc = function () {
var props = 'o#';
if(this.isarray()) {
props = 'a#';
}
for(var i = 0; i < this._props.length; i++) {
props += this._props[i];
props += '#';
}
return props;
};
return TypeDesc;
})(shared.utils.UniqueObject);
types.TypeDesc = TypeDesc;
var TypeStore = (function () {
function TypeStore() {
shared.utils.dassert(TypeStore._instance == null);
this._tree = new shared.utils.Map(shared.utils.hash);
}
TypeStore.instance = function instance() {
if(!TypeStore._instance) {
TypeStore._instance = new TypeStore();
}
return TypeStore._instance;
};
TypeStore.prototype.type = function (obj) {
shared.utils.dassert(shared.utils.isObjectOrArray(obj));
var p = TypeStore.props(obj);
var td = this._tree.find(p);
if(td === null) {
var ps = p.split('#');
ps.shift();
ps.pop();
td = new TypeDesc(shared.utils.isObject(obj), ps);
this._tree.insert(p, td);
}
return td;
};
TypeStore.props = function props(obj) {
shared.utils.dassert(shared.utils.isObjectOrArray(obj));
var props = 'o#';
if(obj instanceof Array) {
props = 'a#';
}
for(var prop in obj) {
if(obj.hasOwnProperty(prop)) {
props += prop;
props += '#';
}
}
return props;
};
return TypeStore;
})();
types.TypeStore = TypeStore;
})(shared.types || (shared.types = {}));
var types = shared.types;
// types
})(shared || (shared = {}));
// shared
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='utils.ts' />
/// <reference path='id.ts' />
var shared;
(function (shared) {
(function (serial) {
/*
* Object/Array reference holder. Used to represent a reference when
* de-serialising data.
*/
var Reference = (function () {
function Reference(id) {
shared.utils.dassert(shared.utils.isUID(id));
this._id = id;
}
Reference.prototype.id = function () {
return this._id;
};
return Reference;
})();
serial.Reference = Reference;
/*
* Append serialized form of an object/array onto the supplied string.
* Returns the passed string.
*/
function writeObject(rh, obj, to, identify) {
if (typeof to === "undefined") { to = ''; }
if (typeof identify === "undefined") { identify = false; }
shared.utils.dassert(shared.utils.isObjectOrArray(rh));
shared.utils.dassert(shared.utils.isObjectOrArray(obj));
if(obj instanceof Array) {
to += '[';
} else {
to += '{';
}
if(identify) {
to += rh.valueId(obj) + ' ';
to += rh.valueRev(obj) + ' ';
}
var k = Object.keys(obj);
for(var i = 0; i < k.length; i++) {
to = writeValue(rh, k[i], to);
to += ":";
to = writeValue(rh, obj[k[i]], to);
if(i < k.length - 1) {
to += ',';
}
}
if(obj instanceof Array) {
to += ']';
} else {
to += '}';
}
return to;
}
serial.writeObject = writeObject;
/*
* Append serialized form of a value onto the supplied string.
* Object/Array values are serialised by reference, see writeObject() for
* full serialisation of object/array properties. Returns the passed string.
*/
function writeValue(rh, value, to) {
if (typeof to === "undefined") { to = ''; }
shared.utils.dassert(shared.utils.isObject(rh));
var type = shared.utils.treatAs(value);
switch(type) {
case 'null':
to += 'null';
break;
case 'undefined':
to += 'undefined';
break;
case 'number':
case 'Number':
case 'boolean':
case 'Boolean':
to += value.toString();
break;
case 'string':
case 'String':
to += JSON.stringify(value);
break;
case 'Date':
to += JSON.stringify(value.toString());
break;
case 'Object':
case 'Array':
to += '<' + rh.valueId(value) + '>';
break;
case 'function':
case 'RegExp':
case 'Error':
to += 'null';
break;
default:
shared.utils.defaultLogger().fatal('Unexpected type: %s', type);
break;
}
return to;
}
serial.writeValue = writeValue;
function readObject(str, proto) {
shared.utils.dassert(str.length > 1 && (str.charAt(0) === '[' || str.charAt(0) === '{') && (str.charAt(str.length - 1) === ']' || str.charAt(str.length - 1) === '}'));
// Check is we have a proto & its the right type
if(str.charAt(0) === '{') {
if(!shared.utils.isValue(proto)) {
proto = {
};
} else {
shared.utils.dassert(shared.utils.isObject(proto));
}
} else {
if(!shared.utils.isValue(proto)) {
proto = [];
} else {
shared.utils.dassert(shared.utils.isArray(proto));
// Prop delete does not work well on arrays so zero proto
proto.length = 0;
}
}
// Read props
var rs = new ReadStream(str.substr(1, str.length - 2));
var keys = Object.keys(proto);
var k = 0;
while(true) {
rs.skipWS();
if(rs.eof()) {
break;
}
// Read prop name
var prop = rs.readNextValue();
shared.utils.dassert(typeof prop === 'string');
// Delete rest of proto props if does not match what is being read
if(k !== -1 && prop != keys[k]) {
for(var i = k; i < keys.length; i++) {
delete proto[keys[i]];
}
k = -1;
}
// Skip ':'
rs.skipWS();
shared.utils.dassert(!rs.eof());
shared.utils.dassert(rs.peek() === ':');
rs.skip();
rs.skipWS();
// Read value & assign
var value = rs.readNextValue();
proto[prop] = value;
// Skip ',' if present
rs.skipWS();
if(!rs.eof()) {
shared.utils.dassert(rs.peek() === ',');
rs.skip();
rs.skipWS();
} else {
break;
}
}
return proto;
}
serial.readObject = readObject;
/*
* Read a value as encoded by writeValue. The passed string must contain
* one complete value with no leading or trailing characters. May return
* null if passed 'null'.
*/
function readValue(str) {
shared.utils.dassert(shared.utils.isValue(str));
var rs = new ReadStream(str);
return rs.readNextValue();
}
serial.readValue = readValue;
var ReadStream = (function () {
function ReadStream(from) {
shared.utils.dassert(shared.utils.isValue(from));
this._from = from;
this._at = 0;
}
ReadStream._numberPat = /^-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+][0-9]+)?/;
ReadStream.prototype.eof = function () {
return this._at >= this._from.length;
};
ReadStream.prototype.skip = function (n) {
if (typeof n === "undefined") { n = 1; }
this._at += n;
};
ReadStream.prototype.skipWS = function () {
while(this._at < this._from.length && (this._from[this._at] === ' ' || this._from[this._at] === '\t')) {
this._at++;
}
};
ReadStream.prototype.peek = function (n) {
if (typeof n === "undefined") { n = 0; }
shared.utils.dassert(this._at + n < this._from.length);
return this._from[this._at + n];
};
ReadStream.prototype.readNextValue = /*
* Read a value as encoded by writeValue. The passed string must contain
* one complete value with no leading or trailing characters. May return
* null if passed 'null'.
*/
function () {
// Simple things first
if(this._from.substr(this._at, 4) === 'null') {
this._at += 4;
return null;
} else if(this._from.substr(this._at, 9) === 'undefined') {
this._at += 9;
return undefined;
} else if(this._from.substr(this._at, 4) === 'true') {
this._at += 4;
return true;
} else if(this._from.substr(this._at, 5) === 'false') {
this._at += 5;
return false;
} else if(this._from.substr(this._at, 3) === 'NaN') {
this._at += 3;
return NaN;
} else if(this._from.substr(this._at, 8) === 'Infinity') {
this._at += 8;
return Infinity;
} else if(this._from.substr(this._at, 9) === '-Infinity') {
this._at += 9;
return -Infinity;
}
// JSON escaped string?
if(this._from.charAt(this._at) === '"') {
var end = this._at + 1;
while(end < this._from.length) {
if(this._from.charAt(end) === '\\') {
end += 1;
} else if(this._from.charAt(end) === '"') {
break;
}
end += 1;
}
if(end < this._from.length) {
var s = this._from.substr(this._at, end - this._at + 1);
this._at = end + 1;
return JSON.parse(s);
}
}
// Reference?
if(this._from.charAt(this._at) === '<' && this._from.charAt(this._at + 1 + shared.utils.uidStringLength) === '>') {
var id = this._from.substr(this._at + 1, shared.utils.uidStringLength);
this._at += (2 + shared.utils.uidStringLength);
return new Reference(shared.utils.makeUID(id));
}
// Maybe a number
var l = this.numberLength();
if(l) {
var n = parseFloat(this._from.substr(this._at));
shared.utils.dassert(!isNaN(n));
this._at += l;
return n;
}
shared.utils.defaultLogger().fatal('Unexpected value encoding: %s', this._from.substr(this._at));
};
ReadStream.prototype.numberLength = function () {
var ex = ReadStream._numberPat.exec(this._from.substr(this._at));
if(ex) {
return ex[0].length;
} else {
return 0;
}
};
return ReadStream;
})();
})(shared.serial || (shared.serial = {}));
var serial = shared.serial;
// tracker
})(shared || (shared = {}));
// shared
// Copyright (c) Kevin Jones. All rights reserved. Licensed under the Apache
// License, Version 2.0. See LICENSE.txt in the project root for complete
// license information.
/// <reference path='import.ts' />
/// <reference path='utils.ts' />
/// <reference path='id.ts' />
/// <reference path='types.ts' />
/// <reference path='serial.ts' />
/*
* Tracking provides a core service to enabling monitoring of how objects
* an arrays are changed over some period. It has similar motives to the
* proposed Object.observe model but is specifically designed to be
* node portable & suitable for distributed transactions.
*
* This code generates raw tracking logs. They need post-processing for
* most use cases, see mtx.ts for code that does this in this case.
*/
var shared;
(function (shared) {
(function (tracker) {
var Buffer = require('buffer');
/*
* Exception for indicating the cache is missing an object
* needed for navigation.
*/
var UnknownReference = (function () {
// Id of missing object
function UnknownReference(id, prop, missing) {
this._id = id;
this._prop = prop;
this._missing = missing;
}
UnknownReference.prototype.id = function () {
return this._id;
};
UnknownReference.prototype.prop = function () {
return this._prop;
};
UnknownReference.prototype.missing = function () {
return this._missing;
};
return UnknownReference;
})();
tracker.UnknownReference = UnknownReference;
/*
* Recover the tracker for an object/array, may return null
*/
function getTrackerUnsafe(value) {
if(value._tracker === undefined) {
return null;
}
return value._tracker;
}
tracker.getTrackerUnsafe = getTrackerUnsafe;
/*
* Recover the tracker for an object/array
*/
function getTracker(value) {
shared.utils.dassert(shared.utils.isObject(value._tracker));
return value._tracker;
}
tracker.getTracker = getTracker;
/*
* Test if object is tracked
*/
function isTracked(value) {
return shared.utils.isObject(value._tracker);
}
tracker.isTracked = isTracked;
/*
* Object/Array tracker. Construct this over an object/array and it will
* attach itself to that object/array as a non-enumerable '_tracker' property.
* This is kind of odd, but saves doing object->tracker lookups. The downside
* is to avoid a circular ref many tracker methods must be passed the objects
* they are tracking as this is not recorded in the tracker itself.
*
* The tracker wraps the enumerable properties of the object/array so that
* it can log reads to other objects/arrays and any mutations. The log can
* be accessed via changes().
*
* The mechanics here are messy so I have simply tried to write this as correct
* rather than as quick & correct. A bit of extra thought can probably
* improve the performance a lot.
*/
var Tracker = (function () {
function Tracker(tc, obj, id, rev) {
if (typeof id === "undefined") { id = shared.utils.UID(); }
shared.utils.dassert(shared.utils.isObject(tc));
shared.utils.dassert(shared.utils.isUID(id));
// Error check
if(obj === null || typeof (obj) !== 'object') {
shared.utils.defaultLogger().fatal('Trying to track non-object/array type');
}
if(obj.hasOwnProperty('_tracker')) {
shared.utils.defaultLogger().fatal('Trying to track already tracked object or array');
}
// Init
this._tc = tc;
this._rev = rev || 0;
this._id = id;
this._lastTx = -1;
this._id = id;
this._type = shared.types.TypeStore.instance().type(obj);
this._userdata = null;
this._ref = 0;
// Add tracker to object
Object.defineProperty(obj, '_tracker', {
value: this
});
// Start tracking
if(obj instanceof Array) {
trackArray(obj);
}
for(var prop in obj) {
this.track(obj, prop);
}
}
Tracker.prototype.kill = /*
* When trackers die they lose connection to the cache. Normally
* they die when changes to the object can not be undone and so
* the object needs to be refreshed from the master cache.
*/
function () {
this._tc = null;
};
Tracker.prototype.isDead = /*
* Has this tracker/object combo died
*/
function () {
return this._tc === null;
};
Tracker.prototype.tc = /**
* Get the tracker cache this tracker is using
*/
function () {
return this._tc;
};
Tracker.prototype.id = /**
* Get the unique object id
*/
function () {
return this._id;
};
Tracker.prototype.type = /**
* Get the objects (pre-changes) type
*/
function () {
return this._type;
};
Tracker.prototype.rev = /**
* Get/Increment the object revision, returning new value
*/
function (by) {
if(by !== undefined) {
this._rev += by;
}
return this._rev;
};
Tracker.prototype.setRev = /**
* Set object rev to a value, must be >= to existing rev
*/
function (to) {
if(to