Socket
Socket
Sign inDemoInstall

data

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

data - npm Package Compare versions

Comparing version 0.4.1 to 0.6.0

.npmignore

1565

data.js

@@ -1,2 +0,2 @@

// (c) 2011 Michael Aufreiter
// (c) 2012 Michael Aufreiter
// Data.js is freely distributable under the MIT license.

@@ -6,3 +6,3 @@ // Portions of Data.js are inspired or borrowed from Underscore.js,

// For all details and documentation:
// http://substance.io/#michael/data-js
// http://substance.io/michael/data-js

@@ -24,3 +24,3 @@ (function(){

// Current version of the library. Keep in sync with `package.json`.
Data.VERSION = '0.4.1';
Data.VERSION = '0.6.0';

@@ -31,3 +31,2 @@ // Require Underscore, if we're on the server, and it's not already present.

// Top Level API

@@ -43,56 +42,9 @@ // -------

];
Data.isValueType = function (type) {
return _.include(Data.VALUE_TYPES, type);
return _.include(Data.VALUE_TYPES, _.last(type));
};
// Returns true if a certain object matches a particular query object
// TODO: optimize!
Data.matches = function(node, queries) {
queries = _.isArray(queries) ? queries : [queries];
var matched = false;
// Matches at least one query
_.each(queries, function(query) {
if (matched) return;
var rejected = false;
_.each(query, function(value, key) {
if (rejected) return;
var condition;
// Extract operator
var matches = key.match(/^([a-z_]{1,30})(=|==|!=|>|>=|<|<=|\|=|&=)?$/),
property = matches[1],
operator = matches[2] || (property == "type" || _.isArray(value) ? "|=" : "=");
if (operator === "|=") { // one of operator
var values = _.isArray(value) ? value : [value];
var objectValues = _.isArray(node[property]) ? node[property] : [node[property]];
condition = false;
_.each(values, function(val) {
if (_.include(objectValues, val)) {
condition = true;
}
});
} else if (operator === "&=") {
var values = _.isArray(value) ? value : [value];
var objectValues = _.isArray(node[property]) ? node[property] : [node[property]];
condition = _.intersect(objectValues, values).length === values.length;
} else { // regular operators
switch (operator) {
case "!=": condition = !_.isEqual(node[property], value); break;
case ">": condition = node[property] > value; break;
case ">=": condition = node[property] >= value; break;
case "<": condition = node[property] < value; break;
case "<=": condition = node[property] <= value; break;
default : condition = _.isEqual(node[property], value); break;
}
}
// TODO: Make sure we exit the loop and return immediately when a condition is not met
if (!condition) return rejected = true;
});
if (!rejected) return matched = true;
});
return matched;
};
/*!

@@ -136,747 +88,41 @@ Math.uuid.js (v1.4)

// Helpers
// -------
// _.Events (borrowed from Backbone.js)
// -----------------
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may `bind` or `unbind` a callback function to an event;
// `trigger`-ing an event fires all callbacks in succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.bind('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
_.Events = {
// Bind an event, specified by a string name, `ev`, to a `callback` function.
// Passing `"all"` will bind the callback to all events fired.
bind : function(ev, callback) {
var calls = this._callbacks || (this._callbacks = {});
var list = this._callbacks[ev] || (this._callbacks[ev] = []);
list.push(callback);
return this;
},
// Remove one or many callbacks. If `callback` is null, removes all
// callbacks for the event. If `ev` is null, removes all bound callbacks
// for all events.
unbind : function(ev, callback) {
var calls;
if (!ev) {
this._callbacks = {};
} else if (calls = this._callbacks) {
if (!callback) {
calls[ev] = [];
} else {
var list = calls[ev];
if (!list) return this;
for (var i = 0, l = list.length; i < l; i++) {
if (callback === list[i]) {
list.splice(i, 1);
break;
}
}
}
}
return this;
},
// Trigger an event, firing all bound callbacks. Callbacks are passed the
// same arguments as `trigger` is, apart from the event name.
// Listening for `"all"` passes the true event name as the first argument.
trigger : function(ev) {
var list, calls, i, l;
if (!(calls = this._callbacks)) return this;
if (list = calls[ev]) {
for (i = 0, l = list.length; i < l; i++) {
list[i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
if (list = calls['all']) {
for (i = 0, l = list.length; i < l; i++) {
list[i].apply(this, arguments);
}
}
return this;
}
};
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
// Taken from Underscore.js (c) Jeremy Ashkenas
_.inherits = function(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call `super()`.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`, for `instanceof`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
// Data.Hash
// Data.Query
// --------------
// A Hash data structure that provides a simple layer of abstraction for
// managing a sortable data-structure with hash semantics. It's heavily
// used throughout Data.js.
Data.Hash = function(data) {
var that = this;
this.data = {};
this.keyOrder = [];
this.length = 0;
// Query module to be mixed into Data.Graph and Data.Collection data structures
// No indexing yet, this has been shifted to Data.js 0.7.0
if (data instanceof Array) {
_.each(data, function(datum, index) {
that.set(index, datum);
});
} else if (data instanceof Object) {
_.each(data, function(datum, key) {
that.set(key, datum);
});
}
if (this.initialize) this.initialize(attributes, options);
};
Data.Query = {
_.extend(Data.Hash.prototype, _.Events, {
// Returns all objects matching a particular query object
query: function(qry) {
// Returns a copy of the Hash
// Used by transformation methods
clone: function () {
var copy = new Data.Hash();
copy.length = this.length;
_.each(this.data, function(value, key) {
copy.data[key] = value;
});
copy.keyOrder = this.keyOrder.slice(0, this.keyOrder.length);
return copy;
},
// Set a value at a given *key*
set: function (key, value, targetIndex) {
var index;
if (key === undefined)
return this;
if (!this.data[key]) {
if (targetIndex !== undefined) { // insert at a given index
var front = this.select(function(item, key, i) {
return i < targetIndex;
});
var back = this.select(function(item, key, i) {
return i >= targetIndex;
});
this.keyOrder = [].concat(front.keyOrder);
this.keyOrder.push(key);
this.keyOrder = this.keyOrder.concat(back.keyOrder);
} else {
this.keyOrder.push(key);
}
index = this.length;
this.length += 1;
} else {
index = this.index(key);
function toArray(v) {
return _.isArray(v) ? v : [v];
}
this.data[key] = value;
this[index] = this.data[key];
this.trigger('set', key);
return this;
},
// Delete entry at given *key*
del: function (key) {
if (this.data[key]) {
var l = this.length;
var index = this.index(key);
delete this.data[key];
this.keyOrder.splice(index, 1);
Array.prototype.splice.call(this, index, 1);
this.length = l-1;
this.trigger('del', key);
}
return this;
},
// Get value at given *key*
get: function (key) {
return this.data.hasOwnProperty(key) ? this.data[key] : undefined;
},
// Get value at given *index*
at: function (index) {
var key = this.keyOrder[index];
return this.data[key];
},
// Get first item
first: function () {
return this.at(0);
},
// Returns a sub-range of the current *hash*
range: function(start, end) {
var result = new Data.Hash();
for(var i=start; i<=end; i++) {
result.set(this.key(i), this.at(i));
}
return result;
},
// Returns the rest of the elements.
// Pass an index to return the items from that index onward.
rest: function(index) {
return this.range(index, this.length-1);
},
// Get last item
last: function () {
return this.at(this.length-1);
},
// Returns for an index the corresponding *key*
key: function (index) {
return this.keyOrder[index];
},
// Returns for a given *key* the corresponding *index*
index: function(key) {
return this.keyOrder.indexOf(key);
},
// Iterate over values contained in the `Data.Hash`
each: function (fn) {
var that = this;
_.each(this.keyOrder, function(key, index) {
fn.call(that, that.data[key], key, index);
});
return this;
},
// Convert to an ordinary JavaScript Array containing just the values
values: function () {
var result = [];
this.each(function(value, key, index) {
result.push(value);
});
return result;
},
// Returns all keys in current order
keys: function () {
return _.clone(this.keyOrder);
},
// Convert to an ordinary JavaScript Array containing
// key value pairs. Used by `sort`.
toArray: function () {
var result = [];
this.each(function(value, key) {
result.push({key: key, value: value});
});
return result;
},
// Serialize
toJSON: function() {
var result = {};
this.each(function(value, key) {
result[key] = value.toJSON ? value.toJSON() : value;
});
return result;
},
function match(obj, qry) {
var matched = true;
// Map the `Data.Hash` to your needs
map: function (fn) {
var result = this.clone(),
that = this;
result.each(function(item, key, index) {
result.data[that.key(index)] = fn.call(result, item);
});
return result;
},
// Select items that match some conditions expressed by a matcher function
select: function (fn) {
var result = new Data.Hash(),
that = this;
this.each(function(value, key, index) {
if (fn.call(that, value, key, index)) {
result.set(key, value);
}
});
return result;
},
// Performs a sort
sort: function (comparator) {
var result = this.clone();
sortedKeys = result.toArray().sort(comparator);
// update keyOrder
result.keyOrder = _.map(sortedKeys, function(k) {
return k.key;
});
return result;
},
// Performs an intersection with the given *hash*
intersect: function(hash) {
var that = this,
result = new Data.Hash();
// Ensure that is the smaller one
if (hash.length < that.length) {
that = hash;
hash = this;
}
that.each(function(value,key) {
if (hash.get(key)) result.set(key, value);
});
return result;
},
// Performs an union with the given *hash*
union: function(hash) {
var that = this,
result = new Data.Hash();
this.each(function(value, key) {
result.set(key, value);
});
hash.each(function(value, key) {
if (!result.get(key)) result.set(key, value);
});
return result;
},
// Computes the difference between the current *hash* and a given *hash*
difference: function(hash) {
var that = this;
result = new Data.Hash();
this.each(function(value, key) {
if (!hash.get(key)) result.set(key, value);
});
return result;
}
});
// Data.Comparators
// --------------
Data.Comparators = {};
Data.Comparators.ASC = function(item1, item2) {
return item1.value === item2.value ? 0 : (item1.value < item2.value ? -1 : 1);
};
Data.Comparators.DESC = function(item1, item2) {
return item1.value === item2.value ? 0 : (item1.value > item2.value ? -1 : 1);
};
// Data.Aggregators
// --------------
Data.Aggregators = {};
Data.Aggregators.SUM = function (values) {
var result = 0;
values.each(function(value, key, index) {
if (_.isNumber(value)) result += value;
});
return result;
};
Data.Aggregators.MIN = function (values) {
var result = Infinity;
values.each(function(value, key, index) {
if (_.isNumber(value) && value < result) result = value;
});
return result;
};
Data.Aggregators.MAX = function (values) {
var result = -Infinity;
values.each(function(value, key, index) {
if (_.isNumber(value) && value > result) result = value;
});
return result;
};
Data.Aggregators.AVG = function (values) {
var sum = 0,
count = 0;
values.each(function(value, key, index) {
if (_.isNumber(value)) {
sum += value;
count += 1;
}
});
return count === 0 ? 0 : (sum / count);
};
Data.Aggregators.COUNT = function (values) {
return values.length;
};
// Data.Modifiers
// --------------
Data.Modifiers = {};
// The default modifier simply does nothing
Data.Modifiers.DEFAULT = function (attribute) {
return attribute;
};
Data.Modifiers.MONTH = function (attribute) {
return attribute.getMonth();
};
Data.Modifiers.QUARTER = function (attribute) {
return Math.floor(attribute.getMonth() / 3) + 1;
};
// Data.Transformers
// --------------
Data.Transformers = {
group: function(g, type, keys, properties) {
var gspec = {},
type = g.get(type),
groups = {},
count = 0;
gspec[type._id] = {"type": "/type/type", "properties": {}, indexes: type.indexes};
// Include group keys to the output graph
_.each(keys, function(key) {
gspec[type._id].properties[key] = type.properties().get(key).toJSON();
});
// Include additional properties
_.each(properties, function(options, key) {
var p = type.properties().get(options.property || key).toJSON();
if (options.name) p.name = options.name;
gspec[type._id].properties[key] = p;
});
var groupedGraph = new Data.Graph(gspec);
_.each(keys, function(key) {
groups[key] = type.properties().get(key).all('values');
});
function aggregate(key) {
var members = new Data.Hash();
_.each(keys, function(k, index) {
var objects = groups[keys[index]].get(key[index]).referencedObjects;
members = index === 0 ? members.union(objects) : members.intersect(objects);
});
// Empty group key
if (key.length === 0) members = g.objects();
if (members.length === 0) return null;
var res = {type: type._id};
_.each(gspec[type._id].properties, function(p, pk) {
if (_.include(keys, pk)) {
res[pk] = key[_.indexOf(keys, pk)];
} else {
var numbers = members.map(function(obj) {
return obj.get(properties[pk].property || pk);
});
var aggregator = properties[pk].aggregator || Data.Aggregators.SUM;
res[pk] = aggregator(numbers);
_.find(qry, function(value, property) {
var val = property === "type" ? obj.types : obj.properties[property];
var matchedValues = _.intersect(toArray(value), toArray(val));
if (matchedValues.length === 0) {
matched = false;
return true;
}
});
return res;
return matched;
}
function extractGroups(keyIndex, key) {
if (keyIndex === keys.length-1) {
var aggregatedItem = aggregate(key);
if (aggregatedItem) groupedGraph.set(key.join('::'), aggregatedItem);
} else {
keyIndex += 1;
groups[keys[keyIndex]].each(function(grp, grpkey) {
extractGroups(keyIndex, key.concat([grpkey]));
});
}
}
extractGroups(-1, []);
return groupedGraph;
}
};
// Data.Node
// --------------
// JavaScript Node implementation that hides graph complexity from
// the interface. It introduces properties, which group types of edges
// together. Therefore multi-partite graphs are possible without any hassle.
// Every Node simply contains properties which conform to outgoing edges.
// It makes heavy use of hashing through JavaScript object properties to
// allow random access whenever possible. If I've got it right, it should
// perform sufficiently fast, allowing speedy graph traversals.
Data.Node = function(options) {
this.nodeId = Data.Node.generateId();
if (options) {
this.val = options.value;
}
this._properties = {};
if (this.initialize) this.initialize(options);
};
Data.Node.nodeCount = 0;
// Generates a unique id for each node
Data.Node.generateId = function () {
return Data.Node.nodeCount += 1;
};
_.extend(Data.Node.prototype, _.Events, {
// Node identity, which is simply the node's id
identity: function() {
return this.nodeId;
},
// Replace a property with a complete `Hash`
replace: function(property, hash) {
this._properties[property] = hash;
},
// Set a Node's property
//
// Takes a property key, a value key and value. Values that aren't
// instances of `Data.Node` wrapped are automatically.
set: function (property, key, value) {
if (!this._properties[property]) {
this._properties[property] = new Data.Hash();
}
this._properties[property].set(key, value instanceof Data.Node ? value : new Data.Node({value: value}));
return this;
},
// Get node for given *property* at given *key*
get: function (property, key) {
if (key !== undefined && this._properties[property] !== undefined) {
return this._properties[property].get(key);
}
},
// Get all connected nodes at given *property*
all: function(property) {
return this._properties[property];
},
// Get first connected node at given *property*
//
// Useful if you want to mimic the behavior of unique properties.
// That is, if you know that there's always just one associated node
// at a given property.
first: function(property) {
var p = this._properties[property];
return p ? p.first() : null;
},
// Value of first connected target node at given *property*
value: function(property) {
return this.values(property).first();
},
// Values of associated target nodes for non-unique properties
values: function(property) {
if (!this.all(property)) return new Data.Hash();
return this.all(property).map(function(n) {
return n.val;
var type = this.get(qry.type);
var objects = _.select(this.objects, function(o) {
return match(o, qry);
});
return Data.Collection.create(type, objects);
}
});
// Data.Adapter
// --------------
// An abstract interface for writing and reading Data.Graphs.
Data.Adapter = function(config) {
// The config object is used to describe database credentials
this.config = config;
};
// Namespace where Data.Adapters can register
Data.Adapters = {};
// Data.Property
// --------------
// Meta-data (data about data) is represented as a set of properties that
// belongs to a certain `Data.Type`. A `Data.Property` holds a key, a name
// and an expected type, telling whether the data is numeric or textual, etc.
Data.Property = _.inherits(Data.Node, {
constructor: function(type, id, options) {
Data.Node.call(this);
this.key = id;
this._id = id;
this.type = type;
this.unique = options.unique;
this.name = options.name;
this.meta = options.meta || {};
this.validator = options.validator;
this.required = options["required"];
this["default"] = options["default"];
// TODO: ensure that object and value types are not mixed
this.expectedTypes = _.isArray(options['type']) ? options['type'] : [options['type']];
this.replace('values', new Data.Hash());
},
// TODO: this desctroys Data.Node#values
// values: function() {
// return this.all('values');
// },
isValueType: function() {
return Data.isValueType(this.expectedTypes[0]);
},
isObjectType: function() {
return !this.isValueType();
},
// Register values of a certain object
registerValues: function(values, obj) {
var that = this;
var res = new Data.Hash();
_.each(values, function(v, index) {
if (v === undefined) return; // skip
var val;
// Skip registration for object type values
// TODO: check edge cases!
if (that.isValueType() && that.expectedTypes[0] === 'object') {
val = new Data.Node({value: v});
res.set(index, val);
return;
}
// Check if we can recycle an old value of that object
if (obj.all(that.key)) val = obj.all(that.key).get(v);
if (!val) { // Can't recycle
val = that.get('values', v);
if (!val) {
// Well, a new value needs to be created
if (that.isObjectType()) {
// Create on the fly if an object is passed as a value
if (typeof v === 'object') v = that.type.g.set(null, v)._id;
val = that.type.g.get('nodes', v);
if (!val) {
// Register the object (even if not yet loaded)
val = new Data.Object(that.type.g, v);
that.type.g.set('nodes', v, val);
}
} else {
val = new Data.Node({value: v});
val.referencedObjects = new Data.Hash();
}
// Register value on the property
that.set('values', v, val);
}
val.referencedObjects.set(obj._id, obj);
}
res.set(v, val);
});
// Unregister values that are no longer used on the object
if (obj.all(that.key)) {
this.unregisterValues(obj.all(that.key).difference(res), obj);
}
return res;
},
// Unregister values from a certain object
unregisterValues: function(values, obj) {
var that = this;
values.each(function(val, key) {
if (val.referencedObjects && val.referencedObjects.length>1) {
val.referencedObjects.del(obj._id);
} else {
that.all('values').del(key);
}
});
},
// Aggregates the property's values
aggregate: function (fn) {
return fn(this.values("values"));
},
// Serialize a propery definition
toJSON: function() {
return {
name: this.name,
type: this.expectedTypes,
unique: this.unique,
meta: this.meta,
validator: this.validator,
required: this.required,
"default": this["default"]
}
}
});
// Data.Type

@@ -890,59 +136,29 @@ // --------------

Data.Type = _.inherits(Data.Node, {
constructor: function(g, id, type) {
var that = this;
Data.Node.call(this);
this.g = g; // Belongs to the DataGraph
this.key = id;
this._id = id;
this._rev = type._rev;
this._conflicted = type._conflicted;
this.type = type.type;
Data.Type = function(type) {
this._id = type._id;
this.type = "/type/type";
this.name = type.name;
this.meta = type.meta || {};
this.indexes = type.indexes;
that.replace('properties', new Data.Hash);
// Extract properties
_.each(type.properties, function(property, key) {
that.set('properties', key, new Data.Property(that, key, property));
});
},
// Convenience function for accessing properties
properties: function() {
return this.all('properties');
},
// Objects of this type
objects: function() {
return this.all('nodes');
},
this.indexes = type.indexes || {};
this.properties = type.properties;
_.each(this.properties, _.bind(function(property, key) {
property.type = _.isArray(property.type) ? property.type : [ property.type ];
property.unique = _.isBoolean(property.unique) ? property.unique : true;
}, this));
};
_.extend(Data.Type.prototype, _.Events, {
// Serialize a single type node
toJSON: function() {
var result = {
return {
_id: this._id,
type: '/type/type',
name: this.name,
properties: {}
};
if (this._rev) result._rev = this._rev;
if (this.meta && _.keys(this.meta).length > 0) result.meta = this.meta;
if (this.indexes && _.keys(this.indexes).length > 0) result.indexes = this.indexes;
this.all('properties').each(function(property) {
var p = result.properties[property.key] = {
name: property.name,
unique: property.unique,
type: property.expectedTypes,
required: property.required ? true : false
};
if (property["default"]) p["default"] = property["default"];
if (property.validator) p.validator = property.validator;
if (property.meta && _.keys(property.meta).length > 0) p.meta = property.meta;
});
return result;
properties: this.properties,
meta: this.meta,
indexes: _.map(this.indexes, function(i) { return i.properties })
}
}

@@ -957,131 +173,27 @@ });

// Provides access to properties, defined on the corresponding `Data.Type`.
Data.Object = _.inherits(Data.Node, {
constructor: function(g, id, data) {
var that = this;
Data.Node.call(this);
this.g = g;
// TODO: remove in favor of _id
this.key = id;
this._id = id;
this.html_id = id.replace(/\//g, '_');
this._dirty = true; // Every constructed node is dirty by default
this.errors = []; // Stores validation errors
this._types = new Data.Hash();
// Associated Data.Objects
this.referencedObjects = new Data.Hash();
// Memoize raw data for the build process
if (data) this.data = data;
Data.Object = function(object, host) {
this._id = object._id;
this.host = host;
this.properties = {};
this.set(object);
};
_.extend(Data.Object.prototype, _.Events, {
// Returns the most specific type
type: function() {
return this.host.get(_.last(this.types));
},
// Convenience function for accessing all related types
types: function() {
return this._types;
// Property lookup according to the type chain
property: function(property) {
var p = null;
_.find(this.types.reverse(), _.bind(function(type) {
return p = this.host.get(type).properties[property];
}, this));
return p;
},
toString: function() {
return this.get('name') || this.val || this._id;
},
// Properties from all associated types
properties: function() {
var properties = new Data.Hash();
// Prototypal inheritance in action: overriden properties belong to the last type specified
this._types.each(function(type) {
type.all('properties').each(function(property) {
properties.set(property.key, property);
});
});
return properties;
},
// After all nodes are recognized the object can be built
build: function() {
var that = this;
var types = _.isArray(this.data.type) ? this.data.type : [this.data.type];
if (!this.data) throw new Error('Object has no data, and cannot be built');
this._rev = this.data._rev;
this._conflicted = this.data._conflicted;
this._deleted = this.data._deleted;
// Initialize primary type (backward compatibility)
this.type = this.g.get('nodes', _.last(types));
// Initialize types
_.each(types, function(type) {
that._types.set(type, that.g.get('nodes', type));
// Register properties for all types
that._types.get(type).all('properties').each(function(property, key) {
function applyValue(value) {
var values = _.isArray(value) ? value : [value];
// Apply property values
that.replace(property.key, property.registerValues(values, that));
}
if (that.data[key] !== undefined) {
applyValue(that.data[key]);
} else if (property["default"]) {
applyValue(property["default"]);
}
});
});
if (this._dirty) this.g.trigger('dirty', this);
},
// Validates an object against its type (=schema)
validate: function() {
if (this.type.key === '/type/type') return true; // Skip type nodes
var that = this;
this.errors = [];
this.properties().each(function(property, key) {
// Required property?
if ((that.get(key) === undefined || that.get(key) === null) || that.get(key) === "") {
if (property.required) {
that.errors.push({property: key, message: "Property \"" + property.name + "\" is required"});
}
} else {
// Correct type?
var types = property.expectedTypes;
function validType(value, types) {
if (_.include(types, typeof value)) return true;
// FIXME: assumes that unloaded objects are valid properties
if (!value.data) return true;
if (value instanceof Data.Object && _.intersect(types, value.types().keys()).length>0) return true;
if (typeof value === 'object' && _.include(types, value.constructor.name.toLowerCase())) return true;
return false;
}
// Unique properties
if (property.unique && !validType(that.get(key), types)) {
that.errors.push({property: key, message: "Invalid type for property \"" + property.name + "\""});
}
// Non unique properties
if (!property.unique && !_.all(that.get(key).values(), function(v) { return validType(v, types); })) {
that.errors.push({property: key, message: "Invalid value type for property \"" + property.name + "\""});
}
}
// Validator satisfied?
function validValue() {
return new RegExp(property.validator).test(that.get(key));
}
if (property.validator) {
if (!validValue()) {
that.errors.push({property: key, message: "Invalid value for property \"" + property.name + "\""});
}
}
});
return this.errors.length === 0;
},

@@ -1103,62 +215,36 @@ // There are four different access scenarios for getting a certain property

get: function(property, key) {
if (!this.data) return null;
var p = this.properties().get(property);
if (!p) return null;
if (arguments.length === 1) {
if (p.isObjectType()) {
return p.unique ? this.first(property) : this.all(property);
} else {
return p.unique ? this.value(property) : this.values(property);
}
var p = this.property(property),
value = this.properties[property];
if (!p || !value) return null;
if (Data.isValueType(p.type)) {
return value;
} else {
return Data.Node.prototype.get.call(this, property, key);
return p.unique ? this.host.get(value)
: _.map(value, _.bind(function(v) { return this.host.get(v); }, this));
}
},
// Sets properties on the object
// Existing properties are overridden / replaced
set: function(properties) {
set: function(object) {
var that = this;
if (arguments.length === 1) {
_.each(properties, function(value, key) {
var p = that.properties().get(key);
if (!p) return; // Property not found on type
// Setup values
that.replace(p.key, p.registerValues(_.isArray(value) ? value : [value], that));
that._dirty = true;
that.g.trigger('dirty', that);
that.g.snapshot();
});
} else {
return Data.Node.prototype.set.call(this, arguments[0], arguments[1], arguments[2]);
}
if (object.type) this.types = _.isArray(object.type) ? object.type : [object.type];
if (object.meta) this.meta = this.object.meta;
_.each(object, _.bind(function(value, key) {
if (!that.property(key) || key === "type") return;
that.properties[key] = value;
}, this));
},
// Serialize an `Data.Object`'s properties
toJSON: function() {
var that = this;
result = {};
_.each(this._properties, function(value, key) {
var p = that.properties().get(key);
if (p.isObjectType()) {
result[key] = p.unique ? that.all(key).keys()[0] : that.all(key).keys()
} else {
result[key] = p.unique ? that.value(key) : that.values(key).values();
}
});
result['type'] = this.types().keys();
result['_id'] = this._id;
if (this._rev !== undefined) result['_rev'] = this._rev;
if (this._deleted) result['_deleted'] = this._deleted;
return result;
return _.extend(this.properties, {_id: this._id, type: this.types})
}
});
_.extend(Data.Object.prototype, _.Events);
// Data.Graph

@@ -1172,150 +258,55 @@ // --------------

// Set a new Data.Adapter and enable Persistence API
Data.Graph = function(graph, options) {
this.nodes = [];
this.objects = [];
this.types = [];
this.keys = {}; // Lookup objects by key
if (!graph) return;
this.merge(graph);
};
Data.Graph = _.inherits(Data.Node, {
constructor: function(g, options) {
var that = this;
Data.Node.call(this);
this.watchers = {};
this.replace('nodes', new Data.Hash());
if (!g) return;
this.merge(g, options.dirty);
if (options.persistent) {
this.persistent = options.persistent;
this.restore(); // Restore data
}
},
_.extend(Data.Graph.prototype, Data.Query, _.Events, {
connect: function(name, config) {
if (typeof exports !== 'undefined') {
var Adapter = require(__dirname + '/adapters/'+name+'_adapter');
this.adapter = new Adapter(this, config);
} else {
if (!Data.Adapters[name]) throw new Error('Adapter "'+name+'" not found');
this.adapter = new Data.Adapters[name](this, config);
}
return this;
},
// Called when the Data.Adapter is ready
connected: function(callback) {
if (this.adapter.realtime) {
this.connectedCallback = callback;
} else {
callback();
}
},
// Serve graph along with an httpServer instance
serve: function(server, options) {
require(__dirname + '/server').initialize(server, this);
},
// Watch for graph updates
watch: function(channel, query, callback) {
this.watchers[channel] = callback;
this.adapter.watch(channel, query, function(err) {});
},
// Stop watching that channel
unwatch: function(channel, callback) {
delete this.watchers[channel];
this.adapter.unwatch(channel, function() {});
},
// Empty graph
empty: function() {
var that = this;
_.each(this.objects().keys(), function(id) {
that.del(id);
that.all('nodes').del(id);
});
},
// Merges in another Graph
merge: function(g, dirty) {
var that = this;
// Process schema nodes
var types = _.select(g, function(node, key) {
if (node.type === '/type/type' || node.type === 'type') {
if (!that.get('nodes', key)) {
that.set('nodes', key, new Data.Type(that, key, node));
that.get(key)._dirty = node._dirty ? node._dirty : dirty;
}
return true;
}
return false;
});
// Process object nodes
var objects = _.select(g, function(node, key) {
if (node.type !== '/type/type' && node.type !== 'type') {
var res = that.get('nodes', key);
var types = _.isArray(node.type) ? node.type : [node.type];
if (!res) {
res = new Data.Object(that, key, node);
that.set('nodes', key, res);
} else {
// Populate existing node with data in order to be rebuilt
res.data = node;
}
// Check for type existence
_.each(types, function(type) {
if (!that.get('nodes', type)) {
throw new Error("Type '"+type+"' not found for "+key+"...");
}
that.get('nodes', type).set('nodes', key, res);
});
that.get(key)._dirty = node._dirty ? node._dirty : dirty;
if (!node._id) node._id = key;
return true;
}
return false;
});
// Now that all new objects are registered we can build them
_.each(objects, function(o) {
var obj = that.get(o._id);
if (obj.data) obj.build();
});
// Create a new snapshot
this.snapshot();
merge: function(nodes) {
_.each(nodes, _.bind(function(n, key) { this.set(_.extend(n, { _id: key })); }, this));
return this;
},
// API method for accessing objects in the graph space
get: function(id) {
return this.nodes[this.keys[id]];
},
set: function(node) {
var id, that = this;
// Backward compatibility
if (arguments.length === 2) node = _.extend(arguments[1], {_id: arguments[0]});
var types = _.isArray(node.type) ? node.type : [node.type];
node._id = node._id ? node._id : Data.uuid('/' + _.last(_.last(types).split('/')) + '/');
var types = _.isArray(node.type) ? node.type : [node.type];
if (arguments.length <= 2) {
node._id = node._id ? node._id : Data.uuid('/' + _.last(_.last(types).split('/')) + '/');
// Recycle existing object if there is one
var res = that.get(node._id) ? that.get(node._id) : new Data.Object(that, node._id, _.clone(node), true);
res.data = node;
res._dirty = true;
res.build();
this.set('nodes', node._id, res);
this.snapshot();
return res;
} else { // Delegate to Data.Node#set
return Data.Node.prototype.set.call(this, arguments[0], arguments[1], arguments[2]);
function createNode() {
return _.last(types) === "/type/type" ? new Data.Type(node)
: new Data.Object(node, this);
}
},
// API method for accessing objects in the graph space
get: function(id) {
if (arguments.length === 1) {
return this.get('nodes', id);
var n = this.get(node._id);
if (!n) {
n = createNode.apply(this);
this.keys[node._id] = this.nodes.length;
this.nodes.push(n);
// Register
if (_.last(types) === "/type/type") {
this.types.push(n);
} else {
this.objects.push(n);
}
} else {
return Data.Node.prototype.get.call(this, arguments[0], arguments[1]);
n.set(node);
}
return n;
},
// Return all objects matching a query object
find: function(qry) {
return this.query(qry);
},

@@ -1327,170 +318,15 @@ // Delete node by id, referenced nodes remain untouched

node._deleted = true;
node._dirty = true;
// Remove registered values
node.properties().each(function(p, key) {
var values = node.all(key);
if (values) p.unregisterValues(values, node);
});
this.trigger('dirty', node);
this.snapshot();
},
// Find objects that match a particular query
find: function(query) {
return this.objects().select(function(o) {
return Data.matches(o.toJSON(), query);
});
},
// Memoize a snapshot of the current graph
snapshot: function() {
if (!this.persistent) return;
localStorage.setItem("graph", JSON.stringify(this.toJSON(true)));
},
// Restore latest snapshot from localStorage
restore: function() {
var snapshot = JSON.parse(localStorage.getItem("graph"));
if (snapshot) this.merge(snapshot);
},
// Fetches a new subgraph from the adapter and either merges the new nodes
// into the current set of nodes
fetch: function(query, options, callback) {
var that = this,
nodes = new Data.Hash(); // collects arrived nodes
// Options are optional
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = {};
}
this.adapter.read(query, options, function(err, graph) {
if (graph) {
that.merge(graph, false);
_.each(graph, function(node, key) {
nodes.set(key, that.get(key));
});
}
err ? callback(err) : callback(null, nodes);
});
},
// Synchronize dirty nodes with the backend
sync: function(callback) {
callback = callback || function() {};
var that = this,
nodes = that.dirtyNodes();
var validNodes = new Data.Hash();
nodes.select(function(node, key) {
if (!node.validate || (node.validate && node.validate())) {
validNodes.set(key, node);
}
});
this.adapter.write(validNodes.toJSON(), function(err, g) {
if (err) return callback(err);
that.merge(g, false);
// Check for rejectedNodes / conflictedNodes
validNodes.each(function(n, key) {
if (g[key]) {
n._dirty = false;
n._rejected = false;
} else {
n._rejected = true;
}
});
// Update localStorage
if (this.persistent) that.snapshot();
if (that.invalidNodes().length > 0) that.trigger('invalid');
if (that.conflictedNodes().length > 0) that.trigger('conflicted');
if (that.rejectedNodes().length > 0) that.trigger('rejected');
var unsavedNodes = that.invalidNodes().union(that.conflictedNodes())
.union(that.rejectedNodes()).length;
callback(unsavedNodes > 0 ? unsavedNodes+' unsaved nodes' : null);
});
},
// Perform a group operation on a Data.Graph
group: function(type, keys, properties) {
var res = new Data.Collection();
res.g = Data.Transformers.group(this, type, keys, properties);
return res;
},
// Type nodes
types: function() {
return this.all('nodes').select(function(node, key) {
return node.type === '/type/type' || node.type === 'type';
});
},
// Object nodes
objects: function() {
return this.all('nodes').select(function(node, key) {
return node.type !== '/type/type' && node.type !== 'type' && node.data && !node._deleted;
});
},
// Get dirty nodes
// Used by Data.Graph#sync
dirtyNodes: function() {
return this.all('nodes').select(function(obj, key) {
return (obj._dirty && (obj.data || obj instanceof Data.Type));
});
},
// Get invalid nodes
invalidNodes: function() {
return this.all('nodes').select(function(obj, key) {
return (obj.errors && obj.errors.length > 0);
});
},
// Get conflicting nodes
conflictedNodes: function() {
return this.all('nodes').select(function(obj, key) {
return obj._conflicted;
});
},
// Nodes that got rejected during sync
rejectedNodes: function() {
return this.all('nodes').select(function(obj, key) {
return obj._rejected;
});
},
// Serializes the graph to the JSON-based exchange format
toJSON: function(extended) {
var result = {};
// Serialize object nodes
this.all('nodes').each(function(obj, key) {
// Only serialize fetched nodes
if (obj.data || obj instanceof Data.Type) {
result[key] = obj.toJSON();
if (extended) {
// include special properties
if (obj._dirty) result[key]._dirty = true;
if (obj._conflicted) result[key]._conclicted = true;
if (obj._rejected) result[key].rejected = true;
}
}
_.each(this.nodes, function(n) {
result[n._id] = n.toJSON()
});
return result;
}
});
_.extend(Data.Graph.prototype, _.Events);
// Data.Collection

@@ -1503,72 +339,81 @@ // --------------

// think of a Collection as a table of data, except it provides precise
// information about the data contained (meta-data). A Data.Collection
// just wraps a `Data.Graph` internally, in order to simplify the interface,
// for cases where you do not have to deal with linked data.
// information about the data contained (meta-data).
Data.Collection = function(spec) {
var that = this,
gspec = { "/type/item": { "type": "/type/type", "properties": {}} };
if (spec) gspec["/type/item"]["indexes"] = spec.indexes || {};
this.type = new Data.Type(spec.type, this);
this.objects = [];
this.length = 0;
this.keys = {};
// Convert to Data.Graph serialization format
if (spec) {
_.each(spec.properties, function(property, key) {
gspec["/type/item"].properties[key] = property;
});
this.g = new Data.Graph(gspec);
_.each(spec.items, function(item, key) {
that.set(key, item);
});
} else {
this.g = new Data.Graph();
}
_.each(spec.objects, _.bind(function(obj) {
this.add(obj);
}, this));
};
// Creates a Data.Collection using a Data.Type, and an array of Data.Objects
Data.Collection.create = function(type, objects) {
var c = new Data.Collection({type: type, objects: []});
c.objects = objects;
c.length = objects.length;
// Register keys for fast lookup
_.each(objects, function(o, i) {
c.keys[o._id] = i;
});
return c;
};
_.extend(Data.Collection.prototype, {
_.extend(Data.Collection.prototype, _.Events, Data.Query, {
// Get an object (item) from the collection
get: function(key) {
return this.g.get.apply(this.g, arguments);
get: function(id) {
if (id.match('^/type/')) return this.type;
return this.objects[this.keys[id]];
},
// Set (add) a new object to the collection
set: function(id, properties) {
this.g.set(id, _.extend(properties, {type: "/type/item"}));
// Return object at a given index
at: function(index) {
return this.objects[index];
},
// Find objects that match a particular query
find: function(query) {
query["type|="] = "/type/item";
return this.g.find(query);
// Return index for a given key
index: function(key) {
return this.keys[key];
},
// Returns a filtered collection containing only items that match a certain query
filter: function(query) {
return new Data.Collection({
properties: this.properties().toJSON(),
items: this.find(query).toJSON()
});
// Return key for a given index
key: function(index) {
return this.objects[index]._id;
},
// Perform a group operation on the collection
group: function(keys, properties) {
var res = new Data.Collection();
res.g = Data.Transformers.group(this.g, "/type/item", keys, properties);
return res;
},
// Convenience function for accessing properties
properties: function() {
return this.g.get('nodes', '/type/item').all('properties');
// Add a new object to the collection
add: function(obj) {
obj._id = obj._id ? obj._id : Data.uuid('/' + _.last(this.type._id.split('/')) + '/');
obj.type = this.type._id;
var o = this.get(obj._id);
if (!o) {
o = new Data.Object(obj, this);
this.keys[o._id] = this.objects.length;
this.objects.push(o);
this.length = this.objects.length;
} else {
o.set(obj);
}
return o;
},
// Convenience function for accessing items
items: function() {
return this.g.objects();
// Find objects that match a particular query
find: function(qry) {
qry["type"] = this.type._id;
return this.query(qry);
},
// Convenience function for accessing indexes defined on the collection
indexes: function() {
return this.g.get('/type/item').indexes;
// Iterate over all objects in the collection
each: function (fn) {
_.each(this.objects, function(object, i) {
fn.call(this, object, object._id, i);
}, this);
return this;
},

@@ -1579,7 +424,7 @@

return {
properties: this.g.toJSON()["/type/item"].properties,
items: this.g.objects().toJSON()
}
type: this.type.toJSON(),
objects: _.map(this.objects, function(n) { return n.toJSON(); })
};
}
});
})();
})();

@@ -1,36 +0,8 @@

(function(){var e;e=typeof exports!=="undefined"?exports:this.Data={};e.VERSION="0.4.1";var g=this._;if(!g&&typeof require!=="undefined")g=require("underscore");e.VALUE_TYPES=["string","object","number","boolean","date"];e.isValueType=function(a){return g.include(e.VALUE_TYPES,a)};e.matches=function(a,b){b=g.isArray(b)?b:[b];var c=false;g.each(b,function(d){if(!c){var f=false;g.each(d,function(h,i){if(!f){var k,l=i.match(/^([a-z_]{1,30})(=|==|!=|>|>=|<|<=|\|=|&=)?$/),j=l[1];l=l[2]||(j=="type"||g.isArray(h)?
"|=":"=");if(l==="|="){l=g.isArray(h)?h:[h];var m=g.isArray(a[j])?a[j]:[a[j]];k=false;g.each(l,function(o){if(g.include(m,o))k=true})}else if(l==="&="){l=g.isArray(h)?h:[h];m=g.isArray(a[j])?a[j]:[a[j]];k=g.intersect(m,l).length===l.length}else switch(l){case "!=":k=!g.isEqual(a[j],h);break;case ">":k=a[j]>h;break;case ">=":k=a[j]>=h;break;case "<":k=a[j]<h;break;case "<=":k=a[j]<=h;break;default:k=g.isEqual(a[j],h);break}if(!k)return f=true}});if(!f)return c=true}});return c};e.uuid=function(a){for(var b=
"0123456789abcdefghijklmnopqrstuvwxyz".split(""),c=[],d=0;d<32;d++)c[d]=b[0|Math.random()*16];return(a?a:"")+c.join("")};g.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,f=c.length;d<f;d++)if(b===c[d]){c.splice(d,1);break}}else c[a]=[]}else this._callbacks={};return this},trigger:function(a){var b,c,d,f;if(!(c=this._callbacks))return this;
if(b=c[a]){d=0;for(f=b.length;d<f;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(f=b.length;d<f;d++)b[d].apply(this,arguments)}return this}};var q=function(){};g.inherits=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};q.prototype=a.prototype;d.prototype=new q;b&&g.extend(d.prototype,b);c&&g.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d};e.Hash=function(a){var b=this;this.data=
{};this.keyOrder=[];this.length=0;if(a instanceof Array)g.each(a,function(c,d){b.set(d,c)});else a instanceof Object&&g.each(a,function(c,d){b.set(d,c)});this.initialize&&this.initialize(attributes,options)};g.extend(e.Hash.prototype,g.Events,{clone:function(){var a=new e.Hash;a.length=this.length;g.each(this.data,function(b,c){a.data[c]=b});a.keyOrder=this.keyOrder.slice(0,this.keyOrder.length);return a},set:function(a,b,c){var d;if(a===undefined)return this;if(this.data[a])d=this.index(a);else{if(c!==
undefined){d=this.select(function(h,i,k){return k<c});var f=this.select(function(h,i,k){return k>=c});this.keyOrder=[].concat(d.keyOrder);this.keyOrder.push(a);this.keyOrder=this.keyOrder.concat(f.keyOrder)}else this.keyOrder.push(a);d=this.length;this.length+=1}this.data[a]=b;this[d]=this.data[a];this.trigger("set",a);return this},del:function(a){if(this.data[a]){var b=this.length,c=this.index(a);delete this.data[a];this.keyOrder.splice(c,1);Array.prototype.splice.call(this,c,1);this.length=b-1;
this.trigger("del",a)}return this},get:function(a){return this.data.hasOwnProperty(a)?this.data[a]:undefined},at:function(a){return this.data[this.keyOrder[a]]},first:function(){return this.at(0)},range:function(a,b){for(var c=new e.Hash,d=a;d<=b;d++)c.set(this.key(d),this.at(d));return c},rest:function(a){return this.range(a,this.length-1)},last:function(){return this.at(this.length-1)},key:function(a){return this.keyOrder[a]},index:function(a){return this.keyOrder.indexOf(a)},each:function(a){var b=
this;g.each(this.keyOrder,function(c,d){a.call(b,b.data[c],c,d)});return this},values:function(){var a=[];this.each(function(b){a.push(b)});return a},keys:function(){return g.clone(this.keyOrder)},toArray:function(){var a=[];this.each(function(b,c){a.push({key:c,value:b})});return a},toJSON:function(){var a={};this.each(function(b,c){a[c]=b.toJSON?b.toJSON():b});return a},map:function(a){var b=this.clone(),c=this;b.each(function(d,f,h){b.data[c.key(h)]=a.call(b,d)});return b},select:function(a){var b=
new e.Hash,c=this;this.each(function(d,f,h){a.call(c,d,f,h)&&b.set(f,d)});return b},sort:function(a){var b=this.clone();sortedKeys=b.toArray().sort(a);b.keyOrder=g.map(sortedKeys,function(c){return c.key});return b},intersect:function(a){var b=this,c=new e.Hash;if(a.length<b.length){b=a;a=this}b.each(function(d,f){a.get(f)&&c.set(f,d)});return c},union:function(a){var b=new e.Hash;this.each(function(c,d){b.set(d,c)});a.each(function(c,d){b.get(d)||b.set(d,c)});return b},difference:function(a){result=
new e.Hash;this.each(function(b,c){a.get(c)||result.set(c,b)});return result}});e.Comparators={};e.Comparators.ASC=function(a,b){return a.value===b.value?0:a.value<b.value?-1:1};e.Comparators.DESC=function(a,b){return a.value===b.value?0:a.value>b.value?-1:1};e.Aggregators={};e.Aggregators.SUM=function(a){var b=0;a.each(function(c){if(g.isNumber(c))b+=c});return b};e.Aggregators.MIN=function(a){var b=Infinity;a.each(function(c){if(g.isNumber(c)&&c<b)b=c});return b};e.Aggregators.MAX=function(a){var b=
-Infinity;a.each(function(c){if(g.isNumber(c)&&c>b)b=c});return b};e.Aggregators.AVG=function(a){var b=0,c=0;a.each(function(d){if(g.isNumber(d)){b+=d;c+=1}});return c===0?0:b/c};e.Aggregators.COUNT=function(a){return a.length};e.Modifiers={};e.Modifiers.DEFAULT=function(a){return a};e.Modifiers.MONTH=function(a){return a.getMonth()};e.Modifiers.QUARTER=function(a){return Math.floor(a.getMonth()/3)+1};e.Transformers={group:function(a,b,c,d){function f(j){var m=new e.Hash;g.each(c,function(r,n){var p=
k[c[n]].get(j[n]).referencedObjects;m=n===0?m.union(p):m.intersect(p)});if(j.length===0)m=a.objects();if(m.length===0)return null;var o={type:b._id};g.each(i[b._id].properties,function(r,n){if(g.include(c,n))o[n]=j[g.indexOf(c,n)];else{var p=m.map(function(s){return s.get(d[n].property||n)});o[n]=(d[n].aggregator||e.Aggregators.SUM)(p)}});return o}function h(j,m){if(j===c.length-1){var o=f(m);o&&l.set(m.join("::"),o)}else{j+=1;k[c[j]].each(function(r,n){h(j,m.concat([n]))})}}var i={};b=a.get(b);var k=
{};i[b._id]={type:"/type/type",properties:{},indexes:b.indexes};g.each(c,function(j){i[b._id].properties[j]=b.properties().get(j).toJSON()});g.each(d,function(j,m){var o=b.properties().get(j.property||m).toJSON();if(j.name)o.name=j.name;i[b._id].properties[m]=o});var l=new e.Graph(i);g.each(c,function(j){k[j]=b.properties().get(j).all("values")});h(-1,[]);return l}};e.Node=function(a){this.nodeId=e.Node.generateId();if(a)this.val=a.value;this._properties={};this.initialize&&this.initialize(a)};e.Node.nodeCount=
0;e.Node.generateId=function(){return e.Node.nodeCount+=1};g.extend(e.Node.prototype,g.Events,{identity:function(){return this.nodeId},replace:function(a,b){this._properties[a]=b},set:function(a,b,c){this._properties[a]||(this._properties[a]=new e.Hash);this._properties[a].set(b,c instanceof e.Node?c:new e.Node({value:c}));return this},get:function(a,b){if(b!==undefined&&this._properties[a]!==undefined)return this._properties[a].get(b)},all:function(a){return this._properties[a]},first:function(a){return(a=
this._properties[a])?a.first():null},value:function(a){return this.values(a).first()},values:function(a){if(!this.all(a))return new e.Hash;return this.all(a).map(function(b){return b.val})}});e.Adapter=function(a){this.config=a};e.Adapters={};e.Property=g.inherits(e.Node,{constructor:function(a,b,c){e.Node.call(this);this._id=this.key=b;this.type=a;this.unique=c.unique;this.name=c.name;this.meta=c.meta||{};this.validator=c.validator;this.required=c.required;this["default"]=c["default"];this.expectedTypes=
g.isArray(c.type)?c.type:[c.type];this.replace("values",new e.Hash)},isValueType:function(){return e.isValueType(this.expectedTypes[0])},isObjectType:function(){return!this.isValueType()},registerValues:function(a,b){var c=this,d=new e.Hash;g.each(a,function(f,h){if(f!==undefined){var i;if(c.isValueType()&&c.expectedTypes[0]==="object"){i=new e.Node({value:f});d.set(h,i)}else{if(b.all(c.key))i=b.all(c.key).get(f);if(!i){i=c.get("values",f);if(!i){if(c.isObjectType()){if(typeof f==="object")f=c.type.g.set(null,
f)._id;i=c.type.g.get("nodes",f);if(!i){i=new e.Object(c.type.g,f);c.type.g.set("nodes",f,i)}}else{i=new e.Node({value:f});i.referencedObjects=new e.Hash}c.set("values",f,i)}i.referencedObjects.set(b._id,b)}d.set(f,i)}}});b.all(c.key)&&this.unregisterValues(b.all(c.key).difference(d),b);return d},unregisterValues:function(a,b){var c=this;a.each(function(d,f){d.referencedObjects&&d.referencedObjects.length>1?d.referencedObjects.del(b._id):c.all("values").del(f)})},aggregate:function(a){return a(this.values("values"))},
toJSON:function(){return{name:this.name,type:this.expectedTypes,unique:this.unique,meta:this.meta,validator:this.validator,required:this.required,"default":this["default"]}}});e.Type=g.inherits(e.Node,{constructor:function(a,b,c){var d=this;e.Node.call(this);this.g=a;this._id=this.key=b;this._rev=c._rev;this._conflicted=c._conflicted;this.type=c.type;this.name=c.name;this.meta=c.meta||{};this.indexes=c.indexes;d.replace("properties",new e.Hash);g.each(c.properties,function(f,h){d.set("properties",
h,new e.Property(d,h,f))})},properties:function(){return this.all("properties")},objects:function(){return this.all("nodes")},toJSON:function(){var a={_id:this._id,type:"/type/type",name:this.name,properties:{}};if(this._rev)a._rev=this._rev;if(this.meta&&g.keys(this.meta).length>0)a.meta=this.meta;if(this.indexes&&g.keys(this.indexes).length>0)a.indexes=this.indexes;this.all("properties").each(function(b){var c=a.properties[b.key]={name:b.name,unique:b.unique,type:b.expectedTypes,required:b.required?
true:false};if(b["default"])c["default"]=b["default"];if(b.validator)c.validator=b.validator;if(b.meta&&g.keys(b.meta).length>0)c.meta=b.meta});return a}});e.Object=g.inherits(e.Node,{constructor:function(a,b,c){e.Node.call(this);this.g=a;this._id=this.key=b;this.html_id=b.replace(/\//g,"_");this._dirty=true;this.errors=[];this._types=new e.Hash;this.referencedObjects=new e.Hash;if(c)this.data=c},types:function(){return this._types},toString:function(){return this.get("name")||this.val||this._id},
properties:function(){var a=new e.Hash;this._types.each(function(b){b.all("properties").each(function(c){a.set(c.key,c)})});return a},build:function(){var a=this,b=g.isArray(this.data.type)?this.data.type:[this.data.type];if(!this.data)throw Error("Object has no data, and cannot be built");this._rev=this.data._rev;this._conflicted=this.data._conflicted;this._deleted=this.data._deleted;this.type=this.g.get("nodes",g.last(b));g.each(b,function(c){a._types.set(c,a.g.get("nodes",c));a._types.get(c).all("properties").each(function(d,
f){function h(i){i=g.isArray(i)?i:[i];a.replace(d.key,d.registerValues(i,a))}if(a.data[f]!==undefined)h(a.data[f]);else d["default"]&&h(d["default"])})});this._dirty&&this.g.trigger("dirty",this)},validate:function(){if(this.type.key==="/type/type")return true;var a=this;this.errors=[];this.properties().each(function(b,c){if(a.get(c)===undefined||a.get(c)===null||a.get(c)==="")b.required&&a.errors.push({property:c,message:'Property "'+b.name+'" is required'});else{var d=b.expectedTypes,f=function(h,
i){if(g.include(i,typeof h))return true;if(!h.data)return true;if(h instanceof e.Object&&g.intersect(i,h.types().keys()).length>0)return true;if(typeof h==="object"&&g.include(i,h.constructor.name.toLowerCase()))return true;return false};b.unique&&!f(a.get(c),d)&&a.errors.push({property:c,message:'Invalid type for property "'+b.name+'"'});!b.unique&&!g.all(a.get(c).values(),function(h){return f(h,d)})&&a.errors.push({property:c,message:'Invalid value type for property "'+b.name+'"'})}if(b.validator)RegExp(b.validator).test(a.get(c))||
a.errors.push({property:c,message:'Invalid value for property "'+b.name+'"'})});return this.errors.length===0},get:function(a,b){if(!this.data)return null;var c=this.properties().get(a);if(!c)return null;return arguments.length===1?c.isObjectType()?c.unique?this.first(a):this.all(a):c.unique?this.value(a):this.values(a):e.Node.prototype.get.call(this,a,b)},set:function(a){var b=this;if(arguments.length===1)g.each(a,function(c,d){var f=b.properties().get(d);if(f){b.replace(f.key,f.registerValues(g.isArray(c)?
c:[c],b));b._dirty=true;b.g.trigger("dirty",b);b.g.snapshot()}});else return e.Node.prototype.set.call(this,arguments[0],arguments[1],arguments[2])},toJSON:function(){var a=this;result={};g.each(this._properties,function(b,c){var d=a.properties().get(c);result[c]=d.isObjectType()?d.unique?a.all(c).keys()[0]:a.all(c).keys():d.unique?a.value(c):a.values(c).values()});result.type=this.types().keys();result._id=this._id;if(this._rev!==undefined)result._rev=this._rev;if(this._deleted)result._deleted=this._deleted;
return result}});g.extend(e.Object.prototype,g.Events);e.Graph=g.inherits(e.Node,{constructor:function(a,b){e.Node.call(this);this.watchers={};this.replace("nodes",new e.Hash);if(a){this.merge(a,b.dirty);if(b.persistent){this.persistent=b.persistent;this.restore()}}},connect:function(a,b){if(typeof exports!=="undefined")this.adapter=new (require(__dirname+"/adapters/"+a+"_adapter"))(this,b);else{if(!e.Adapters[a])throw Error('Adapter "'+a+'" not found');this.adapter=new e.Adapters[a](this,b)}return this},
connected:function(a){if(this.adapter.realtime)this.connectedCallback=a;else a()},serve:function(a){require(__dirname+"/server").initialize(a,this)},watch:function(a,b,c){this.watchers[a]=c;this.adapter.watch(a,b,function(){})},unwatch:function(a){delete this.watchers[a];this.adapter.unwatch(a,function(){})},empty:function(){var a=this;g.each(this.objects().keys(),function(b){a.del(b);a.all("nodes").del(b)})},merge:function(a,b){var c=this;g.select(a,function(f,h){if(f.type==="/type/type"||f.type===
"type"){if(!c.get("nodes",h)){c.set("nodes",h,new e.Type(c,h,f));c.get(h)._dirty=f._dirty?f._dirty:b}return true}return false});var d=g.select(a,function(f,h){if(f.type!=="/type/type"&&f.type!=="type"){var i=c.get("nodes",h),k=g.isArray(f.type)?f.type:[f.type];if(i)i.data=f;else{i=new e.Object(c,h,f);c.set("nodes",h,i)}g.each(k,function(l){if(!c.get("nodes",l))throw Error("Type '"+l+"' not found for "+h+"...");c.get("nodes",l).set("nodes",h,i)});c.get(h)._dirty=f._dirty?f._dirty:b;if(!f._id)f._id=
h;return true}return false});g.each(d,function(f){f=c.get(f._id);f.data&&f.build()});this.snapshot();return this},set:function(a){if(arguments.length===2)a=g.extend(arguments[1],{_id:arguments[0]});var b=g.isArray(a.type)?a.type:[a.type];if(arguments.length<=2){a._id=a._id?a._id:e.uuid("/"+g.last(g.last(b).split("/"))+"/");b=this.get(a._id)?this.get(a._id):new e.Object(this,a._id,g.clone(a),true);b.data=a;b._dirty=true;b.build();this.set("nodes",a._id,b);this.snapshot();return b}else return e.Node.prototype.set.call(this,
arguments[0],arguments[1],arguments[2])},get:function(a){return arguments.length===1?this.get("nodes",a):e.Node.prototype.get.call(this,arguments[0],arguments[1])},del:function(a){var b=this.get(a);if(b){b._deleted=true;b._dirty=true;b.properties().each(function(c,d){var f=b.all(d);f&&c.unregisterValues(f,b)});this.trigger("dirty",b);this.snapshot()}},find:function(a){return this.objects().select(function(b){return e.matches(b.toJSON(),a)})},snapshot:function(){this.persistent&&localStorage.setItem("graph",
JSON.stringify(this.toJSON(true)))},restore:function(){var a=JSON.parse(localStorage.getItem("graph"));a&&this.merge(a)},fetch:function(a,b,c){var d=this,f=new e.Hash;if(typeof b==="function"&&typeof c==="undefined"){c=b;b={}}this.adapter.read(a,b,function(h,i){if(i){d.merge(i,false);g.each(i,function(k,l){f.set(l,d.get(l))})}h?c(h):c(null,f)})},sync:function(a){a=a||function(){};var b=this,c=b.dirtyNodes(),d=new e.Hash;c.select(function(f,h){if(!f.validate||f.validate&&f.validate())d.set(h,f)});
this.adapter.write(d.toJSON(),function(f,h){if(f)return a(f);b.merge(h,false);d.each(function(k,l){if(h[l]){k._dirty=false;k._rejected=false}else k._rejected=true});this.persistent&&b.snapshot();b.invalidNodes().length>0&&b.trigger("invalid");b.conflictedNodes().length>0&&b.trigger("conflicted");b.rejectedNodes().length>0&&b.trigger("rejected");var i=b.invalidNodes().union(b.conflictedNodes()).union(b.rejectedNodes()).length;a(i>0?i+" unsaved nodes":null)})},group:function(a,b,c){var d=new e.Collection;
d.g=e.Transformers.group(this,a,b,c);return d},types:function(){return this.all("nodes").select(function(a){return a.type==="/type/type"||a.type==="type"})},objects:function(){return this.all("nodes").select(function(a){return a.type!=="/type/type"&&a.type!=="type"&&a.data&&!a._deleted})},dirtyNodes:function(){return this.all("nodes").select(function(a){return a._dirty&&(a.data||a instanceof e.Type)})},invalidNodes:function(){return this.all("nodes").select(function(a){return a.errors&&a.errors.length>
0})},conflictedNodes:function(){return this.all("nodes").select(function(a){return a._conflicted})},rejectedNodes:function(){return this.all("nodes").select(function(a){return a._rejected})},toJSON:function(a){var b={};this.all("nodes").each(function(c,d){if(c.data||c instanceof e.Type){b[d]=c.toJSON();if(a){if(c._dirty)b[d]._dirty=true;if(c._conflicted)b[d]._conclicted=true;if(c._rejected)b[d].rejected=true}}});return b}});g.extend(e.Graph.prototype,g.Events);e.Collection=function(a){var b=this,
c={"/type/item":{type:"/type/type",properties:{}}};if(a)c["/type/item"].indexes=a.indexes||{};if(a){g.each(a.properties,function(d,f){c["/type/item"].properties[f]=d});this.g=new e.Graph(c);g.each(a.items,function(d,f){b.set(f,d)})}else this.g=new e.Graph};g.extend(e.Collection.prototype,{get:function(){return this.g.get.apply(this.g,arguments)},set:function(a,b){this.g.set(a,g.extend(b,{type:"/type/item"}))},find:function(a){a["type|="]="/type/item";return this.g.find(a)},filter:function(a){return new e.Collection({properties:this.properties().toJSON(),
items:this.find(a).toJSON()})},group:function(a,b){var c=new e.Collection;c.g=e.Transformers.group(this.g,"/type/item",a,b);return c},properties:function(){return this.g.get("nodes","/type/item").all("properties")},items:function(){return this.g.objects()},indexes:function(){return this.g.get("/type/item").indexes},toJSON:function(){return{properties:this.g.toJSON()["/type/item"].properties,items:this.g.objects().toJSON()}}})})();
(function(){var d;d=typeof exports!=="undefined"?exports:this.Data={};d.VERSION="0.6.0";var b=this._;if(!b&&typeof require!=="undefined")b=require("underscore");d.VALUE_TYPES=["string","object","number","boolean","date"];d.isValueType=function(a){return b.include(d.VALUE_TYPES,b.last(a))};d.uuid=function(a){for(var c="0123456789abcdefghijklmnopqrstuvwxyz".split(""),e=[],f=0;f<32;f++)e[f]=c[0|Math.random()*16];return(a?a:"")+e.join("")};d.Query={query:function(a){function c(g,l){var j=true;b.find(l,
function(h,k){var i=k==="type"?g.types:g.properties[k];if(b.intersect(b.isArray(h)?h:[h],b.isArray(i)?i:[i]).length===0){j=false;return true}});return j}var e=this.get(a.type),f=b.select(this.objects,function(g){return c(g,a)});return d.Collection.create(e,f)}};d.Type=function(a){this._id=a._id;this.type="/type/type";this.name=a.name;this.meta=a.meta||{};this.indexes=a.indexes||{};this.properties=a.properties;b.each(this.properties,b.bind(function(c){c.type=b.isArray(c.type)?c.type:[c.type];c.unique=
b.isBoolean(c.unique)?c.unique:true},this))};b.extend(d.Type.prototype,b.Events,{toJSON:function(){return{_id:this._id,type:"/type/type",properties:this.properties,meta:this.meta,indexes:b.map(this.indexes,function(a){return a.properties})}}});d.Object=function(a,c){this._id=a._id;this.host=c;this.properties={};this.set(a)};b.extend(d.Object.prototype,b.Events,{type:function(){return this.host.get(b.last(this.types))},property:function(a){var c=null;b.find(this.types.reverse(),b.bind(function(e){return c=
this.host.get(e).properties[a]},this));return c},get:function(a){var c=this.property(a);a=this.properties[a];if(!c||!a)return null;return d.isValueType(c.type)?a:c.unique?this.host.get(a):b.map(a,b.bind(function(e){return this.host.get(e)},this))},set:function(a){var c=this;if(a.type)this.types=b.isArray(a.type)?a.type:[a.type];if(a.meta)this.meta=this.object.meta;b.each(a,b.bind(function(e,f){!c.property(f)||f==="type"||(c.properties[f]=e)},this))},toJSON:function(){return b.extend(this.properties,
{_id:this._id,type:this.types})}});d.Graph=function(a){this.nodes=[];this.objects=[];this.types=[];this.keys={};a&&this.merge(a)};b.extend(d.Graph.prototype,d.Query,b.Events,{merge:function(a){b.each(a,b.bind(function(c,e){this.set(b.extend(c,{_id:e}))},this));return this},get:function(a){return this.nodes[this.keys[a]]},set:function(a){function c(){return b.last(e)==="/type/type"?new d.Type(a):new d.Object(a,this)}var e=b.isArray(a.type)?a.type:[a.type];a._id=a._id?a._id:d.uuid("/"+b.last(b.last(e).split("/"))+
"/");var f=this.get(a._id);if(f)f.set(a);else{f=c.apply(this);this.keys[a._id]=this.nodes.length;this.nodes.push(f);b.last(e)==="/type/type"?this.types.push(f):this.objects.push(f)}return f},find:function(a){return this.query(a)},del:function(a){if(a=this.get(a))a._deleted=true},toJSON:function(){var a={};b.each(this.nodes,function(c){a[c._id]=c.toJSON()});return a}});d.Collection=function(a){this.type=new d.Type(a.type,this);this.objects=[];this.length=0;this.keys={};b.each(a.objects,b.bind(function(c){this.add(c)},
this))};d.Collection.create=function(a,c){var e=new d.Collection({type:a,objects:[]});e.objects=c;e.length=c.length;b.each(c,function(f,g){e.keys[f._id]=g});return e};b.extend(d.Collection.prototype,b.Events,d.Query,{get:function(a){if(a.match("^/type/"))return this.type;return this.objects[this.keys[a]]},at:function(a){return this.objects[a]},index:function(a){return this.keys[a]},key:function(a){return this.objects[a]._id},add:function(a){a._id=a._id?a._id:d.uuid("/"+b.last(this.type._id.split("/"))+
"/");a.type=this.type._id;var c=this.get(a._id);if(c)c.set(a);else{c=new d.Object(a,this);this.keys[c._id]=this.objects.length;this.objects.push(c);this.length=this.objects.length}return c},find:function(a){a.type=this.type._id;return this.query(a)},each:function(a){b.each(this.objects,function(c,e){a.call(this,c,c._id,e)},this);return this},toJSON:function(){return{type:this.type.toJSON(),objects:b.map(this.objects,function(a){return a.toJSON()})}}})})();
var countries_fixture = {
"items": {
"austria": {
"name": "Austria",
"official_language": "German Language",
"form_of_government": [
"Federal republic",
"Parliamentary republic"
],
"currency_used": "Euro",
"population": 8356700,
"gdp_nominal": 432400000000.0,
"area": 83872.0,
"date_founded": "1955-07-27"
"type": {
"_id": "/type/country",
"name": "Countries",
"properties": {
"name": {"name": "Country Name", "type": "string" },
"languages": {"name": "Languages spoken", "type": "string" },
"population": { "name": "Population", "type": "number" },
"gdp": { "name": "GDP per capita", "type": "number" }
},
"uk": {
"name": "United Kingdom",
"official_language": "English Language",
"form_of_government": [
"Parliamentary system",
"Constitutional monarchy"
],
"currency_used": "UK \u00a3",
"population": 61612300,
"gdp_nominal": 2787000000000.0,
"area": 244820.0,
"date_founded": "1707-05-01"
},
"usa": {
"name": "United States of America",
"official_language": "English Language",
"form_of_government": [
"Federal republic",
"Constitution",
"Democracy",
"Republic",
"Presidential system",
"Constitutional republic"
],
"currency_used": "US$",
"population": 306108000,
"gdp_nominal": 14330000000000.0,
"area": 9826675.0,
"date_founded": "1776-07-04"
},
"ger": {
"name": "Germany",
"official_language": "German Language",
"form_of_government": [
"Federal republic",
"Democracy",
"Parliamentary republic"
],
"currency_used": "Euro",
"population": 82062200,
"gdp_nominal": 3818000000000.0,
"area": 357092.9,
"date_founded": "1949-05-23"
},
"fra": {
"name": "France",
"official_language": "French Language",
"form_of_government": [
"Republic",
"Semi-presidential system"
],
"currency_used": "Euro",
"population": 65073482,
"gdp_nominal": 2987000000000.0,
"area": 674843.0,
"date_founded": "1792"
},
"ita": {
"name": "Italy",
"official_language": "Italian Language",
"form_of_government": [
"Parliamentary republic"
],
"currency_used": "Euro",
"population": 60090400,
"gdp_nominal": 2399000000000.0,
"area": 301338.0,
"date_founded": "1861-03-17"
"indexes": {
"by_name": ["name"]
}
},
"properties": {
"name": {
"name": "Country Name",
"type": "string",
"unique": true
"objects": [
{
"_id": "at",
"name": "Austria",
"languages": ["German", "Austrian"],
"population": 8.3,
"gdp": 41.805
},
"official_language": {
"name": "Official language",
"type": "string",
"unique": true
{
"_id": "de",
"name": "Germany",
"languages": ["German"],
"population": 82,
"gdp": 46.860
},
"form_of_government": {
"name": "Form of governmennt",
"type": "string",
"unique": false
{
"_id": "us",
"name": "United States of America",
"languages": ["German", "English", "Spanish", "Chinese", "French"],
"population": 311,
"gdp": 36.081
},
"currency_used": {
"name": "Currency used",
"type": "string",
"unique": true
{
"_id": "uk",
"name": "United Kingdom",
"languages": ["English", "Irish", "Scottish Gaelic"],
"population": 62.3,
"gdp": 36.081
},
"population": {
"name": "Population",
"type": "number",
"unique": true
{
"_id": "es",
"name": "Spain",
"languages": ["Spanish"],
"population": 30.6,
"gdp": 36.081
},
"gdp_nominal": {
"name": "GDP nominal",
"type": "number",
"unique": true
{
"_id": "gr",
"name": "Greece",
"languages": ["Greek"],
"population": 11.0,
"gdp": 36.081
},
"area": {
"name": "Area",
"type": "number",
"unique": true
},
"date_founded": {
"name": "Date founded",
"type": "date",
"unqiue": true
{
"_id": "ca",
"name": "Canada",
"languages": ["English", "French", "Spanish"],
"population": 40.1,
"gdp": 40.457
}
}
}
]
};

@@ -31,2 +31,5 @@ var documents_fixture = {

}
},
"indexes": {
"by_authors": ["authors"]
}

@@ -33,0 +36,0 @@ },

{
"name" : "data",
"description" : "A Javascript data manipulation library.",
"description" : "A Javascript data representation library.",
"url" : "http://github.com/michael/data/",
"keywords" : ["util", "functional", "linked-data", "server", "client", "browser"],
"keywords" : ["util", "linked-data", "server", "client", "browser"],
"author" : "Michael Aufreiter",
"contributors" : [],
"dependencies" : ["underscore", "now"],
"dependencies" : ["underscore"],
"lib" : ".",
"main" : "index",
"version" : "0.4.1"
"version" : "0.6.0"
}

@@ -18,3 +18,2 @@ var _ = require('underscore');

// AJAX interface

@@ -40,67 +39,7 @@ // --------------

// NowJS interface
// --------------
var nowjs = require('now');
var everyone = nowjs.initialize(server);
// Watcher groups
var channels = {};
everyone.connected(function() {});
everyone.disconnected(function(){});
// Dispatch to all interested parties
function dispatchUpdates(nodes) {
var notifications = {};
// For each node, check channels
_.each(nodes, function(node, key, index) {
_.each(channels, function(channel) {
if (Data.matches(node, channel.query)) {
notifications[channel.name] = notifications[channel.name] ? notifications[channel.name] : {};
notifications[channel.name][key] = node;
}
});
server.post('/graph/pull', function(req, res) {
graph.adapter.pull(req.body, function(err, g) {
err ? res.send(JSON.stringify({error: err})) : res.send(JSON.stringify({"status": "ok", "graph": g}));
});
// Dispatch
_.each(notifications, function(nodes, groupName) {
nowjs.getGroup(groupName).now.update(groupName, nodes);
});
};
// Register a new watcher
everyone.now.watch = function(channel, query, callback) {
var group = nowjs.getGroup(channel);
group.addUser(this.user.clientId);
channels[channel] = {
name: channel,
group: group,
query: query
};
callback();
};
everyone.now.unwatch = function(name, callback) {
var group = nowjs.getGroup(name);
group.removeUser(this.user.clientId);
// TODO: remove the whole group and channel if empty
};
// Read graph
everyone.now.read = function(query, options, callback) {
graph.adapter.read(query, options, function(err, g) {
callback(err, g);
}, this.user);
};
// Write graph
everyone.now.write = function(nodes, callback) {
graph.adapter.write(nodes, function(err, g) {
callback(err, g);
// Notify channel users
dispatchUpdates(g);
}, this.user);
};
});
};

@@ -1,753 +0,245 @@

// Data.js — A utility belt for data manipulation
// Data.js — A uniform interface to domain data
// -------------
var items;
// Data.Hash
// -------------
(function() {
module("Data.Hash", {
setup: function() {
items = new Data.Hash();
items.set("at", "Austria");
items.set("de", "Germany");
},
teardown: function() {
delete items;
}
});
// Helpers
// -------
test("construction from Array", function() {
var numbers = new Data.Hash([1, 2, 5, 10]);
ok(numbers.at(0) === 1);
ok(numbers.at(1) === 2);
ok(numbers.at(2) === 5);
ok(numbers.at(3) === 10);
suite('Data.js API', function() {
var stopwatch, items;
// key equals index
ok(numbers.get(0) === 1);
ok(numbers.get(1) === 2);
ok(numbers.get(2) === 5);
ok(numbers.get(3) === 10);
ok(numbers.length === 4);
});
// Data.Graph
// -------------
test("construction from Hash", function() {
var countries = new Data.Hash({
'at': 'Austria',
'de': 'Germany',
'ch': 'Switzerland'
});
// please note: order is undetermined since native javascript hashes
// are not sorted. please perform a sort() operation after construction
// if you want to rely on item ordering.
ok(countries.get('at') === 'Austria');
ok(countries.get('de') === 'Germany');
ok(countries.get("ch") === "Switzerland");
ok(countries.length === 3);
});
suite('Data.Graph', function() {
var graph,
documentType,
entitiesProperty,
protovis,
unveil,
processingjs,
mention,
stanford,
newYork,
anotherMention;
setup(function() {
graph = new Data.Graph(documents_fixture);
protovis = graph.get('/doc/protovis_introduction');
unveil = graph.get('/doc/unveil_introduction');
processingjs = graph.get('/doc/processing_js_introduction');
mention = graph.get('M0000003');
anotherMention = graph.get('M0000003');
stanford = graph.get('/location/stanford');
newYork = graph.get('/location/new_york');
});
test("insertion", function() {
items.set("ch", "Switzerland");
ok(items.length === 3);
});
test("valid construction", function() {
assert.ok(graph != undefined);
assert.ok(graph.types.length == 3);
test("value overwrite", function() {
items.get("at") === "Austria";
items.set("at", "Österreich");
ok(items.length === 2);
ok(items.get("at") === "Österreich");
});
assert.ok(graph.types[0] instanceof Data.Type);
assert.ok(graph.types[1] instanceof Data.Type);
assert.ok(graph.types[2] instanceof Data.Type);
});
test("clone", function() {
// TODO: add some assertions
items.clone();
});
test("key", function() {
ok(items.key(0) === "at");
ok(items.key(1) === "de");
});
test("Type inspection", function() {
documentType = graph.get('/type/document');
assert.ok(Object.keys(documentType.properties).length === 5);
assert.ok(documentType._id === '/type/document');
assert.ok(documentType.name === 'Document');
});
test("Property inspection", function() {
documentType = graph.get('/type/document');
entitiesProperty = documentType.properties.entities;
assert.ok(entitiesProperty.name === 'Associated Entities');
assert.ok(_.include(entitiesProperty.type, '/type/entity'));
});
test("hash semantics", function() {
var keys = [];
var values = [];
ok(items.get("at") === "Austria");
ok(items.get("de") === "Germany");
// order is also reflected in eachKey
items.each(function(item, key) {
keys.push(key);
values.push(item);
});
ok(keys.length === 2 && values.length === 2);
ok(keys[0] === 'at');
ok(keys[1] === 'de');
ok(values[0] === 'Austria');
ok(values[1] === 'Germany');
});
test("array semantics", function() {
var values = [];
items.get(0) === "Austria";
items.get(1) === "Germany";
items.length === 1;
items.each(function(item, key, index) {
values.push(item);
});
ok(values.length === 2);
ok(values[0] === 'Austria');
ok(values[1] === 'Germany');
ok(items.first() === "Austria");
ok(items.last() === "Germany");
});
test("Object inspection", function() {
assert.ok(protovis instanceof Data.Object);
assert.ok(mention instanceof Data.Object);
assert.ok(anotherMention instanceof Data.Object);
});
test("Data.Hash#del", function() {
items.set("ch", "Switzerland");
items.del('de');
ok(items.length === 2);
ok(items.keyOrder.length === 2);
ok(items.get('de') === undefined);
});
// There are four different access scenarios:
// For convenience there's a get method, which always returns the right result depending on the
// schema information. However, internally, every property of a resource is represented as a
// non-unique Set of Node objects, even if it's a unique property. So if
// you want to be explicit you should use the native methods of the Node API.
test("1. Unique value types", function() {
assert.ok(protovis.get('page_count') === 8);
assert.ok(protovis.get('title') === 'Protovis');
});
test("Data.Hash#each", function() {
var enumerated = [];
items.each(function(item, key, index) {
enumerated.push(item);
});
ok(enumerated[0]==="Austria");
ok(enumerated[1]==="Germany");
});
test("Data.Hash#values", function() {
items.set("ch", "Switzerland");
var values = items.values();
test("2. Non-Unique value types", function() {
assert.ok(protovis.get('authors').length === 2);
assert.ok(protovis.get('authors')[0] === 'Michael Bostock');
assert.ok(protovis.get('authors')[1] === 'Jeffrey Heer');
});
ok(values[0] === "Austria");
ok(values[1] === "Germany");
ok(values[2] === "Switzerland");
});
test("Data.Hash#rest", function() {
items.set("ch", "Switzerland");
var rest = items.rest(1);
ok(rest.get('de') === 'Germany');
ok(rest.get('ch') === 'Switzerland');
ok(rest.length === 2);
});
test("3. Unique object types (one resource)", function() {
assert.ok(mention.get('entity')._id === '/location/new_york');
});
test("Data.Hash#range", function() {
items.set("ch", "Switzerland");
items.set("uk", "United Kingdom");
var range = items.range(0,1);
ok(range.length == 2);
ok(range.at(0) === "Austria");
ok(range.at(1) === "Germany");
});
test("4. Non-unique object types (many resources)", function() {
assert.ok(protovis.get('entities').length === 2);
assert.ok(protovis.get('entities')[0]._id === '/location/stanford');
assert.ok(protovis.get('entities')[1]._id === '/location/new_york');
});
test("Data.Hash#sort", function() {
items.set("ch", "Switzerland");
test("References to the same resource should result in object equality", function() {
assert.ok(mention.get('entity') === anotherMention.get('entity'));
});
ok(items.at(0)==="Austria");
ok(items.at(1)==="Germany");
ok(items.at(2)==="Switzerland");
// sort descending
var sortedItems = items.sort(Data.Comparators.DESC);
ok(sortedItems.at(0)==="Switzerland");
ok(sortedItems.at(1)==="Germany");
ok(sortedItems.at(2)==="Austria");
});
test("Graph traversal (navigation)", function() {
// Hop from a document to the second entity, picking the 2nd mention and go
// to the associated document of this mention.
assert.ok(protovis.get('entities')[1] // => Entity#/location/new_york
.get('mentions')[1] // => Mention#M0000003
.get('document') // => /doc/processing_js_introduction
._id === '/doc/processing_js_introduction');
});
test("Data.Hash#map", function() {
var mappedItems = items.map(function (item) {
return item.slice(0, 3);
});
// leave original Hash untouched
ok(items.get('at') === 'Austria');
ok(items.get('de') === 'Germany');
ok(items.at(0) === 'Austria');
ok(items.at(1) === 'Germany');
ok(mappedItems.get('at') === 'Aus');
ok(mappedItems.get('de') === 'Ger');
// Data.Object Manipulation
ok(mappedItems.at(0) === 'Aus');
ok(mappedItems.at(1) === 'Ger');
});
// Set new nodes on the graph
test("Set new nodes on the graph", function() {
var substance = graph.set({
"_id": "/document/substance",
"type": "/type/document",
"title": "Substance Introduction",
"authors": ["Michael Aufreiter"],
"page_count": 12,
"entities": ["/location/stanford", "/location/new_york"]
});
assert.ok(substance.get('title') === 'Substance Introduction');
assert.ok(substance.get('page_count') === 12);
// Allow null being passed explicitly for an object type property
var mention = graph.set({
"type": "/type/mention",
"document": null,
"entity": "/location/stanford",
"page": 5
});
assert.ok(!mention.get('document'));
});
test("Set value properties of existing nodes", function() {
// Value properties
protovis.set({
'title': 'Protovis Introduction',
'page_count': 20
});
protovis.set({
'page_count': 20
});
test("Data.Hash#select", function() {
var selectedItems = items.select(function (item, key) {
return item === 'Austria';
assert.ok(protovis.get('title') === 'Protovis Introduction');
assert.ok(protovis.get('page_count') === 20);
});
// leave original Set untouched
ok(items.get('at') === 'Austria');
ok(items.get('de') === 'Germany');
ok(items.at(0) === 'Austria');
ok(items.at(1) === 'Germany');
ok(selectedItems.at(0) === 'Austria');
ok(selectedItems.get("at") === 'Austria');
ok(selectedItems.length === 1);
});
test("Set value type objects", function() {
protovis.set({
info: {"foo": "bar"}
});
assert.ok(protovis.get('info').foo === "bar");
protovis.set({
info: {"bar": "baz"}
});
assert.ok(protovis.get('info').bar === "baz");
});
test("Data.Hash#intersect", function() {
var items2 = new Data.Hash(),
intersected;
items2.set('fr', 'France');
items2.set('at', 'Austria');
// leave original Setes untouched
ok(items.get('at') === 'Austria');
ok(items.get('de') === 'Germany');
ok(items.at(0) === 'Austria');
ok(items.at(1) === 'Germany');
ok(items2.get('fr') === 'France');
ok(items2.get('at') === 'Austria');
ok(items2.at(0) === 'France');
ok(items2.at(1) === 'Austria');
intersected = items.intersect(items2);
ok(intersected.length === 1);
ok(intersected.get('at') === 'Austria');
});
test("Set object properties of existing nodes", function() {
assert.ok(_.first(protovis.get('entities')).get('name') === 'Stanford');
assert.ok(_.last(protovis.get('entities')).get('name') === 'New York');
protovis.set({
entities: ['/location/toronto'],
authors: 'Michael Aufreiter'
});
assert.ok(_.first(protovis.get('entities')) === graph.get('/location/toronto'));
});
test("Data.Hash#union", function() {
var items2 = new Data.Hash(),
unitedItems;
items2.set('fr', 'France');
items2.set('at', 'Austria');
// leave original Setes untouched
ok(items.get('at') === 'Austria');
ok(items.get('de') === 'Germany');
ok(items.at(0) === 'Austria');
ok(items.at(1) === 'Germany');
ok(items2.get('fr') === 'France');
ok(items2.get('at') === 'Austria');
ok(items2.at(0) === 'France');
ok(items2.at(1) === 'Austria');
unitedItems = items.union(items2);
ok(unitedItems.length === 3);
ok(unitedItems.get('at') === 'Austria');
ok(unitedItems.get('de') === 'Germany');
ok(unitedItems.get('fr') === 'France');
});
test("Find objects", function() {
// By type
var documents = graph.find({"type": "/type/document"});
assert.ok(documents.length === 3);
test("Only consider own properties", function() {
var hsh = new Data.Hash();
ok(hsh.get('toString') === undefined);
ok(hsh.get('toLocaleString') === undefined);
ok(hsh.get('watch') === undefined);
ok(hsh.get('hasOwnProperty') === undefined);
});
// By authors
documents = graph.find({
"type": "/type/document",
"authors": "Michael Aufreiter"
});
assert.ok(documents.length === 2);
});
});
suite('Data.Collection', function() {
var c;
setup(function() {
c = new Data.Collection(countries_fixture);
});
test("Data.Hash Events", function() {
persons = new Data.Hash();
persons.bind('set', function(key) { ok(key); });
persons.bind('del', function(key) { ok(key); });
persons.set("mi", "Michael");
persons.set("th", "Thomas");
persons.del("mi");
expect(3);
});
test("valid construction", function() {
assert.ok(c != undefined);
assert.ok(c.type instanceof Data.Type);
});
test("each", function() {
var count = 0;
c.each(function(obj, key, index) {
assert.ok(count === index);
count ++;
});
assert.ok(count === c.length);
});
test("fail prevention", function() {
// null is a valid key
items.set(null, 'Netherlands');
// undefined is not
items.set(undefined, 'Netherlands');
items.set('null_value', null);
items.set('undefined_value', undefined);
ok(items.length === 5);
});
test("at", function() {
assert.ok(c.at(6)._id === 'ca');
});
test("get", function() {
assert.ok(c.get('at')._id === 'at');
});
// Data.Node
// -------------
test("index", function() {
assert.ok(c.index('at') === 0);
});
var austrian, english, german, eu, austria, germany, uk;
test("key", function() {
assert.ok(c.key(0) === "at");
});
module("Data.Node", {
setup: function() {
austrian = new Data.Node({value: 'Austrian'});
english = new Data.Node({value: 'English'});
german = new Data.Node({value: 'German'});
// confederations
eu = new Data.Node();
// countries
austria = new Data.Node();
germany = new Data.Node();
uk = new Data.Node();
// people
barroso = new Data.Node({value: 'Barroso'});
// connect some nodes
austria.set('languages', 'at', austrian);
austria.set('languages', 'ger', german);
eu.set('president', 'barroso', barroso);
}
});
test("find objects", function() {
assert.ok(c != undefined);
assert.ok(c.type instanceof Data.Type);
test("get connected nodes", function() {
// order should be preserved
ok(austria.all('languages') instanceof Data.Hash);
ok(austria.all('languages').at(0) === austrian);
ok(austria.all('languages').at(1) === german);
ok(austria.get('languages', 'at') === austrian);
ok(austria.get('languages', 'ger') === german);
});
var countries = c.find({"name": "Austria"});
assert.ok(countries.length === 1);
});
});
test("get first connected node", function() {
ok(eu.first('president') instanceof Data.Node);
ok(eu.first('president').val === 'Barroso');
});
test("iteration of connected nodes", function() {
var nodes = [];
austria.all('languages').each(function(node) {
nodes.push(node);
});
ok(nodes.length === 2);
});
test("Node#list", function() {
// non-unqiue property
ok(austria.all('languages').length === 2);
ok(austria.all('languages').get('at') === austrian);
ok(austria.all('languages').get('ger') === german);
// unique property
ok(eu.all('president').length === 1);
ok(eu.values('president').first() === 'Barroso');
});
test("Node#values", function() {
var values = austria.values('languages');
// for non-unique properties
ok(values.at(0) === 'Austrian');
ok(values.at(1) === 'German');
ok(values.get('at') === 'Austrian');
ok(values.get('ger') === 'German');
// for unique properties
ok(eu.values('president').at(0) === 'Barroso');
});
test("Node#value", function() {
var values = austria.values('languages');
// for non-unique properties
ok(austria.value('languages') === 'Austrian');
// for unique properties
ok(eu.value('president') === 'Barroso');
});
test("Allows null as a key for property values", function() {
var root = new Data.Node({value: 'RootNode'});
var nullNode = new Data.Node({value: null});
root.set('values', null, nullNode);
ok(root.value('values', null) === null);
});
// Data.Graph
// -------------
var graph,
documentType,
entitiesProperty,
protovis,
unveil,
processingjs,
mention,
stanford,
newYork,
anotherMention;
graph = new Data.Graph(documents_fixture);
protovis = graph.get('objects', '/doc/protovis_introduction');
unveil = graph.get('objects', '/doc/unveil_introduction');
processingjs = graph.get('objects', '/doc/processing_js_introduction');
mention = graph.get('objects', 'M0000003');
anotherMention = graph.get('objects', 'M0000003');
stanford = graph.get('/location/stanford');
newYork = graph.get('/location/new_york');
module("Data.Graph", {
setup: function() {
// graph = new Data.Graph(documents_fixture);
},
teardown: function() {
// delete graph;
}
});
test("valid construction", function() {
ok(graph != undefined);
ok(graph.types().length == 3);
ok(graph.types().get('/type/document') instanceof Data.Type);
ok(graph.types().get('/type/entity') instanceof Data.Type);
ok(graph.types().get('/type/mention') instanceof Data.Type);
});
test("Type inspection", function() {
documentType = graph.get('objects', '/type/document');
ok(documentType.all('properties').length === 5);
ok(documentType.key === '/type/document');
ok(documentType.name === 'Document');
});
test("Property inspection", function() {
entitiesProperty = documentType.get('properties', 'entities');
ok(entitiesProperty.name === 'Associated Entities');
ok(_.include(entitiesProperty.expectedTypes, '/type/entity'));
});
test("Object inspection", function() {
ok(protovis instanceof Data.Object);
ok(mention instanceof Data.Object);
ok(anotherMention instanceof Data.Object);
});
// There are four different access scenarios:
// For convenience there's a get method, which always returns the right result depending on the
// schema information. However, internally, every property of a resource is represented as a
// non-unique Set of Node objects, even if it's a unique property. So if
// you want to be explicit you should use the native methods of the Node API.
test("1. Unique value types", function() {
ok(protovis.get('page_count') === 8);
ok(protovis.get('title') === 'Protovis');
// internally delegates to
ok(protovis.get('page_count') === 8);
});
test("2. Non-Unique value types", function() {
ok(protovis.get('authors').length === 2);
ok(protovis.get('authors').at(0) === 'Michael Bostock');
ok(protovis.get('authors').at(1) === 'Jeffrey Heer');
// internally delegates to
ok(protovis.values('authors').length === 2);
});
test("3. Unique object types (one resource)", function() {
ok(mention.get('entity').key === '/location/new_york');
// internally delegates to
ok(mention.first('entity').key === '/location/new_york');
});
test("4. Non-unique object types (many resources)", function() {
ok(protovis.get('entities').length === 2);
ok(protovis.get('entities').at(0).key === '/location/stanford');
ok(protovis.get('entities').at(1).key === '/location/new_york');
// internally delegates to
ok(protovis.all('entities').length === 2);
});
test("References to the same resource should result in object equality", function() {
ok(mention.first('entity') === anotherMention.first('entity'));
});
test("Graph traversal (navigation)", function() {
// Hop from a document to the second entity, picking the 2nd mention and go
// to the associated document of this mention.
ok(protovis.get('entities').at(1) // => Entity#/location/new_york
.get('mentions').at(1) // => Mention#M0000003
.get('document') // => /doc/processing_js_introduction
.key === '/doc/processing_js_introduction');
});
test("Querying information", function() {
var cities = graph.all('objects').select(function(res, key) {
return /or/.test(res.get('name'))
});
ok(cities.length === 3);
ok(cities.get('/location/new_york'));
ok(cities.get('/location/toronto'));
ok(cities.get('/location/stanford'));
});
test("Value identity", function() {
// If the values of a property are shared among resources they should have
// the same identity as well.
ok(unveil.all('authors').at(0) === processingjs.all('authors').at(2));
ok(unveil.get('authors').at(0) === 'Michael Aufreiter');
ok(processingjs.get('authors').at(2) === 'Michael Aufreiter');
// This allows questions like:
// Show all unique values of a certain property e.g. /type/document.authors
ok(protovis.type.get('properties', 'authors').all('values').length === 6);
});
// Data.Object Manipulation
// Set new nodes on the graph
test("Set new nodes on the graph", function() {
var substance = graph.set({
"_id": "/document/substance",
"type": "/type/document",
"title": "Substance Introduction",
"authors": ["Michael Aufreiter"],
"page_count": 12,
"entities": ["/location/stanford", "/location/new_york"]
});
ok(substance.get('title') === 'Substance Introduction');
ok(substance.get('page_count') === 12);
});
test("Set value properties of existing nodes", function() {
// Value properties
protovis.set({
'title': 'Protovis Introduction',
'page_count': 20
});
protovis.set({
'page_count': 20
});
ok(protovis.get('title') === 'Protovis Introduction');
ok(protovis.get('page_count') === 20);
});
test("Set value type objects", function() {
protovis.set({
info: {"foo": "bar"}
});
ok(protovis.get('info').foo === "bar");
protovis.set({
info: {"bar": "baz"}
});
ok(protovis.get('info').bar === "baz");
});
test("Set object properties of existing nodes", function() {
ok(protovis.get('entities').first().get('name') === 'Stanford');
ok(protovis.get('entities').last().get('name') === 'New York');
ok(protovis.type.get('properties', 'entities').get('values', '/location/new_york') === newYork);
ok(protovis.type.get('properties', 'entities').get('values', '/location/stanford') === stanford);
ok(protovis.type.get('properties', 'entities').all('values').length === 2);
ok(protovis.type.get('properties', 'authors').get('values', 'Michael Bostock'));
ok(protovis.type.get('properties', 'authors').get('values', 'Jeffrey Heer'));
protovis.set({
entities: ['/location/toronto'],
authors: 'Michael Aufreiter'
});
ok(protovis.get('entities').first() === graph.get('/location/toronto'));
ok(protovis.type.get('properties', 'entities').get('values', '/location/toronto'));
});
test("Proper value registration / deregistration", function() {
var documentType = graph.types().get('/type/document');
var values = documentType.properties().get('title').all('values');
var valueCount = values.length;
unveil.set({
title: "Unveil Introduction"
});
ok(values.keys().length === valueCount);
var valueCount = values.length;
// Overwrite existing object
graph.set("/doc/unveil_introduction", {
"type": "/type/document",
"title": "Unveil.js Introduction",
"authors": ["Michael Aufreiter", "Lindsay Kay"],
"page_count": 8,
"entities": []
});
ok(values.keys().length === valueCount);
// Proper value unregistration of nodes being deleted
graph.del("/doc/unveil_introduction");
var type = graph.get('/type/document');
ok(!type.properties().get("title").all('values').get("Unveil.js Introduction"));
ok(!type.properties().get("authors").all('values').get("Lindsay Kay"));
});
// Data.Collection
// -------------
module("Data.Collection");
var c = new Data.Collection(countries_fixture);
test("has some properties", function() {
ok(c.properties().get('area') instanceof Data.Node);
ok(c.properties().get('currency_used') instanceof Data.Node);
ok(c.properties().get('doesnotexit') === undefined);
});
test("property is connected to values", function() {
var governmentForms = c.properties().get('form_of_government');
ok(governmentForms.all('values').length === 10);
});
test("read item property values", function() {
var item = c.get('austria');
// Unique properties
ok(item.get('name') === 'Austria');
ok(item.get('area') === 83872);
// Non-unique properties
ok(item.get('form_of_government').length === 2);
});
test("get values of a property", function() {
var population = c.properties().get('population');
ok(population.all('values').length === 6);
});
test("grouping", function() {
var languages = c.group(["official_language"], {
'area_total': { aggregator: Data.Aggregators.SUM, name: "Total Area", property: "area" },
'area_avg': {aggregator: Data.Aggregators.AVG, name: "Average Area", property: "area" },
'population': { aggregator: Data.Aggregators.AVG, name: "Average Population" }
});
ok(languages.items().get('German Language').get('population') === 45209450);
ok(languages.items().get('English Language').get('area_total') === 10071495);
// Deal with empty group key
var onegroup = c.group([], {
'area_total': { aggregator: Data.Aggregators.SUM, name: "Total Area", property: "area" },
'area_avg': {aggregator: Data.Aggregators.AVG, name: "Average Area", property: "area" },
'population': { aggregator: Data.Aggregators.MIN, name: "Smallest Population" }
});
ok(onegroup.items().first().get('population') === 8356700)
});
test("Collection#find", function() {
// Test ANY-OF operator
var englishAndGermanCountries = c.find({
"official_language|=": ["English Language", "German Language"]
});
ok(englishAndGermanCountries.get('austria'));
ok(englishAndGermanCountries.get('ger'));
ok(englishAndGermanCountries.get('uk'));
ok(englishAndGermanCountries.get('usa'));
// Test ALL-OF operator
var republicsAndDemocracies = c.find({
"official_language": "English Language",
"form_of_government&=": ["Constitution", "Democracy"]
});
// Test >= operator
var bigCountries = c.find({
"area>=": 700000
});
ok(bigCountries.length === 1);
});
test("Collection#filter", function() {
var filteredCollection = c.filter({
"official_language|=": ["German Language"]
});
ok(filteredCollection.items().length === 2);
});
module("Data.Aggregators");
// Data.Aggregators
// -------------
test("Aggregators", function() {
var values = new Data.Hash();
values.set('0', 4);
values.set('1', 5);
values.set('2', -3);
values.set('3', 1);
ok(Data.Aggregators.SUM(values) === 7);
ok(Data.Aggregators.MIN(values) === -3);
ok(Data.Aggregators.MAX(values) === 5);
ok(Data.Aggregators.COUNT(values) === 4);
});
test("allow aggregation of property values", function() {
var population = c.properties().get("population");
ok(population.aggregate(Data.Aggregators.MIN) === 8356700);
ok(population.aggregate(Data.Aggregators.MAX) === 306108000);
});
}).call(this);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc