"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv").config();
const Promise = require("bluebird");
const _ = require("lodash");
const DynamicCollection = require("./DynamicCollection.js");
const DynamicSchema = require("./DynamicSchema.js");
// Let's get mongodb working first
const connect = require("./mongoConnection.js")(process.env.mongo_server, process.env.mongo_db_name, process.env.mongo_user, process.env.mongo_pass);
const schemaValidator = new (require("./schemaValidation.js"))(connect);
class DynamicRecord {
/**
* Creates a new DynamicRecord instance.
*
* @name DynamicRecord
* @class
* @param {object} options
* @param {string} options.tableSlug - The slug of the table. Must be lowercase only
* and not containing any whitespace
*/
constructor(options) {
this._databaseConnection = connect;
const _schema = this.schema = new (DynamicSchema(this._databaseConnection))();
const tableSlug = options.tableSlug;
let _db;
// Initialize database connection and populate schema instance
const _ready = this._ready = connect.then((db) => {
_db = this._db = db;
// Collection must already exist in database
return this.schema.read(tableSlug).then((schema) => {
if (schema.tableSlug === "")
return Promise.reject(`Table with name ${tableSlug} does not exist`);
const col = db.collection(tableSlug);
if (col) {
return Promise.resolve(col);
}
else {
return Promise.reject(`Table with name ${tableSlug} does not exist`);
}
});
});
/**
* Create a new DynamicRecord.Model instance.
*
* @name DynamicRecord.Model
* @memberOf DynamicRecord
* @instance
* @constructor
* @param {object} data - Object containing data for this instance of
* DynamicRecord.Model
*/
const Model = this.Model = function (data, _preserveOriginal) {
/**
* The data contained in this instance. It is not kept in sync with
* the database automatically.
*
* You should be directly modifying this object. When done and you
* wish to save the data to the database, call `save()` on the
* parent object instance.
*
* @name data
* @type object
* @memberOf DynamicRecord.Model
* @instance
*/
this.data = data;
if (_preserveOriginal) {
this._original = _.cloneDeep(data);
}
else {
this._original = null;
}
};
/**
* Save the data in this instance to the database.
*
* @method save
* @memberOf DynamicRecord.Model
* @instance
* @return {Promise}
*/
Model.prototype.save = function () {
return schemaValidator.compileAsync({ $ref: _schema.tableSlug }).then((validate) => {
if (validate(this.data)) {
return _ready;
}
else {
return Promise.reject(validate.errors);
}
}).then((col) => {
if (this._original) {
return col.updateOne(this._original, this.data, { upsert: true }).then((result) => {
this._original = _.cloneDeep(this.data);
return Promise.resolve(col);
});
}
else {
// Check if collection contains index that needs auto incrementing
return _db.collection("_counters").findOne({ _$id: tableSlug }).then((res) => {
const promises = [];
if (res !== null) {
// Auto incrementing index exist
_.each(res.sequences, (el, columnLabel) => {
promises.push(_schema._incrementCounter(tableSlug, columnLabel).then((newSequence) => {
this.data[columnLabel] = newSequence;
return Promise.resolve(newSequence);
}));
});
return Promise.all(promises);
}
else {
// No auto incrementing index
return Promise.resolve();
}
}).then(() => {
// Save data into the database
return col.insertOne(this.data).then((result) => {
this._original = _.cloneDeep(this.data);
return Promise.resolve(col);
});
});
}
}).catch((err) => {
return Promise.reject(err);
});
};
/**
* Delete the entry this instance links to. Clear the data property
* of this instance as well.
*
* @method destroy
* @memberOf DynamicRecord.Model
* @instance
* @return {Promise}
*/
Model.prototype.destroy = function () {
return _ready.then((col) => {
if (this._original) {
return col.deleteOne(this._original).then((result) => {
this._original = null;
this.data = null;
return Promise.resolve(col);
});
}
else {
throw new Error("Model not saved in database yet.");
}
});
};
/**
* Validate the data in this instance conform to its schema.
*
* **Implementation not settled**
*
* @method validate
* @memberOf DynamicRecord.Model
* @instance
* @return {boolean}
*/
Model.prototype.validate = function (schema) {
let result = false;
_.each(this.data, (el, key) => {
const field = _.find(schema, (column) => {
return column.label == key;
});
if (field.type == "string") {
result = _.isString(el);
}
else if (field.type == "int") {
result = Number.isInteger(el);
}
});
return result;
};
}
/**
* Close the connection to the database server. Only used to terminate
* the running node instance.
*
* @method closeConnection
* @memberOf DynamicRecord
* @instance
*/
closeConnection() {
// Should only ever be called to terminate the node process
this._ready.then((col) => {
this._db.close();
}).catch((err) => {
// BY ANY MEANS NECESSARY
this._db.close();
});
}
/**
* Find the latest entry in the table that match the query.
*
* @method findBy
* @memberOf DynamicRecord
* @instance
* @param {object} query - A key value pair that will be used to match for entry
* in the database
* @return {Promise} Return promise of DynamicRecord.Model
*/
findBy(query) {
return this._ready.then((col) => {
return col.findOne(query).then((model) => {
return Promise.resolve(new this.Model(model, true));
});
});
}
/**
* Find all the entries in the table that match the query.
*
* You can sort the returned data by providing a string key to sort the
* data by or a sorting function to manually sort the data. By default
* they are sorted in the order they are in in the database.
*
* @method where
* @memberOf DynamicRecord
* @instance
* @param {object} query - A key value pair that will be used to match for entries
* @param {string|function} orderBy - The key to sort by or a sorting function
* @return {Promise} Return promise of DynamicCollection
*/
where(query, orderBy) {
return this._ready.then((col) => {
return col.find(query).toArray().then((models) => {
if (orderBy) {
models = _.sortBy(models, orderBy);
}
const results = new DynamicCollection(this.Model, ...models);
_.each(results, (result) => {
result._original = _.cloneDeep(result.data);
});
return Promise.resolve(results);
});
});
}
/**
* Return all entries from the table.
*
* @method all
* @memberOf DynamicRecord
* @instance
* @return {Promise} Return promise of DynamicCollection.
*/
all() {
return this._ready.then((col) => {
return col.find().toArray().then((models) => {
const results = new DynamicCollection(this.Model, ...models);
_.each(results, (result) => {
result._original = _.cloneDeep(result.data);
});
return Promise.resolve(results);
});
});
}
/**
* Return the first entry in the table.
*
* @method first
* @memberOf DynamicRecord
* @instance
* @return {Promise} Return promise of DynamicRecord.Model
*/
first() {
return this._ready.then((col) => {
return col.findOne().then((model) => {
return Promise.resolve(new this.Model(model, true));
});
});
}
}
// Static constructors for their own separate use
DynamicRecord.DynamicSchema = DynamicSchema(connect);
DynamicRecord.DynamicCollection = DynamicCollection;
module.exports = DynamicRecord;