tabel
Version:
A simple orm for PostgreSQL which works with simple javascript objects and arrays
445 lines (366 loc) • 14.8 kB
JavaScript
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var knex = require('knex');
var _require = require('lodash'),
merge = _require.merge,
isString = _require.isString;
var KRedis = require('./kredis');
var Table = require('./Table');
var Scoper = require('./Scoper');
var Shape = require('./Shape');
var migrator = require('./migrator');
var Orm = function () {
function Orm() {
var _this = this;
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Orm);
if ('db' in config) {
this.knex = knex(config.db);
} else {
throw new Error('no \'db\' config found');
}
if ('redis' in config) {
this.cache = new KRedis(config.redis);
}
// tables definitions
this.tableClasses = new Map();
// tables instances
this.tables = new Map();
// tableColumns cache
this.tableColumns = new Map();
// migrator
this.migrator = migrator(this);
// exports which can be exported in place of orm instance
this.exports = {
orm: this,
table: function table() {
return _this.table.apply(_this, arguments);
},
trx: function trx() {
return _this.trx.apply(_this, arguments);
},
raw: function raw() {
return _this.raw.apply(_this, arguments);
},
migrator: this.migrator,
cache: this.cache,
knex: this.knex,
scoper: function scoper() {
return _this.scoper.apply(_this, arguments);
},
shape: function shape() {
return _this.shape.apply(_this, arguments);
}
};
}
// raw expr helper
_createClass(Orm, [{
key: 'raw',
value: function raw(expr) {
return this.knex.raw(expr);
}
// transaction helper
}, {
key: 'transaction',
value: function transaction(promiseFn) {
return this.knex.transaction(promiseFn);
}
// transaction shorthand
// usage:
// return orm.trx((t) => {
// return orm('users', t).save([{}, {}, {}]);
// }).then((users) => {
// ...
// });
}, {
key: 'trx',
value: function trx(promiseFn) {
var outerResult = void 0;
return this.transaction(function (t) {
return promiseFn(t).then(function (result) {
return t.commit().then(function () {
outerResult = result;
return result;
});
}).catch(function (e) {
t.rollback().then(function () {
throw e;
});
});
}).then(function () {
return outerResult;
});
}
// method to close the database
}, {
key: 'close',
value: function close() {
var promises = [this.knex.destroy()];
if (this.cache) {
promises.push(this.cache.disconnect());
}
return Promise.all(promises);
}
// here, we load the columns of all the tables that have been
// defined via the orm, and return a promise on completion
// cos, if people wanna do that before starting the server
// let em do that. we also call ioredis.connect if its available
}, {
key: 'load',
value: function load() {
var _this2 = this;
var promises = Array.from(this.tables.keys).map(function (name) {
return _this2.table(name).load();
});
// if (this.cache) {
// promises.push(this.cache.connect());
// }
return Promise.all(promises);
}
// get a tableClass
}, {
key: 'tableClass',
value: function tableClass(tableName) {
return this.tableClasses.get(tableName);
}
// get a table object
}, {
key: 'table',
value: function table(tableName) {
var trx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!this.tables.has(tableName)) {
throw new Error('trying to access invalid table ' + tableName);
}
var tbl = this.tables.get(tableName).fork();
if (trx !== null) {
tbl.transacting(trx);
}
return tbl;
}
}, {
key: 'scoper',
value: function scoper(scopes) {
return new Scoper(scopes);
}
}, {
key: 'shape',
value: function shape(checks) {
return new Shape(checks);
}
// shorthand for table
}, {
key: 'tbl',
value: function tbl(tableName) {
var trx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return this.table(tableName, trx);
}
}, {
key: 'defineTable',
value: function defineTable() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var tableName = params.name;
if (!isString(tableName)) {
throw new Error('Invalid table-name: ' + tableName + ' supplied via key \'name\'');
}
if (this.tableClasses.has(tableName)) {
throw new Error('Table \'' + tableName + '\' already defined');
}
this.tableClasses.set(tableName, this.newTableClass(params));
this.instantitateTable(tableName, params);
return this;
}
}, {
key: 'extendTable',
value: function extendTable(tableName, _ref) {
var _ref$scopes = _ref.scopes,
scopes = _ref$scopes === undefined ? {} : _ref$scopes,
_ref$joints = _ref.joints,
joints = _ref$joints === undefined ? {} : _ref$joints,
_ref$relations = _ref.relations,
relations = _ref$relations === undefined ? {} : _ref$relations,
_ref$methods = _ref.methods,
methods = _ref$methods === undefined ? {} : _ref$methods;
if (!this.tableClasses.has(tableName)) {
throw new Error('Table \'' + tableName + '\' not defined yet');
}
var TableClass = this.tableClass(tableName);
var ExtendedTableClass = function (_TableClass) {
_inherits(ExtendedTableClass, _TableClass);
function ExtendedTableClass() {
_classCallCheck(this, ExtendedTableClass);
return _possibleConstructorReturn(this, (ExtendedTableClass.__proto__ || Object.getPrototypeOf(ExtendedTableClass)).apply(this, arguments));
}
return ExtendedTableClass;
}(TableClass);
this.attachScopesToTableClass(ExtendedTableClass, scopes);
this.attachJointsToTableClass(ExtendedTableClass, joints);
this.attachRelationsToTableClass(ExtendedTableClass, relations);
this.attachMethodsToTableClass(ExtendedTableClass, methods);
this.tableClasses.set(tableName, ExtendedTableClass);
this.instantitateTable(tableName);
return this;
}
}, {
key: 'instantitateTable',
value: function instantitateTable(tableName) {
var TableClass = this.tableClasses.get(tableName);
return this.tables.set(tableName, new TableClass(this));
}
}, {
key: 'newTableClass',
value: function newTableClass(params) {
return this.extendTableClass(Table, params);
}
}, {
key: 'extendTableClass',
value: function extendTableClass(TableClass, params) {
var _merge = merge(
// the defaults
{
// the table's name, is required
name: null,
// table properties
props: {
key: 'id',
// default key column, can be ['user_id', 'post_id'] for composite keys
uuid: false,
// by default we don't assume that you use an auto generated uuid as db id
perPage: 25,
// standard batch size per page used by `forPage` method
// forPage method uses offset
// avoid that and use a keyset in prod (http://use-the-index-luke.com/no-offset)
timestamps: false
// set to `true` if you want auto timestamps or
// timestamps: ['created_at', 'updated_at'] (these are defaults when `true`)
// will be assigned in this order only
},
// predefined scopes on the table
scopes: {},
// predefined joints on the table
joints: {},
// relations definitions for the table
relations: {},
// table methods defintions
methods: {}
},
// supplied params which will override the defaults
params),
name = _merge.name,
props = _merge.props,
processors = _merge.processors,
scopes = _merge.scopes,
joints = _merge.joints,
relations = _merge.relations,
methods = _merge.methods;
// the extended table class whose objects will behave as needed
var ExtendedTableClass = function (_TableClass2) {
_inherits(ExtendedTableClass, _TableClass2);
function ExtendedTableClass() {
_classCallCheck(this, ExtendedTableClass);
return _possibleConstructorReturn(this, (ExtendedTableClass.__proto__ || Object.getPrototypeOf(ExtendedTableClass)).apply(this, arguments));
}
return ExtendedTableClass;
}(TableClass);
// assign name to the table class
ExtendedTableClass.prototype.name = name;
// assign props to the table class
ExtendedTableClass.prototype.props = props;
// assign processors to the table class
ExtendedTableClass.prototype.processors = processors;
// store names of defined scopes, joints, relations, and methods
ExtendedTableClass.prototype.definedScopes = new Set();
ExtendedTableClass.prototype.definedJoints = new Set();
ExtendedTableClass.prototype.definedRelations = new Set();
ExtendedTableClass.prototype.definedMethods = new Set();
// attach scopes, joints, relations and methods to tables
// these are the only ones extendable after creation
this.attachScopesToTableClass(ExtendedTableClass, scopes);
this.attachJointsToTableClass(ExtendedTableClass, joints);
this.attachRelationsToTableClass(ExtendedTableClass, relations);
this.attachMethodsToTableClass(ExtendedTableClass, methods);
// return the extended table class
return ExtendedTableClass;
}
}, {
key: 'attachScopesToTableClass',
value: function attachScopesToTableClass(TableClass, scopes) {
// keep a record of defined scopes
Object.keys(scopes).forEach(function (name) {
TableClass.prototype.definedScopes.add(name);
});
// process and merge scopes with table class
merge(TableClass.prototype, Object.keys(scopes).reduce(function (processed, name) {
return merge(processed, _defineProperty({}, name, function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
scopes[name].apply(this, args);
// set the label of the last pushed scope
this.scopeTrack.relabelLastScope(name);
return this;
}));
}, {}));
}
}, {
key: 'attachJointsToTableClass',
value: function attachJointsToTableClass(TableClass, joints) {
// keep a record of defined joints
Object.keys(joints).forEach(function (name) {
TableClass.prototype.definedJoints.add(name);
});
// process and merge joints with table class
merge(TableClass.prototype, Object.keys(joints).reduce(function (processed, name) {
// predefined joints never take arguments
return merge(processed, _defineProperty({}, name, function () {
if (this.scopeTrack.hasJoint(name)) {
return this;
} else {
joints[name].call(this);
// set the label of the last pushed scope
this.scopeTrack.relabelLastScope(name);
// ensure that the last scope is a joint
this.scopeTrack.convertLastScopeToJoint();
return this;
}
}));
}, {}));
}
}, {
key: 'attachRelationsToTableClass',
value: function attachRelationsToTableClass(TableClass, relations) {
// keep a record of defined relations
Object.keys(relations).forEach(function (name) {
TableClass.prototype.definedRelations.add(name);
});
// process and merge relations with table class
merge(TableClass.prototype, Object.keys(relations).reduce(function (processed, name) {
// const relation = relations[name];
return merge(processed, _defineProperty({}, name, function (model) {
if (model) {
return relations[name].bind(this)().setName(name).forModel(model);
} else {
return relations[name].bind(this)().setName(name);
}
}));
}, {}));
}
}, {
key: 'attachMethodsToTableClass',
value: function attachMethodsToTableClass(TableClass, methods) {
// keep a record of defined methods
Object.keys(methods).forEach(function (name) {
TableClass.prototype.definedMethods.add(name);
});
// process and merge relations with table class
merge(TableClass.prototype, Object.keys(methods).reduce(function (processed, name) {
return merge(processed, _defineProperty({}, name, methods[name]));
}, {}));
}
}]);
return Orm;
}();
module.exports = Orm;