data
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -6,2 +6,6 @@ var CouchClient = require('../lib/couch-client'); | ||
// Apply Filter Stack | ||
// -------- | ||
var applyFilters = function(filters, nodes, mode, ctx, callback) { | ||
@@ -44,3 +48,4 @@ var that = this; | ||
function setupIndexes(node, callback) { | ||
function setupType(node, callback) { | ||
// Extract local typename | ||
@@ -54,2 +59,12 @@ var typename = node._id.split('/')[2]; | ||
}; | ||
// Setup validation function | ||
validatorFn = "function(newDoc, oldDoc, userCtx) {\n"; | ||
validatorFn += "if (newDoc.type.indexOf('"+node._id+"')>=0) {\n"; | ||
_.each(node.properties, function(property, key) { | ||
if (property.required) validatorFn += "if (!newDoc['"+key+"']) throw({forbidden : '"+key+" is missing'});\n"; | ||
if (property.validator) validatorFn += "if (!new RegExp('"+property.validator+"').test(newDoc."+key+")) throw({forbidden: '"+key+" is invalid'});" | ||
}); | ||
validatorFn += "}}"; | ||
if (node.indexes) { | ||
@@ -64,5 +79,7 @@ _.each(node.indexes, function(properties, indexName) { | ||
} | ||
db.save({ | ||
_id: '_design/'+typename, | ||
views: views | ||
views: views, | ||
validate_doc_update: validatorFn | ||
}, {force: true}, function (err, doc) { | ||
@@ -77,2 +94,3 @@ err ? callback(err) : callback(); | ||
// Flush the database | ||
self.flush = function(callback) { | ||
@@ -125,3 +143,3 @@ db.request("DELETE", db.uri.pathname, function (err) { | ||
if (newDoc.type == "/type/type") { | ||
setupIndexes(newDoc, function(err) { | ||
setupType(newDoc, function(err) { | ||
if (err) { console.log('Error during index creation:'); console.log(err); }; | ||
@@ -147,3 +165,2 @@ result[nodeId] = newDoc; | ||
// read | ||
@@ -155,3 +172,3 @@ // -------------- | ||
self.read = function(queries, options, callback, ctx) { | ||
// Collects the subgraph that will be returned as a result | ||
// Collects a subgraph that will be returned as a result | ||
var result = {}; | ||
@@ -167,43 +184,153 @@ queries = _.isArray(queries) ? queries : [queries]; | ||
if (!qry.type) return callback('ERROR: No type attribute specified with query.'); | ||
if (!qry.type && !qry._id) return callback('ERROR: No type or _id attribute specified with query.'); | ||
var typeName = qry.type.split('/')[2]; | ||
var typeName = qry.type ? qry.type.split('/')[2] : null; | ||
delete qry.type; | ||
// Pull off relationship definitions (in case of eager loading relationships) | ||
var relationships = {}; | ||
_.each(qry, function(val, property) { | ||
if (typeof val === 'object' && !_.isArray(val)) { | ||
relationships[property] = val; | ||
delete qry[property]; | ||
} | ||
}); | ||
// Just the properties | ||
var properties = _.keys(qry); | ||
// Holds the current traversal path in a nested query | ||
var currentPath = []; | ||
// Lookup view | ||
db.get('_design/'+typeName, function(err, node) { | ||
// Pick the right index based on query parameters | ||
var viewName = null; | ||
_.each(node.views, function(view, key) { | ||
if (view.properties.length == properties.length && _.intersect(view.properties, properties).length == view.properties.length) viewName = key; | ||
}); | ||
// Depending on the current working path, reveal relationships to be | ||
// traversed for the node currently processed | ||
function currentRelationships(currentPath) { | ||
if (currentPath.length === 0) return relationships; | ||
var path = _.clone(currentPath); | ||
var res = relationships; | ||
var key; | ||
while (key = path.splice(0,1)[0]) { | ||
res = res[key]; | ||
} | ||
delete res._recursive; // Ignore directives | ||
return res; | ||
} | ||
// Fetch associated nodes for a certain property | ||
// -------- | ||
function fetchAssociatedProperty(node, property, recursive, callback) { | ||
if (!node[property]) return callback(); // Done if null/undefined | ||
if (viewName) { | ||
// Use view to lookup for matching objects efficiently | ||
var key = []; | ||
_.each(node.views[viewName].properties, function(p) { | ||
key.push(qry[p]); | ||
// Update currentPath to be the current property traversed | ||
currentPath.push(property); | ||
var references = _.isArray(node[property]) ? node[property] : [node[property]]; | ||
var nodes = {}; | ||
async.forEachSeries(references, function(nodeId, callback) { | ||
if (result[nodeId]) return callback(); // Skip if already in the result | ||
db.get(nodeId, function(err, node) { | ||
if (err) return callback(err); | ||
if (!node) return callback(); // Ignore deleted nodes | ||
result[node._id] = node; | ||
nodes[node._id] = node; | ||
fetchAssociated(node, callback); | ||
}); | ||
db.view(typeName+'/'+viewName, {key: key}, function(err, res) { | ||
if (err) callback(err); | ||
_.each(res.rows, function(row) { | ||
result[row.value._id] = row.value; | ||
}, function(err) { | ||
// Once ready remove that property from the currentPath | ||
currentPath.pop(); | ||
// And dig deeper in a recursive scenario | ||
if (recursive) { | ||
async.forEachSeries(Object.keys(nodes), function(nodeId, callback) { | ||
fetchAssociatedProperty(result[nodeId], property, true, callback); | ||
}, callback); | ||
} else { | ||
callback(err); | ||
} | ||
}); | ||
} | ||
function fetchAssociated(node, callback) { | ||
var properties = currentRelationships(currentPath); | ||
// Based on the current relationships fetch associated properties | ||
async.forEachSeries(Object.keys(properties), function(property, callback) { | ||
fetchAssociatedProperty(node, property, properties[property]._recursive, callback); | ||
}, callback); | ||
} | ||
// Resolve references based on specified query paths | ||
// -------- | ||
function resolveReferences(nodes, callback) { | ||
// TODO: Can we do this in parallel? | ||
async.forEachSeries(Object.keys(nodes), function(node, callback) { | ||
fetchAssociated(nodes[node], callback); | ||
}, callback); | ||
} | ||
// Typed Query based on CouchDB Views | ||
// -------- | ||
function executeTypedQuery(callback) { | ||
db.get('_design/'+typeName, function(err, node) { | ||
// Pick the right index based on query parameters | ||
var viewName = null; | ||
_.each(node.views, function(view, key) { | ||
if (view.properties.length == properties.length && _.intersect(view.properties, properties).length === view.properties.length) viewName = key; | ||
}); | ||
if (viewName) { | ||
// Use view to lookup matching objects efficiently | ||
var key = []; | ||
_.each(node.views[viewName].properties, function(p) { | ||
key.push(qry[p]); | ||
}); | ||
callback(); | ||
}); | ||
} else { // Fetch all objects of this type and check manually | ||
console.log('WARNING: No index could be found for this query:'); | ||
console.log(qry); | ||
qry["type|="] = "/type/"+typeName; | ||
db.view(typeName+'/all', function(err, res) { | ||
db.view(typeName+'/'+viewName, {key: key}, function(err, res) { | ||
if (err) callback(err); | ||
_.each(res.rows, function(row) { | ||
result[row.value._id] = row.value; | ||
}); | ||
callback(); | ||
}); | ||
} else { // Fetch all objects of this type and check manually | ||
console.log('WARNING: No index could be found for this query:'); | ||
console.log(qry); | ||
qry["type|="] = "/type/"+typeName; | ||
db.view(typeName+'/all', function(err, res) { | ||
if (err) return callback(err); | ||
_.each(res.rows, function(row) { | ||
if (Data.matches(row.value, qry)) result[row.value._id] = row.value; | ||
}); | ||
callback(); | ||
}); | ||
} | ||
}); | ||
} | ||
// Untyped Query based on ids | ||
// -------- | ||
function executeUntypedQuery(callback) { | ||
var references = _.isArray(qry._id) ? qry._id : [qry._id]; | ||
async.forEach(references, function(nodeId, callback) { | ||
if (result[nodeId]) callback(); // Skip if already included in the result | ||
db.get(nodeId, function(err, node) { | ||
if (err) return callback(err); | ||
_.each(res.rows, function(row) { | ||
if (Data.matches(row.value, qry)) result[row.value._id] = row.value; | ||
}); | ||
if (!node) return callback(); // Ignore deleted nodes | ||
result[node._id] = node; | ||
callback(); | ||
}); | ||
} | ||
}); | ||
}, function(err) { | ||
callback(err); | ||
}); | ||
} | ||
// Execute query either typed (by id) or untyped (using a CouchDB View) | ||
qry._id ? executeUntypedQuery(function() { resolveReferences(result, callback); }) | ||
: executeTypedQuery(function() { resolveReferences(result, callback); }); | ||
} | ||
@@ -220,2 +347,3 @@ | ||
self.db = db; | ||
@@ -222,0 +350,0 @@ |
246
data.js
@@ -23,3 +23,3 @@ // (c) 2011 Michael Aufreiter | ||
// Current version of the library. Keep in sync with `package.json`. | ||
Data.VERSION = '0.3.0'; | ||
Data.VERSION = '0.4.0'; | ||
@@ -59,6 +59,6 @@ // Require Underscore, if we're on the server, and it's not already present. | ||
// Extract operator | ||
var matches = key.match(/^([a-z_]{1,30})(!=|>|>=|<|<=|\|=|&=)?$/), | ||
var matches = key.match(/^([a-z_]{1,30})(=|==|!=|>|>=|<|<=|\|=|&=)?$/), | ||
property = matches[1], | ||
operator = matches[2] || '=='; | ||
operator = matches[2] || (property == "type" || _.isArray(value) ? "|=" : "="); | ||
if (operator === "|=") { // one of operator | ||
@@ -336,3 +336,3 @@ var values = _.isArray(value) ? value : [value]; | ||
get: function (key) { | ||
return this.data[key]; | ||
return this.data.hasOwnProperty(key) ? this.data[key] : undefined; | ||
}, | ||
@@ -351,8 +351,15 @@ | ||
// 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.select(function(value, key, i) { | ||
return i >= index; | ||
}); | ||
return this.range(index, this.length-1); | ||
}, | ||
@@ -459,7 +466,10 @@ | ||
result = new Data.Hash(); | ||
this.each(function(value, key) { | ||
hash.each(function(value2, key2) { | ||
if (key === key2) result.set(key, value); | ||
}); | ||
// 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); | ||
}); | ||
@@ -475,8 +485,6 @@ return result; | ||
this.each(function(value, key) { | ||
if (!result.get(key)) | ||
result.set(key, value); | ||
result.set(key, value); | ||
}); | ||
hash.each(function(value, key) { | ||
if (!result.get(key)) | ||
result.set(key, value); | ||
if (!result.get(key)) result.set(key, value); | ||
}); | ||
@@ -488,7 +496,6 @@ return result; | ||
difference: function(hash) { | ||
var that = this, | ||
result = this.clone(); | ||
hash.each(function(value, key) { | ||
if (result.get(key)) result.del(key); | ||
var that = this; | ||
result = new Data.Hash(); | ||
this.each(function(value, key) { | ||
if (!hash.get(key)) result.set(key, value); | ||
}); | ||
@@ -578,3 +585,2 @@ return result; | ||
// Data.Transformers | ||
@@ -590,3 +596,3 @@ // -------------- | ||
gspec[type._id] = {"type": "/type/type", "properties": {}}; | ||
gspec[type._id] = {"type": "/type/type", "properties": {}, indexes: type.indexes}; | ||
@@ -600,3 +606,3 @@ // Include group keys to the output graph | ||
_.each(properties, function(options, key) { | ||
var p = type.properties().get(key).toJSON(); | ||
var p = type.properties().get(options.property || key).toJSON(); | ||
if (options.name) p.name = options.name; | ||
@@ -607,3 +613,3 @@ gspec[type._id].properties[key] = p; | ||
var groupedGraph = new Data.Graph(gspec); | ||
// Compute group memberships | ||
_.each(keys, function(key) { | ||
@@ -615,2 +621,3 @@ groups[key] = type.properties().get(key).all('values'); | ||
var members = new Data.Hash(); | ||
_.each(keys, function(k, index) { | ||
@@ -621,2 +628,6 @@ var objects = groups[keys[index]].get(key[index]).referencedObjects; | ||
// Empty group key | ||
if (key.length === 0) members = g.objects(); | ||
if (members.length === 0) return null; | ||
var res = {type: type._id}; | ||
@@ -628,5 +639,5 @@ _.each(gspec[type._id].properties, function(p, pk) { | ||
var numbers = members.map(function(obj) { | ||
return obj.get(pk); | ||
return obj.get(properties[pk].property || pk); | ||
}); | ||
var aggregator = properties[pk].aggregator || Data.Aggregators.SUM | ||
var aggregator = properties[pk].aggregator || Data.Aggregators.SUM; | ||
res[pk] = aggregator(numbers); | ||
@@ -640,3 +651,4 @@ } | ||
if (keyIndex === keys.length-1) { | ||
groupedGraph.set(key.join('::'), aggregate(key)); | ||
var aggregatedItem = aggregate(key); | ||
if (aggregatedItem) groupedGraph.set(key.join('::'), aggregatedItem); | ||
} else { | ||
@@ -649,3 +661,2 @@ keyIndex += 1; | ||
} | ||
extractGroups(-1, []); | ||
@@ -823,7 +834,7 @@ return groupedGraph; | ||
if (typeof v === 'object') v = that.type.g.set(null, v)._id; | ||
val = that.type.g.get('objects', v); | ||
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('objects', v, val); | ||
that.type.g.set('nodes', v, val); | ||
} | ||
@@ -906,2 +917,3 @@ } else { | ||
that.replace('properties', new Data.Hash); | ||
// Extract properties | ||
@@ -920,3 +932,3 @@ _.each(type.properties, function(property, key) { | ||
objects: function() { | ||
return this.all('objects'); | ||
return this.all('nodes'); | ||
}, | ||
@@ -970,3 +982,3 @@ | ||
this.html_id = id.replace(/\//g, '_'); | ||
this.dirty = true; // Every constructed node is dirty by default | ||
this._dirty = true; // Every constructed node is dirty by default | ||
@@ -1011,14 +1023,12 @@ this.errors = []; // Stores validation errors | ||
// Pull off _id and _rev properties | ||
delete this.data._id; | ||
this._rev = this.data._rev; // delete this.data._rev; | ||
this._rev = this.data._rev; | ||
this._conflicted = this.data._conflicted; | ||
this._deleted = this.data._deleted; // delete this.data._deleted; | ||
this._deleted = this.data._deleted; | ||
// Initialize primary type (backward compatibility) | ||
this.type = this.g.get('objects', _.last(types)); | ||
this.type = this.g.get('nodes', _.last(types)); | ||
// Initialize types | ||
_.each(types, function(type) { | ||
that._types.set(type, that.g.get('objects', type)); | ||
that._types.set(type, that.g.get('nodes', type)); | ||
// Register properties for all types | ||
@@ -1039,4 +1049,3 @@ that._types.get(type).all('properties').each(function(property, key) { | ||
}); | ||
if (this.dirty) this.g.trigger('dirty'); | ||
if (this._dirty) this.g.trigger('dirty'); | ||
}, | ||
@@ -1137,3 +1146,3 @@ | ||
that.dirty = true; | ||
that._dirty = true; | ||
that.g.trigger('dirty'); | ||
@@ -1185,3 +1194,3 @@ }); | ||
this.watchers = {}; | ||
this.replace('objects', new Data.Hash()); | ||
this.replace('nodes', new Data.Hash()); | ||
if (!g) return; | ||
@@ -1228,2 +1237,11 @@ this.merge(g, dirty); | ||
// 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 | ||
@@ -1236,5 +1254,5 @@ merge: function(g, dirty) { | ||
if (node.type === '/type/type' || node.type === 'type') { | ||
if (!that.get('objects', key)) { | ||
that.set('objects', key, new Data.Type(that, key, node)); | ||
that.get(key).dirty = dirty; | ||
if (!that.get('nodes', key)) { | ||
that.set('nodes', key, new Data.Type(that, key, node)); | ||
that.get(key)._dirty = dirty; | ||
} | ||
@@ -1249,7 +1267,7 @@ return true; | ||
if (node.type !== '/type/type' && node.type !== 'type') { | ||
var res = that.get('objects', key); | ||
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('objects', key, res); | ||
that.set('nodes', key, res); | ||
} else { | ||
@@ -1261,8 +1279,9 @@ // Populate existing node with data in order to be rebuilt | ||
_.each(types, function(type) { | ||
if (!that.get('objects', type)) { | ||
if (!that.get('nodes', type)) { | ||
throw new Error("Type '"+type+"' not found for "+key+"..."); | ||
} | ||
that.get('objects', type).set('objects', key, res); | ||
that.get('nodes', type).set('nodes', key, res); | ||
}); | ||
that.get(key).dirty = dirty; | ||
that.get(key)._dirty = dirty; | ||
if (!node._id) node._id = key; | ||
return true; | ||
@@ -1272,22 +1291,27 @@ } | ||
}); | ||
// Now that all objects are registered we can build them | ||
this.objects().each(function(r, key, index) { | ||
if (r.data) r.build(); | ||
// 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(); | ||
}); | ||
return this; | ||
}, | ||
// Set (add) a new node on the graph | ||
set: function(id, properties) { | ||
var that = this; | ||
var types = _.isArray(properties.type) ? properties.type : [properties.type]; | ||
if (arguments.length === 2) { | ||
id = id ? id : Data.uuid('/' + _.last(_.last(types).split('/')) + '/'); | ||
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]; | ||
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(id) ? that.get(id) : new Data.Object(that, id, properties, true); | ||
res.data = properties; | ||
res.dirty = true; | ||
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('objects', id, res); | ||
return this.get('objects', id); | ||
this.set('nodes', node._id, res); | ||
return res; | ||
} else { // Delegate to Data.Node#set | ||
@@ -1299,7 +1323,5 @@ return Data.Node.prototype.set.call(this, arguments[0], arguments[1], arguments[2]); | ||
// API method for accessing objects in the graph space | ||
// TODO: Ask the datastore if the node is not known in the local graph | ||
// use async method queues for this! | ||
get: function(id) { | ||
if (arguments.length === 1) { | ||
return this.get('objects', id); | ||
return this.get('nodes', id); | ||
} else { | ||
@@ -1315,3 +1337,3 @@ return Data.Node.prototype.get.call(this, arguments[0], arguments[1]); | ||
node._deleted = true; | ||
node.dirty = true; | ||
node._dirty = true; | ||
// Remove registered values | ||
@@ -1354,4 +1376,4 @@ node.properties().each(function(p, key) { | ||
}, | ||
// Synchronize dirty nodes with the database | ||
// Synchronize dirty nodes with the backend | ||
sync: function(callback) { | ||
@@ -1363,8 +1385,5 @@ callback = callback || function() {}; | ||
var validNodes = new Data.Hash(); | ||
var invalidNodes = nodes.select(function(node, key) { | ||
nodes.select(function(node, key) { | ||
if (!node.validate || (node.validate && node.validate())) { | ||
validNodes.set(key, node); | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
@@ -1374,22 +1393,24 @@ }); | ||
this.adapter.write(validNodes.toJSON(), function(err, g) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
that.merge(g, false); | ||
// Check for rejectedNodes | ||
validNodes.each(function(n, key) { | ||
if (g[key]) { | ||
n.dirty = false; | ||
n._rejected = false; | ||
} else { | ||
n._rejected = true; | ||
} | ||
}); | ||
if (that.conflictedNodes().length > 0) that.trigger('conflicted'); | ||
if (that.rejectedNodes().length > 0) that.trigger('rejected'); | ||
callback(invalidNodes.length > 0 ? 'Some invalid nodes' : null, invalidNodes); | ||
} | ||
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; | ||
} | ||
}); | ||
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); | ||
}); | ||
@@ -1407,3 +1428,3 @@ }, | ||
types: function() { | ||
return this.all('objects').select(function(node, key) { | ||
return this.all('nodes').select(function(node, key) { | ||
return node.type === '/type/type' || node.type === 'type'; | ||
@@ -1415,3 +1436,3 @@ }); | ||
objects: function() { | ||
return this.all('objects').select(function(node, key) { | ||
return this.all('nodes').select(function(node, key) { | ||
return node.type !== '/type/type' && node.type !== 'type' && node.data && !node._deleted; | ||
@@ -1424,4 +1445,4 @@ }); | ||
dirtyNodes: function() { | ||
return this.all('objects').select(function(obj, key) { | ||
return (obj.dirty && (obj.data || obj instanceof Data.Type)); | ||
return this.all('nodes').select(function(obj, key) { | ||
return (obj._dirty && (obj.data || obj instanceof Data.Type)); | ||
}); | ||
@@ -1432,3 +1453,3 @@ }, | ||
invalidNodes: function() { | ||
return this.all('objects').select(function(obj, key) { | ||
return this.all('nodes').select(function(obj, key) { | ||
return (obj.errors && obj.errors.length > 0); | ||
@@ -1440,3 +1461,3 @@ }); | ||
conflictedNodes: function() { | ||
return this.all('objects').select(function(obj, key) { | ||
return this.all('nodes').select(function(obj, key) { | ||
return obj._conflicted; | ||
@@ -1446,4 +1467,5 @@ }); | ||
// Nodes that got rejected during sync | ||
rejectedNodes: function() { | ||
return this.all('objects').select(function(obj, key) { | ||
return this.all('nodes').select(function(obj, key) { | ||
return obj._rejected; | ||
@@ -1458,3 +1480,3 @@ }); | ||
// Serialize object nodes | ||
this.all('objects').each(function(obj, key) { | ||
this.all('nodes').each(function(obj, key) { | ||
// Only serialize fetched nodes | ||
@@ -1486,4 +1508,6 @@ if (obj.data || obj instanceof Data.Type) { | ||
var that = this, | ||
gspec = { "/type/item": {"type": "/type/type", "properties": {}}}; | ||
gspec = { "/type/item": { "type": "/type/type", "properties": {}} }; | ||
if (spec) gspec["/type/item"]["indexes"] = spec.indexes || {}; | ||
// Convert to Data.Graph serialization format | ||
@@ -1517,2 +1541,3 @@ if (spec) { | ||
find: function(query) { | ||
query["type|="] = "/type/item"; | ||
return this.g.find(query); | ||
@@ -1538,3 +1563,3 @@ }, | ||
properties: function() { | ||
return this.g.get('objects', '/type/item').all('properties'); | ||
return this.g.get('nodes', '/type/item').all('properties'); | ||
}, | ||
@@ -1547,2 +1572,7 @@ | ||
// Convenience function for accessing indexes defined on the collection | ||
indexes: function() { | ||
return this.g.get('/type/item').indexes; | ||
}, | ||
// Serialize | ||
@@ -1549,0 +1579,0 @@ toJSON: function() { |
@@ -1,33 +0,35 @@ | ||
(function(){var e;e=typeof exports!=="undefined"?exports:this.Data={};e.VERSION="0.3.0";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore");e.VALUE_TYPES=["string","object","number","boolean","date"];e.isValueType=function(a){return f.include(e.VALUE_TYPES,a)};e.matches=function(a,b){b=f.isArray(b)?b:[b];var c=false;f.each(b,function(d){if(!c){var g=false;f.each(d,function(h,j){if(!g){var k,i=j.match(/^([a-z_]{1,30})(!=|>|>=|<|<=|\|=|&=)?$/),l=i[1];i=i[2]||"==";if(i==="|="){i= | ||
f.isArray(h)?h:[h];var m=f.isArray(a[l])?a[l]:[a[l]];k=false;f.each(i,function(o){if(f.include(m,o))k=true})}else if(i==="&="){i=f.isArray(h)?h:[h];m=f.isArray(a[l])?a[l]:[a[l]];k=f.intersect(m,i).length===i.length}else switch(i){case "!=":k=!f.isEqual(a[l],h);break;case ">":k=a[l]>h;break;case ">=":k=a[l]>=h;break;case "<":k=a[l]<h;break;case "<=":k=a[l]<=h;break;default:k=f.isEqual(a[l],h);break}if(!k)return g=true}});if(!g)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("")};f.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,g=c.length;d<g;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,g;if(!(c=this._callbacks))return this;if(b=c[a]){d=0;for(g=b.length;d< | ||
g;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(g=b.length;d<g;d++)b[d].apply(this,arguments)}return this}};var q=function(){};f.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&&f.extend(d.prototype,b);c&&f.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)f.each(a,function(c,d){b.set(d,c)});else a instanceof Object&&f.each(a,function(c,d){b.set(d,c)});this.initialize&&this.initialize(attributes,options)};f.extend(e.Hash.prototype,f.Events,{clone:function(){var a=new e.Hash;a.length=this.length;f.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, | ||
j,k){return k<c});var g=this.select(function(h,j,k){return k>=c});this.keyOrder=[].concat(d.keyOrder);this.keyOrder.push(a);this.keyOrder=this.keyOrder.concat(g.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[a]}, | ||
at:function(a){return this.data[this.keyOrder[a]]},first:function(){return this.at(0)},rest:function(a){return this.select(function(b,c,d){return d>=a})},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;f.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 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,g,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,g,h){a.call(c,d,g,h)&&b.set(g,d)});return b},sort:function(a){var b=this.clone();sortedKeys=b.toArray().sort(a);b.keyOrder=f.map(sortedKeys,function(c){return c.key}); | ||
return b},intersect:function(a){var b=new e.Hash;this.each(function(c,d){a.each(function(g,h){d===h&&b.set(d,c)})});return b},union:function(a){var b=new e.Hash;this.each(function(c,d){b.get(d)||b.set(d,c)});a.each(function(c,d){b.get(d)||b.set(d,c)});return b},difference:function(a){var b=this.clone();a.each(function(c,d){b.get(d)&&b.del(d)});return b}});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(f.isNumber(c))b+=c});return b};e.Aggregators.MIN=function(a){var b=Infinity;a.each(function(c){if(f.isNumber(c)&&c<b)b=c});return b};e.Aggregators.MAX=function(a){var b=-Infinity;a.each(function(c){if(f.isNumber(c)&&c>b)b=c});return b};e.Aggregators.AVG=function(a){var b=0,c=0;a.each(function(d){if(f.isNumber(d)){b+=d;c+=1}});return 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 g(i){var l=new e.Hash;f.each(c,function(o,n){var p=k[c[n]].get(i[n]).referencedObjects;l=n===0?l.union(p):l.intersect(p)});var m={type:b._id};f.each(j[b._id].properties,function(o,n){if(f.include(c,n))m[n]=i[f.indexOf(c,n)];else{var p=l.map(function(r){return r.get(n)});m[n]=(d[n].aggregator|| | ||
e.Aggregators.SUM)(p)}});return m}function h(i,l){if(i===c.length-1)j[l.join("::")]=g(l);else{i+=1;k[c[i]].each(function(m,o){h(i,l.concat([o]))})}}var j={};b=a.get(b);var k={};j[b._id]={type:"/type/type",properties:{}};f.each(c,function(i){j[b._id].properties[i]=b.properties().get(i).toJSON()});f.each(d,function(i,l){var m=b.properties().get(l).toJSON();if(i.name)m.name=i.name;j[b._id].properties[l]=m});f.each(c,function(i){k[i]=b.properties().get(i).all("values")});h(-1,[]);return new e.Graph(j)}}; | ||
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};f.extend(e.Node.prototype,f.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=f.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=f.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;f.each(a,function(g){if(g){var h;if(b.all(c.key))h=b.all(c.key).get(g);if(!h){h=c.get("values",g);if(!h){if(c.isObjectType()){if(typeof g=== | ||
"object")g=c.type.g.set(null,g)._id;h=c.type.g.get("objects",g);if(!h){h=new e.Object(c.type.g,g);c.type.g.set("objects",g,h)}}else{h=new e.Node({value:g});h.referencedObjects=new e.Hash}c.set("values",g,h)}h.referencedObjects.set(b._id,b)}d.set(g,h)}});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,g){d.referencedObjects.length>1?d.referencedObjects.del(b._id):c.all("values").del(g)})},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=f.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;f.each(c.properties,function(g,h){d.set("properties",h,new e.Property(d,h,g))})},properties:function(){return this.all("properties")}, | ||
objects:function(){return this.all("objects")},toJSON:function(){var a={_id:this._id,type:"/type/type",name:this.name,properties:{}};if(this._rev)a._rev=this._rev;if(this.meta&&f.keys(this.meta).length>0)a.meta=this.meta;if(this.indexes&&f.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&&f.keys(b.meta).length>0)c.meta=b.meta});return a}});e.Object=f.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=f.isArray(this.data.type)?this.data.type:[this.data.type];if(!this.data)throw Error("Object has no data, and cannot be built");delete this.data._id;this._rev=this.data._rev;this._conflicted=this.data._conflicted;this._deleted=this.data._deleted;this.type=this.g.get("objects",f.last(b));f.each(b,function(c){a._types.set(c,a.g.get("objects",c));a._types.get(c).all("properties").each(function(d,g){function h(j){j=f.isArray(j)?j:[j];a.replace(d.key,d.registerValues(j, | ||
a))}if(a.data[g]!==undefined)h(a.data[g]);else d["default"]&&h(d["default"])})});this.dirty&&this.g.trigger("dirty")},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,g=function(h,j){if(f.include(j,typeof h))return true;if(!h.data)return true;if(h instanceof | ||
e.Object&&f.intersect(j,h.types().keys()).length>0)return true;if(typeof h==="object"&&f.include(j,h.constructor.name.toLowerCase()))return true;return false};b.unique&&!g(a.get(c),d)&&a.errors.push({property:c,message:'Invalid type for property "'+b.name+'"'});!b.unique&&!f.all(a.get(c).values(),function(h){return g(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)f.each(a,function(c,d){var g=b.properties().get(d);if(g){b.replace(g.key,g.registerValues(f.isArray(c)?c:[c],b));b.dirty=true;b.g.trigger("dirty")}});else return e.Node.prototype.set.call(this, | ||
arguments[0],arguments[1],arguments[2])},toJSON:function(){var a=this;result={};f.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}});f.extend(e.Object.prototype,f.Events);e.Graph=f.inherits(e.Node,{constructor:function(a, | ||
b){e.Node.call(this);this.watchers={};this.replace("objects",new e.Hash);a&&this.merge(a,b)},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(){})},merge:function(a,b){var c=this;f.select(a,function(d,g){if(d.type==="/type/type"||d.type==="type"){if(!c.get("objects",g)){c.set("objects",g,new e.Type(c,g,d));c.get(g).dirty=b}return true}return false});f.select(a,function(d,g){if(d.type!=="/type/type"&&d.type!=="type"){var h=c.get("objects",g),j=f.isArray(d.type)?d.type:[d.type];if(h)h.data=d;else{h=new e.Object(c, | ||
g,d);c.set("objects",g,h)}f.each(j,function(k){if(!c.get("objects",k))throw Error("Type '"+k+"' not found for "+g+"...");c.get("objects",k).set("objects",g,h)});c.get(g).dirty=b;return true}return false});this.objects().each(function(d){d.data&&d.build()});return this},set:function(a,b){var c=f.isArray(b.type)?b.type:[b.type];if(arguments.length===2){a=a?a:e.uuid("/"+f.last(f.last(c).split("/"))+"/");c=this.get(a)?this.get(a):new e.Object(this,a,b,true);c.data=b;c.dirty=true;c.build();this.set("objects", | ||
a,c);return this.get("objects",a)}else return e.Node.prototype.set.call(this,arguments[0],arguments[1],arguments[2])},get:function(a){return arguments.length===1?this.get("objects",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 g=b.all(d);g&&c.unregisterValues(g,b)});this.trigger("dirty")}},find:function(a){return this.objects().select(function(b){return e.matches(b.toJSON(),a)})}, | ||
fetch:function(a,b,c){var d=this,g=new e.Hash;if(typeof b==="function"&&typeof c==="undefined"){c=b;b={}}this.adapter.read(a,b,function(h,j){if(j){d.merge(j,false);f.each(j,function(k,i){g.set(i,d.get(i))})}h?c(h):c(null,g)})},sync:function(a){a=a||function(){};var b=this,c=b.dirtyNodes(),d=new e.Hash,g=c.select(function(h,j){if(!h.validate||h.validate&&h.validate()){d.set(j,h);return false}else return true});this.adapter.write(d.toJSON(),function(h,j){if(h)a(h);else{b.merge(j,false);d.each(function(k, | ||
i){if(j[i]){k.dirty=false;k._rejected=false}else k._rejected=true});b.conflictedNodes().length>0&&b.trigger("conflicted");b.rejectedNodes().length>0&&b.trigger("rejected");a(g.length>0?"Some invalid nodes":null,g)}})},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("objects").select(function(a){return a.type==="/type/type"||a.type==="type"})},objects:function(){return this.all("objects").select(function(a){return a.type!== | ||
"/type/type"&&a.type!=="type"&&a.data&&!a._deleted})},dirtyNodes:function(){return this.all("objects").select(function(a){return a.dirty&&(a.data||a instanceof e.Type)})},invalidNodes:function(){return this.all("objects").select(function(a){return a.errors&&a.errors.length>0})},conflictedNodes:function(){return this.all("objects").select(function(a){return a._conflicted})},rejectedNodes:function(){return this.all("objects").select(function(a){return a._rejected})},toJSON:function(){var a={};this.all("objects").each(function(b, | ||
c){if(b.data||b instanceof e.Type)a[c]=b.toJSON()});return a}});f.extend(e.Graph.prototype,f.Events);e.Collection=function(a){var b=this,c={"/type/item":{type:"/type/type",properties:{}}};if(a){f.each(a.properties,function(d,g){c["/type/item"].properties[g]=d});this.g=new e.Graph(c);f.each(a.items,function(d,g){b.set(g,d)})}else this.g=new e.Graph};f.extend(e.Collection.prototype,{get:function(){return this.g.get.apply(this.g,arguments)},set:function(a,b){this.g.set(a,f.extend(b,{type:"/type/item"}))}, | ||
find:function(a){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("objects","/type/item").all("properties")},items:function(){return this.g.objects()},toJSON:function(){return{properties:this.g.toJSON()["/type/item"].properties,items:this.g.objects().toJSON()}}})})(); | ||
(function(){var e;e=typeof exports!=="undefined"?exports:this.Data={};e.VERSION="0.4.0";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")},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")}});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);a&&this.merge(a,b)},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= | ||
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=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()});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);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")}},find:function(a){return this.objects().select(function(b){return e.matches(b.toJSON(),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});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(){var a={};this.all("nodes").each(function(b,c){if(b.data||b instanceof e.Type)a[c]=b.toJSON()});return a}});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()}}})})(); |
@@ -10,8 +10,4 @@ var fs = require('fs'); | ||
var graph = new Data.Graph(seed, true); | ||
var graph = new Data.Graph(seed, true).connect('couch', { url: config.couchdb_url }); | ||
// Setup Data.Adapter | ||
graph.setAdapter('couch', { url: config.couchdb_url }); | ||
if (process.argv[2] == "--flush") { | ||
@@ -18,0 +14,0 @@ graph.adapter.flush(function(err) { |
@@ -11,3 +11,3 @@ { | ||
"main" : "index", | ||
"version" : "0.3.0" | ||
"version" : "0.4.0" | ||
} |
@@ -25,3 +25,4 @@ var _ = require('underscore'); | ||
query = JSON.parse(req.query.qry), | ||
options = JSON.parse(req.query.options) | ||
options = JSON.parse(req.query.options); | ||
graph.adapter.read(JSON.parse(req.query.qry), JSON.parse(req.query.options), function(err, g) { | ||
@@ -28,0 +29,0 @@ err ? res.send(callback+"({\"error\": "+JSON.stringify(err)+"});") |
@@ -154,2 +154,13 @@ // Data.js — A utility belt for data manipulation | ||
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("Data.Hash#sort", function() { | ||
@@ -258,2 +269,13 @@ items.set("ch", "Switzerland"); | ||
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); | ||
}); | ||
test("Data.Hash Events", function() { | ||
@@ -405,3 +427,2 @@ persons = new Data.Hash(); | ||
module("Data.Graph", { | ||
@@ -429,3 +450,3 @@ setup: function() { | ||
documentType = graph.get('objects', '/type/document'); | ||
ok(documentType.all('properties').length === 4); | ||
ok(documentType.all('properties').length === 5); | ||
ok(documentType.key === '/type/document'); | ||
@@ -533,4 +554,4 @@ ok(documentType.name === 'Document'); | ||
test("Set new nodes on the graph", function() { | ||
var substance = graph.set('/document/substance', { | ||
var substance = graph.set({ | ||
"_id": "/document/substance", | ||
"type": "/type/document", | ||
@@ -548,3 +569,2 @@ "title": "Substance Introduction", | ||
test("Set value properties of existing nodes", function() { | ||
@@ -663,11 +683,20 @@ // Value properties | ||
test("grouping", function() { | ||
var languages = c.group(["official_language"], { | ||
'area': { aggregator: Data.Aggregators.SUM, name: "Total Area" }, | ||
'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') === 10071495); | ||
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) | ||
}); | ||
@@ -674,0 +703,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
29
9
6
4878040
36
7214