stork-odm
Advanced tools
Comparing version 0.1.7 to 0.1.8
@@ -49,4 +49,6 @@ /*jslint node: true, nomen: true, white: true */ | ||
, isoStringPropertiesToDates: function(obj) { | ||
Object.keys(obj).forEach(function(key) { | ||
, isoStringPropertiesToDates: function(obj, properties) { | ||
properties = properties || Object.keys(obj); | ||
properties.forEach(function(key) { | ||
if (obj[key].toString().match(dateRegex)) { | ||
@@ -53,0 +55,0 @@ obj[key] = new Date(obj[key]); |
@@ -66,2 +66,13 @@ /*jslint node: true, white: true */ | ||
definers.binary = function(name, options) { | ||
if (name === undefined) { | ||
throw new Error('binary definer requires a name'); | ||
} | ||
this.properties[name] = {type: 'object'}; | ||
if (options) { | ||
required(options, this.properties[name]); | ||
} | ||
this.binaryProperties.push(name); | ||
}; | ||
definers.bool = function(name, options) { | ||
@@ -68,0 +79,0 @@ if (name === undefined) { |
@@ -24,2 +24,3 @@ /*jslint node: true, nomen: true, white: true */ | ||
, refProperties = schema.refProperties | ||
, binaryProperties = schema.binaryProperties | ||
, o = this | ||
@@ -51,2 +52,31 @@ , result | ||
binaryProperties.forEach(function(propertyName) { | ||
var prop = o[propertyName] | ||
, isBuffer = prop && prop instanceof Buffer | ||
, isFiley = prop && prop.content instanceof Buffer && prop.type | ||
; | ||
if (prop === undefined) { | ||
return; | ||
} | ||
result.errors = result.errors.filter(function (r) { | ||
return !(r.property === propertyName && r.attribute === 'required'); | ||
}); | ||
result.errors = result.errors.filter(function (r) { | ||
return !(r.property === propertyName && r.attribute === 'type'); | ||
}); | ||
if (!isBuffer && !isFiley) { | ||
result.valid = false; | ||
result.errors.push({ | ||
attribute: 'type', | ||
property: propertyName, | ||
expected: 'object', | ||
message: 'must be of type Buffer or object like {type: [mime/type], content: [Buffer]}' | ||
}); | ||
} | ||
}); | ||
refProperties.forEach(function(propertyName) { | ||
@@ -131,2 +161,18 @@ var prop = o[propertyName] | ||
if (nproto._attachments) { | ||
Object.keys(nproto._attachments).forEach(function (key) { | ||
nproto[key] = { | ||
type: nproto._attachments[key].content_type, | ||
length: nproto._attachments[key].length, | ||
digest: nproto._attachments[key].digest, | ||
pipeFrom: function (db) { | ||
var nanodb, att; | ||
nanodb = common.makeNano(db, typeName + '#' + key); | ||
att = nanodb.attachment.get(nproto._id, key); | ||
return att.pipe.bind(att); | ||
} | ||
}; | ||
}); | ||
} | ||
for (prop in nproto) { | ||
@@ -133,0 +179,0 @@ if (typeof nproto[prop] === 'function') { |
@@ -6,2 +6,3 @@ /*jslint node: true, nomen: true, white: true */ | ||
, util = require('utile') | ||
, async = require('async') | ||
; | ||
@@ -98,3 +99,3 @@ | ||
to.sync = function(cb) { | ||
to.sync = function(callback) { | ||
var bsre = /\\/g | ||
@@ -119,2 +120,3 @@ , sortKeyMap = function(o) { | ||
} | ||
, designDocName = '_design/' + typeName | ||
; | ||
@@ -135,3 +137,24 @@ Object.keys(schema.views).forEach(function(viewName) { | ||
}); | ||
this.db.insert(doc, '_design/' + typeName, cb); | ||
async.waterfall([ | ||
function(cb) { | ||
/*jslint unparam: true*/ | ||
db.get(designDocName, function (e, entry) { | ||
cb(null, entry); | ||
}); | ||
/*jslint unparam: false*/ | ||
} | ||
, function(entry) { | ||
var params; | ||
params = designDocName; | ||
if (entry) { | ||
/*jslint nomen: true*/ | ||
params = { | ||
doc_name: params, | ||
rev: entry._rev | ||
}; | ||
/*jslint nomen: false*/ | ||
} | ||
db.insert(doc, params, callback); | ||
} | ||
]); | ||
}; | ||
@@ -138,0 +161,0 @@ |
@@ -6,2 +6,3 @@ /*jslint node: true, nomen: true, white: true, forin: true*/ | ||
, common = require('./common') | ||
, uuid = require('node-uuid') | ||
; | ||
@@ -33,2 +34,6 @@ | ||
, childProperties = schema.childProperties | ||
, binaryProperties = schema.binaryProperties | ||
, insert | ||
, attachments = [] | ||
, params | ||
; | ||
@@ -43,2 +48,31 @@ | ||
} | ||
if (binaryProperties.length === 0) { | ||
insert = db.insert.bind(db); | ||
} else { | ||
binaryProperties.forEach(function (prop) { | ||
var value, attachment; | ||
attachment = {name: prop}; | ||
value = state[prop]; | ||
if (value instanceof Buffer) { | ||
attachment.data = value; | ||
} else if (value) { | ||
attachment.data = value.content; | ||
attachment.content_type = value.type; | ||
} else { | ||
return; | ||
} | ||
state[prop] = prop; | ||
attachments.push(attachment); | ||
}); | ||
params = { | ||
doc_name: state._id || uuid.v4() | ||
}; | ||
if (state._rev) { | ||
params.rev = state._rev; | ||
} | ||
insert = function(o, callback) { | ||
db.multipart.insert(o, attachments, params, callback); | ||
}; | ||
} | ||
state = JSON.parse(JSON.stringify(state)); | ||
@@ -57,3 +91,3 @@ if (instance._rev) { | ||
} | ||
db.insert(state, function(err, result) { | ||
insert(state, function(err, result) { | ||
var childUpdates | ||
@@ -97,3 +131,3 @@ ; | ||
}); | ||
} else { | ||
} else if(cb !== undefined) { | ||
cb(err, instance); | ||
@@ -100,0 +134,0 @@ } |
@@ -6,4 +6,7 @@ /*jslint node: true, nomen: true, white: true */ | ||
, unseeable = require('./common').unseeable | ||
, makeNano = require('./common').makeNano | ||
, isoStringPropertiesToDates = require('./common').isoStringPropertiesToDates | ||
, toForTypeNameAndSchema = require('./entity_to').toForTypeNameAndSchema | ||
, newForTypeNameAndSchema = require('./entity_new').newForTypeNameAndSchema | ||
, rehydrateForTypeNameAndSchema = require('./entity_new').rehydrateForTypeNameAndSchema | ||
, fromForTypeNameAndSchema = require('./entity_from').fromForTypeNameAndSchema | ||
@@ -19,2 +22,3 @@ ; | ||
, refProperties: [] | ||
, binaryProperties: [] | ||
, views: {} | ||
@@ -51,4 +55,44 @@ } | ||
unseeable(entity, '$kind', typeName); | ||
unseeable(entity, '$schema', schema); | ||
return entity; | ||
}; | ||
module.exports.from = function(db) { | ||
var from; | ||
db = makeNano(db, 'stork#from must be a couchdb url or nano db'); | ||
from = { | ||
get: function (models, id, callback) { | ||
var modelLookup = {}; | ||
models.forEach(function (model) { | ||
modelLookup[model.$kind] = model; | ||
}); | ||
db.get(id, function (e, record) { | ||
var kind, schema, entity; | ||
if (e) { | ||
return callback(e); | ||
} | ||
isoStringPropertiesToDates(record); | ||
try { | ||
kind = record.kind; | ||
schema = modelLookup[kind].$schema; | ||
entity = rehydrateForTypeNameAndSchema(kind, schema)(id, record); | ||
} catch (error) { | ||
return callback(new Error('Could not create an entity for «' + id + '»')); | ||
} | ||
callback(null, entity); | ||
}); | ||
} | ||
}; | ||
unseeable(from, 'db', db); | ||
return from; | ||
}; |
{ | ||
"name": "stork-odm", | ||
"version": "0.1.7", | ||
"version": "0.1.8", | ||
"description": "Stork provides a layer of document management over the CouchDB.", | ||
@@ -26,10 +26,11 @@ "main": "./lib/stork.js", | ||
"devDependencies": { | ||
"nodeunit": "~0.8.3", | ||
"nodeunit": "~0.9.0", | ||
"should": "~3.3.0", | ||
"autotest": "~0.2.6", | ||
"nodemock": "~0.3.3", | ||
"nodemock": "~0.3.4", | ||
"grunt": "~0.4.2", | ||
"grunt-jslint": "~1.1.2", | ||
"grunt-contrib-nodeunit": "~0.3.0", | ||
"grunt-contrib-watch": "^0.6.1" | ||
"grunt-contrib-nodeunit": "~0.4.0", | ||
"grunt-contrib-watch": "^0.6.1", | ||
"memorystream": "^0.2.0" | ||
}, | ||
@@ -44,4 +45,5 @@ "repository": { | ||
"nano": "~5.7.0", | ||
"async": "~0.7.0" | ||
"async": "~0.9.0", | ||
"node-uuid": "^1.4.1" | ||
} | ||
} |
@@ -1,2 +0,3 @@ | ||
stork [![Build Status](https://travis-ci.org/curtissimo/stork.png?branch=master)](https://travis-ci.org/realistschuckle/stork) [![Dependency Status](https://gemnasium.com/curtissimo/stork.svg)](https://gemnasium.com/curtissimo/stork) | ||
stork [![Build Status](https://travis-ci.org/curtissimo/stork.svg?branch=develop)](https://travis-ci.org/curtissimo/stork) [![Dependency Status](https://gemnasium.com/curtissimo/stork.svg)](https://gemnasium.com/curtissimo/stork) | ||
===== | ||
@@ -38,3 +39,3 @@ Stork provides a layer of document management over the CouchDB with multitenant | ||
Comment = odm.devlier('comment', function() { | ||
Comment = odm.deliver('comment', function() { | ||
this.string('title', { required: true }); | ||
@@ -41,0 +42,0 @@ this.string('content'); |
@@ -94,1 +94,61 @@ var should = require('should') | ||
}; | ||
exports['Entity#from has #get that handles binary properties'] = { | ||
setUp: function(cb) { | ||
this.entityName = util.randomString(10).replace('_', ''); | ||
this.Entity = odm.deliver(this.entityName, function() { | ||
this.binary('b'); | ||
}); | ||
this.mockDb = function(id, err, result) { | ||
var db = mock.mock('get') | ||
.takes(id, empty) | ||
.calls(1, [err, result]); | ||
db.config = {url: true, db: true}; | ||
return db; | ||
}; | ||
// Because node mock is stupid loud. | ||
this.errorStream = console.error; | ||
console.error = function() {}; | ||
cb(); | ||
} | ||
, tearDown: function(cb) { | ||
// Because node mock is stupid loud. | ||
console.error = this.errorStream; | ||
cb(); | ||
} | ||
, 'by copying _attachments content_type and length information': function(t) { | ||
var Entity = this.Entity | ||
, value = { | ||
_attachments: { | ||
b: { content_type: 'image/png', length: 12345 } | ||
} | ||
} | ||
, id = util.randomString(10).replace('_', '') | ||
, db = this.mockDb(id, null, value) | ||
; | ||
Entity.from(db).get(id, function(err, result) { | ||
result.b.type.should.be.equal('image/png'); | ||
result.b.length.should.be.equal(12345); | ||
t.done(); | ||
}); | ||
} | ||
, 'by having a pipeFrom method on the binary property': function(t) { | ||
var Entity = this.Entity | ||
, value = { | ||
_attachments: { | ||
b: { content_type: 'image/png', length: 12345 } | ||
} | ||
} | ||
, id = util.randomString(10).replace('_', '') | ||
, db = this.mockDb(id, null, value) | ||
; | ||
Entity.from(db).get(id, function(err, result) { | ||
result.b.pipeFrom.should.be.Function; | ||
t.done(); | ||
}); | ||
} | ||
}; |
@@ -15,2 +15,5 @@ var should = require('should') | ||
setUp: function(cb) { | ||
var docName = '_design/entity' | ||
, rev = 5 | ||
; | ||
this.entity = function() { | ||
@@ -21,4 +24,7 @@ return odm.deliver('entity', this.views); | ||
var db = mock.mock('insert') | ||
.takes(doc, '_design/entity', empty) | ||
.takes(doc, {}, empty) | ||
.calls(2, [err, result]); | ||
db.mock('get') | ||
.takes(docName, empty) | ||
.calls(1, [err, {_id: docName, _rev: rev}]); | ||
db.config = {url: true, db: true}; | ||
@@ -25,0 +31,0 @@ return db; |
@@ -341,1 +341,68 @@ var should = require('should') | ||
}; | ||
exports['instances that use #binary'] = { | ||
setUp: function(cb) { | ||
this.Person = odm.deliver('person', function() { | ||
this.binary('photo'); | ||
}); | ||
this.mockDb = function(doc, att, params, mixin) { | ||
var db = mock.mock('insert') | ||
, callbackIndex = 3 | ||
, callbackArgs = [null] | ||
, state = JSON.parse(JSON.stringify(doc)) | ||
; | ||
db = db.takes(state, att, params, empty); | ||
callbackArgs.push(mixin); | ||
db = db.calls(callbackIndex, callbackArgs); | ||
return { | ||
multipart: db, | ||
config: {url: true, db: true} | ||
}; | ||
}; | ||
// Because node mock is stupid loud. | ||
this.errorStream = console.error; | ||
console.error = function() {}; | ||
cb(); | ||
} | ||
, tearDown: function(cb) { | ||
// Because node mock is stupid loud. | ||
console.error = this.errorStream; | ||
cb(); | ||
} | ||
, 'calls #multipart#insert on save for Buffer binary property': function(t) { | ||
var Person = this.Person | ||
, buffer = new Buffer('123') | ||
, inst = Person.new({photo: buffer, _id: 'moo'}) | ||
, state = {kind: 'person', photo: 'photo', _id: 'moo'} | ||
, attachments = [{name: 'photo', data: buffer}] | ||
, mixin = {_id: 'woot', _rev: 'another'} | ||
, db = this.mockDb(state, attachments, {doc_name: 'moo'}, mixin) | ||
; | ||
inst.to(db).save(function(e, doc) { | ||
should(e).not.be.ok; | ||
t.done(); | ||
}); | ||
} | ||
, 'calls #multipart#insert on save for object/Buffer binary property': function(t) { | ||
var Person = this.Person | ||
, buffer = new Buffer('123') | ||
, binary = {content: new Buffer('123'), type: 'application/octet-stream'} | ||
, inst = Person.new({photo: binary, _id: 'woot', _rev: 'another'}) | ||
, state = {kind: 'person', photo: 'photo'} | ||
, attachments = [{name: 'photo', data: buffer, content_type: 'application/octet-stream'}] | ||
, mixin = {_id: 'woot', _rev: 'another'} | ||
, db = this.mockDb(state, attachments, {doc_name: 'woot', rev: 'another'}, mixin) | ||
; | ||
inst.to(db).save(function(e, doc) { | ||
should(e).not.be.ok; | ||
t.done(); | ||
}); | ||
} | ||
}; |
@@ -5,2 +5,3 @@ var nano = require('nano') | ||
, util = require('utile') | ||
, MemoryStream = require('memorystream') | ||
, odm = require('../lib/stork') | ||
@@ -32,5 +33,14 @@ ; | ||
dbms.db.destroy(db.config.db, function(err) { | ||
dbms.db.create(db.config.db, function(err) { | ||
cb(err); | ||
}); | ||
if (err) { | ||
console.log('could not destroy database', db.config.db); | ||
// return cb(err); | ||
} | ||
setTimeout(function () { | ||
dbms.db.create(db.config.db, function(err) { | ||
if (err) { | ||
console.log('could not create database', db.config.db); | ||
} | ||
cb(err); | ||
}); | ||
}, 0); | ||
}); | ||
@@ -62,2 +72,22 @@ } | ||
, 'update entity design document': function(t) { | ||
var entityName = 'defaulty' | ||
, entity = odm.deliver(entityName) | ||
; | ||
entity.to(dburl).sync(function(e, answer1) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
entity.to(dburl).sync(function(e, answer2) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
answer1.id.should.equal(answer2.id); | ||
answer1.rev.should.not.equal(answer2.rev); | ||
t.done(); | ||
}); | ||
}); | ||
} | ||
, 'create entity design document with sort': function(t) { | ||
@@ -216,2 +246,5 @@ var entityName = 'sorty' | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
var objs = results[results.length - 1] | ||
@@ -255,2 +288,5 @@ ; | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
async.series([ | ||
@@ -260,2 +296,5 @@ from.get.bind(from, results[1]._id) | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
results[0].should.have.properties(nakeds[0]); | ||
@@ -270,2 +309,43 @@ results[0].should.have.property('$schema', instances[0].$schema); | ||
, 'get a previously saved object with stork#form': function(t) { | ||
var entityName = 'sorty' | ||
, Entity = odm.deliver(entityName, function() { | ||
this.string('s', {required: true}); | ||
this.datetime('dt', {required: true}); | ||
}) | ||
, nakeds = [ | ||
{s: 'text', dt: new Date(2012, 6, 14), extra: 1} | ||
, {s: 'txet', dt: new Date(2013, 9, 21), extra: -1} | ||
] | ||
, instances = [ | ||
Entity.new(nakeds[0]) | ||
, Entity.new(nakeds[1]) | ||
] | ||
, from = odm.from(dburl) | ||
; | ||
async.series([ | ||
function(cb) { Entity.to(dburl).sync(cb); } | ||
, function(cb) { instances[0].to(dburl).save(cb); } | ||
, function(cb) { instances[1].to(dburl).save(cb); } | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
async.series([ | ||
from.get.bind(from, [ Entity ], results[1]._id) | ||
, from.get.bind(from, [ Entity ], results[2]._id) | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
results[0].should.have.properties(nakeds[0]); | ||
results[0].should.have.property('$schema', instances[0].$schema); | ||
results[1].should.have.properties(nakeds[1]); | ||
results[1].should.have.property('$schema', instances[1].$schema); | ||
t.done(); | ||
}); | ||
}); | ||
} | ||
, 'create entity design document with complex-key query': function(t) { | ||
@@ -385,2 +465,5 @@ var entityName = util.randomString(10).replace('_', '') | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
Entity.from(db).someView(function(err, results) { | ||
@@ -496,2 +579,5 @@ results[0].should.have.properties(nakeds[1]); | ||
], function(err, results) { | ||
if (err) { | ||
return t.done(err); | ||
} | ||
Entity.from(db).someView('key1', function(err, results) { | ||
@@ -569,2 +655,110 @@ results[0].should.have.properties(nakeds[0]); | ||
} | ||
, 'save a binary property with a full specification': function(t) { | ||
var Person = odm.deliver('person', function() { | ||
this.binary('photo'); | ||
}) | ||
, person = Person.new({photo: {type: 'image/png', content: new Buffer('123')}}) | ||
; | ||
person.to(dburl).save(function (e) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
db.attachment.get(person._id, 'photo', function (e, body) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
// should does not compare Buffer contents correctly | ||
JSON.stringify(body).should.equal(JSON.stringify(new Buffer('123'))); | ||
db.get(person._id, function (e, p) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
p._attachments.photo.content_type.should.equal('image/png'); | ||
t.done(); | ||
}); | ||
}); | ||
}); | ||
} | ||
, 'save a binary property with just the buffer': function(t) { | ||
var Person = odm.deliver('person', function() { | ||
this.binary('photo'); | ||
}) | ||
, person = Person.new({photo: new Buffer('abcdefg')}) | ||
; | ||
person.to(dburl).save(function (e) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
db.attachment.get(person._id, 'photo', function (e, body) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
// should does not compare Buffer contents correctly | ||
JSON.stringify(body).should.equal(JSON.stringify(new Buffer('abcdefg'))); | ||
db.get(person._id, function (e, p) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
p._attachments.photo.content_type.should.equal('application/octet-stream'); | ||
t.done(); | ||
}); | ||
}); | ||
}); | ||
} | ||
, 'save an object with no binary value': function(t) { | ||
var Person = odm.deliver('person', function() { | ||
this.binary('photo'); | ||
}) | ||
, person = Person.new() | ||
; | ||
person.to(dburl).save(function (e) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
db.get(person._id, function (e, p) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
t.ok(p._attachments === undefined); | ||
t.done(); | ||
}); | ||
}); | ||
} | ||
, 'get a binary property with the pipeFrom method': function(t) { | ||
var Person = odm.deliver('person', function() { | ||
this.binary('photo'); | ||
}) | ||
, id = 'iAmLegend' | ||
, person = Person.new({photo: new Buffer('abcdefg'), _id: id}) | ||
; | ||
person.to(dburl).save(function (e) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
Person.from(dburl).get(id, function (e, per) { | ||
if (e) { | ||
return t.done(e); | ||
} | ||
var stream = new MemoryStream(); | ||
per.photo.pipeFrom(dburl)(stream); | ||
stream.on('data', function(d) { | ||
d.toString('utf8').should.be.equal('abcdefg'); | ||
}); | ||
stream.on('end', function () { | ||
t.done(); | ||
}); | ||
stream.on('error', function(e) { | ||
t.done(e); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); |
197194
43
5811
165
5
9
+ Addednode-uuid@^1.4.1
- Removedasync@0.7.0(transitive)
Updatedasync@~0.9.0