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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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