├── .gitignore ├── README.md ├── examples └── basic.js ├── lib ├── helpers.js └── q-orm.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.sublime-project 3 | 4 | *.sublime-workspace 5 | 6 | *.log 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Promise-based wrapper for node-orm2 2 | 3 | This lib supplies promise-returning methods for your habitual node-orm2 objects: 4 | 5 | ```js 6 | var orm = require('orm'); 7 | var qOrm = require('q-orm'); 8 | 9 | return qOrm.qConnect('mysql://username:password@host/database') 10 | .then(function (db) { 11 | 12 | var Person = db.qDefine("person", { 13 | name : String, 14 | surname : String, 15 | age : Number, 16 | male : Boolean, 17 | continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type 18 | photo : Buffer, // BLOB/BINARY 19 | data : Object // JSON encoded 20 | }, { 21 | methods: { 22 | fullName: function () { 23 | return this.name + ' ' + this.surname; 24 | } 25 | }, 26 | validations: { 27 | age: orm.enforce.ranges.number(18, undefined, "under-age") 28 | } 29 | }); 30 | 31 | return Person.qAll({ surname: "Doe" }) 32 | .then(function (people) { 33 | // SQL: "SELECT * FROM person WHERE surname = 'Doe'" 34 | 35 | console.log("People found: %d", people.length); 36 | console.log("First person: %s, age %d", people[0].fullName(), people[0].age); 37 | 38 | people[0].age = 16; 39 | return people[0].qSave() 40 | .fail(function (err) { 41 | console.log(err.stack); 42 | }); 43 | }); 44 | }) 45 | .fail(function (err) { 46 | throw err; 47 | }); 48 | ``` 49 | 50 | ##Supported methods 51 | 52 | - `qOrm.qConnect, qOrm.qExpress` 53 | - `db.qDefine, db.qExecQuery, db.qSync, db.qDrop` 54 | - `Model.qCreate, Model.qGet, Model.qOne, Model.qAll, Model.qCount, Model.qHasOne, Model.qHasMany, Model.qFind` 55 | - `instance.qSave, instance.qRemove, instance.qValidate` 56 | - `instance.qGetAssociatedModel`, etc. 57 | 58 | ##Notes 59 | 60 | - All methods inherit their habitual parameters from their callback-based counterparts. (Behind the scenes, we use `Q.nbind`.) 61 | - This is very beta! Works on my application (it's been tested extensively in there), but does not have its own unit tests yet. 62 | - Features such as `orm.enforce`, `orm.eq`, etc. are not wrapped. If you need them (such as in the example), you have to `require('orm')` as well. 63 | 64 | ##TODO 65 | 66 | - Tests 67 | - More examples 68 | - Better README 69 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var orm = require('orm'); 4 | var qOrm = require('q-orm'); 5 | 6 | return qOrm.qConnect('mysql://username:password@host/database') 7 | .then(function (db) { 8 | 9 | var Person = db.qDefine("person", { 10 | name : String, 11 | surname : String, 12 | age : Number, 13 | male : Boolean, 14 | continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type 15 | photo : Buffer, // BLOB/BINARY 16 | data : Object // JSON encoded 17 | }, { 18 | methods: { 19 | fullName: function () { 20 | return this.name + ' ' + this.surname; 21 | } 22 | }, 23 | validations: { 24 | age: orm.enforce.ranges.number(18, undefined, "under-age") 25 | } 26 | }); 27 | 28 | return Person.qAll({ surname: "Doe" }) 29 | .then(function (people) { 30 | // SQL: "SELECT * FROM person WHERE surname = 'Doe'" 31 | 32 | console.log("People found: %d", people.length); 33 | console.log("First person: %s, age %d", people[0].fullName(), people[0].age); 34 | 35 | people[0].age = 16; 36 | return people[0].qSave() 37 | .fail(function (err) { 38 | console.log(err.stack); 39 | }); 40 | }); 41 | }) 42 | .fail(function (err) { 43 | throw err; 44 | }); -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.ucfirst = function (text) { 4 | return text[0].toUpperCase() + text.substr(1); 5 | }; -------------------------------------------------------------------------------- /lib/q-orm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'), 4 | Qx = require('qx'), 5 | orm = require('orm'), 6 | events = require('events'); 7 | 8 | var ucfirst = require('./helpers.js').ucfirst; 9 | 10 | exports.qConnect = function (configs) { 11 | return Q.ninvoke(orm, 'connect', configs) 12 | .then(setupConnectionMethods); 13 | }; 14 | 15 | (function() { 16 | var _db = null; 17 | var _models = {}; 18 | var _pending = 0; 19 | var _queue = []; 20 | 21 | exports.qExpress = function (uri, opts) { 22 | opts = opts || {}; 23 | 24 | // Pause requests handling. 25 | _pending += 1; 26 | 27 | exports.qConnect(uri) 28 | .then(function (db) { 29 | // Save connection instances for middleware. 30 | if (Array.isArray(_db)) { 31 | _db.push(db); 32 | } else if (_db !== null) { 33 | _db = [_db, db]; 34 | } else { 35 | _db = db; 36 | } 37 | 38 | // Call define function. 39 | if (typeof opts.define === 'function') { 40 | if (opts.define.length > 2) { 41 | return Q.nfcall(opts.define, db, _models); 42 | } 43 | return Q.fcall(opts.define, db, _models); 44 | } 45 | }) 46 | .catch(function(err) { 47 | // Dispatch connection errors. 48 | if (typeof opts.error === 'function') { 49 | opts.error(err); 50 | } else { 51 | throw err; 52 | } 53 | }) 54 | .then(function() { 55 | _pending -= 1; 56 | if (_pending > 0) return; 57 | // Resume requests handling and handle requests from queue. 58 | if (_queue.length === 0) return; 59 | for (var i = 0; i < _queue.length; i++) { 60 | _queue[i](); 61 | } 62 | _queue.length = 0; 63 | }).done(); 64 | 65 | // Middleware function. 66 | return function QORM_ExpressMiddleware(req, res, next) { 67 | if (!req.hasOwnProperty("models")) { 68 | req.models = _models; 69 | } 70 | if (!req.hasOwnProperty("db")) { 71 | req.db = _db; 72 | } 73 | if (next === undefined && typeof res === 'function') { 74 | next = res; 75 | } 76 | if (_pending > 0) { 77 | _queue.push(next); 78 | return; 79 | } 80 | return next(); 81 | } 82 | }; 83 | }).bind(this)() 84 | 85 | function setupConnectionMethods(connection) { 86 | 87 | connection.qDefine = defineModel.bind(connection); 88 | connection.qExecQuery = Q.nbind(connection.driver.execQuery, connection.driver); 89 | connection.qSync = Q.nbind(connection.sync, connection); 90 | connection.qDrop = Q.nbind(connection.drop, connection); 91 | 92 | return connection; 93 | } 94 | 95 | function defineModel(name, properties, opts) { 96 | var connection = this; 97 | 98 | if (!opts) { 99 | opts = {}; 100 | } 101 | var Model = connection.define(name, properties, opts); 102 | 103 | Model.events = new events.EventEmitter(); 104 | 105 | Model.oneAssociations = []; 106 | Model.manyAssociations = []; 107 | 108 | Model.qCreate = function () { 109 | return Q.npost(Model, 'create', Array.prototype.slice.apply(arguments)) 110 | .then(extendInstance); 111 | }; 112 | 113 | Model.qGet = function () { 114 | return Q.npost(Model, 'get', Array.prototype.slice.apply(arguments)) 115 | .then(extendInstance); 116 | }; 117 | 118 | Model.qFind = function() { 119 | return Q.npost(Model, 'find', Array.prototype.slice.apply(arguments)) 120 | .then(extendInstance); 121 | } 122 | 123 | Model.qOne = function () { 124 | return Q.npost(Model, 'one', Array.prototype.slice.apply(arguments)) 125 | .then(extendInstance); 126 | }; 127 | 128 | Model.qAll = function () { 129 | return Q.npost(Model, 'all', Array.prototype.slice.apply(arguments)) 130 | .then(extendInstance); 131 | }; 132 | 133 | Model.qCount = function () { 134 | return Q.npost(Model, 'count', Array.prototype.slice.apply(arguments)); 135 | }; 136 | 137 | Model.qHasOne = hasOne.bind(Model); 138 | Model.qHasMany = hasMany.bind(Model); 139 | 140 | setupOneAssociations(Model, opts.hasOne); 141 | setupManyAssociations(Model, opts.hasMany); 142 | 143 | return Model; 144 | 145 | } 146 | 147 | function hasOne() { 148 | var Model = this; 149 | 150 | var opts = {}; 151 | 152 | var name; 153 | var OtherModel = Model; 154 | 155 | for (var i = 0; i < arguments.length; i++) { 156 | switch (typeof arguments[i]) { 157 | case "string": 158 | name = arguments[i]; 159 | break; 160 | case "function": 161 | if (arguments[i].table) { 162 | OtherModel = arguments[i]; 163 | } 164 | break; 165 | case "object": 166 | opts = arguments[i]; 167 | break; 168 | } 169 | } 170 | 171 | Model.hasOne(name, OtherModel, opts); 172 | 173 | setUpOneAssociation(name, Model, OtherModel, opts); 174 | 175 | if (opts.reverse) { 176 | setUpManyAssociation(opts.reverse, OtherModel, Model, { 177 | accessor: opts.reverseAccessor 178 | }); 179 | } 180 | } 181 | 182 | function hasMany() { 183 | var Model = this; 184 | 185 | var name; 186 | var OtherModel = Model; 187 | var props = null; 188 | var opts = {}; 189 | 190 | for (var i = 0; i < arguments.length; i++) { 191 | switch (typeof arguments[i]) { 192 | case "string": 193 | name = arguments[i]; 194 | break; 195 | case "function": 196 | OtherModel = arguments[i]; 197 | break; 198 | case "object": 199 | if (props === null) { 200 | props = arguments[i]; 201 | } else { 202 | opts = arguments[i]; 203 | } 204 | break; 205 | } 206 | } 207 | 208 | Model.hasMany(name, OtherModel, props, opts); 209 | 210 | setUpManyAssociation(name, Model, OtherModel, opts); 211 | 212 | if (opts.reverse) { 213 | setUpManyAssociation(opts.reverse, OtherModel, Model, { 214 | accessor: opts.reverseAccessor 215 | }); 216 | } 217 | } 218 | 219 | function extendInstanceWithAssociation(Instance, association) { 220 | 221 | function extendInstanceForAssociation(instance) { 222 | return extendInstance(instance, association.model); 223 | } 224 | 225 | Object.defineProperty(Instance, 'q'+ucfirst(association.hasAccessor), { 226 | value: function () { 227 | return Q.npost(Instance, association.hasAccessor, Array.prototype.slice.apply(arguments)) 228 | .then(extendInstanceForAssociation); 229 | }, 230 | enumerable: false 231 | }); 232 | Object.defineProperty(Instance, 'q'+ucfirst(association.getAccessor), { 233 | value: function () { 234 | return Q.npost(Instance, association.getAccessor, Array.prototype.slice.apply(arguments)) 235 | .then(extendInstanceForAssociation); 236 | }, 237 | enumerable: false 238 | }); 239 | Object.defineProperty(Instance, 'q'+ucfirst(association.setAccessor), { 240 | value: function () { 241 | return Q.npost(Instance, association.setAccessor, Array.prototype.slice.apply(arguments)) 242 | .then(extendInstanceForAssociation); 243 | }, 244 | enumerable: false 245 | }); 246 | if (!association.reversed) { 247 | Object.defineProperty(Instance, 'q'+ucfirst(association.delAccessor), { 248 | value: function () { 249 | return Q.npost(Instance, association.delAccessor, Array.prototype.slice.apply(arguments)) 250 | .then(extendInstanceForAssociation); 251 | }, 252 | enumerable: false 253 | }); 254 | } 255 | if (association.addAccessor) { 256 | Object.defineProperty(Instance, 'q'+ucfirst(association.addAccessor), { 257 | value: function () { 258 | return Q.npost(Instance, association.addAccessor, Array.prototype.slice.apply(arguments)) 259 | .then(extendInstanceForAssociation); 260 | }, 261 | enumerable: false 262 | }); 263 | } 264 | } 265 | 266 | function extendInstance(instances, MyModel) { 267 | 268 | if (instances === null || instances === []) { 269 | return null; 270 | } 271 | 272 | if (Array.isArray(instances)) { 273 | return Qx.map(instances, function (instance) { 274 | return extendInstance(instance, MyModel); 275 | }); 276 | } 277 | 278 | var instance = instances; 279 | 280 | if (instance.isExtended) { 281 | return instance; 282 | } 283 | 284 | if (!MyModel) { 285 | MyModel = instance.model(); 286 | } 287 | 288 | Object.defineProperty(instance, 'qSave', { 289 | value: Q.nbind(instance.save, instance), 290 | enumerable: false 291 | }); 292 | 293 | Object.defineProperty(instance, 'qRemove', { 294 | value: Q.nbind(instance.remove, instance), 295 | enumerable: false 296 | }); 297 | 298 | Object.defineProperty(instance, 'qValidate', { 299 | value: Q.nbind(instance.validate, instance), 300 | enumerable: false 301 | }); 302 | 303 | var i; 304 | for (i = 0; MyModel.oneAssociations && (i < MyModel.oneAssociations.length); i++) { 305 | extendInstanceWithAssociation(instance, MyModel.oneAssociations[i]); 306 | } 307 | 308 | for (i = 0; MyModel.manyAssociations && (i < MyModel.manyAssociations.length); i++) { 309 | extendInstanceWithAssociation(instance, MyModel.manyAssociations[i]); 310 | } 311 | 312 | Object.defineProperty(instance, 'isExtended', { 313 | value: true, 314 | enumerable: false 315 | }); 316 | 317 | if (MyModel.qAfterLoad) { 318 | return MyModel.qAfterLoad.apply(instance); 319 | } 320 | 321 | return instance; 322 | } 323 | 324 | function setUpOneAssociation(name, Model, OtherModel, opts) { 325 | var assocName = opts.name || ucfirst(name); 326 | var assocTemplateName = opts.accessor || assocName; 327 | 328 | var association = { 329 | model : OtherModel, 330 | getAccessor : opts.getAccessor || ("get" + assocTemplateName), 331 | setAccessor : opts.setAccessor || ("set" + assocTemplateName), 332 | hasAccessor : opts.hasAccessor || ("has" + assocTemplateName), 333 | delAccessor : opts.delAccessor || ("remove" + assocTemplateName) 334 | }; 335 | Model.oneAssociations.push(association); 336 | Model["qFindBy" + assocTemplateName] = Q.nbind(Model["findBy" + assocTemplateName], Model); 337 | } 338 | 339 | function setUpManyAssociation(name, Model, OtherModel, opts) { 340 | var assocName = opts.name || ucfirst(name); 341 | var assocTemplateName = opts.accessor || assocName; 342 | 343 | var association = { 344 | model : OtherModel, 345 | getAccessor : opts.getAccessor || ("get" + assocTemplateName), 346 | setAccessor : opts.setAccessor || ("set" + assocTemplateName), 347 | hasAccessor : opts.hasAccessor || ("has" + assocTemplateName), 348 | delAccessor : opts.delAccessor || ("remove" + assocTemplateName), 349 | addAccessor : opts.addAccessor || ("add" + assocTemplateName) 350 | }; 351 | Model.manyAssociations.push(association); 352 | } 353 | 354 | function setupOneAssociations(Model, hasOne) { 355 | if (!hasOne) { 356 | return; 357 | } 358 | 359 | var assoc; 360 | for (var name in hasOne) { 361 | assoc = hasOne[name]; 362 | Model.qHasOne(name, assoc.model, assoc.opts); 363 | } 364 | } 365 | 366 | function setupManyAssociations(Model, hasMany) { 367 | if (!hasMany) { 368 | return; 369 | } 370 | 371 | var assoc; 372 | for (var name in hasMany) { 373 | assoc = hasMany[name]; 374 | Model.qHasMany(name, assoc.model, assoc.extra, assoc.opts); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Rafael Kaufmann", 4 | "email": "rafael.kaufmann@resolveai.com.br" 5 | }, 6 | "contributors": [ 7 | { 8 | "name": "Aleksandr Derbenev", 9 | "email": "alex-ac@yandex-team.ru" 10 | } 11 | ], 12 | "name": "q-orm", 13 | "description": "Promise-based wrapper for node-orm2", 14 | "keywords": [ 15 | "orm", 16 | "q", 17 | "promise" 18 | ], 19 | "version": "0.0.3", 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/rafaelkaufmann/q-orm.git" 24 | }, 25 | "main": "./lib/q-orm", 26 | "engines": { 27 | "node": "*" 28 | }, 29 | "analyse": false, 30 | "dependencies": { 31 | "q": "", 32 | "qx": "", 33 | "orm": "" 34 | }, 35 | "readmeFilename": "README.md" 36 | } 37 | --------------------------------------------------------------------------------