UNPKG

dynamic-record

Version:

A bare minimum Javascript implementation of the Active Record pattern

314 lines (313 loc) 14.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamicRecord = void 0; const _ = require("lodash"); const DynamicRecord_1 = require("../DynamicRecord"); const DynamicCollection_1 = require("./DynamicCollection"); const DynamicSchema_1 = require("./DynamicSchema"); const schemaValidation_1 = require("./schemaValidation"); class DynamicRecord extends DynamicRecord_1.DynamicRecord { constructor(options) { super(options); let _db, _client; const connect = this._databaseConnection = options.connection; const _schemaValidator = this._schemaValidator = (0, schemaValidation_1.default)(this._databaseConnection); const tableSlug = options.tableSlug; const _schema = this.schema = connect.interface.then(({ db }) => { return new DynamicSchema_1.DynamicSchema({ connection: this._databaseConnection }); }).then((schema) => { return schema.read(tableSlug); }).then((schema) => { if (schema.tableSlug === null) return Promise.reject(`Table with name ${tableSlug} does not exist`); return schema; }); // Initialize database connection and populate schema instance // Collection must already exist in database const _ready = this._ready = connect.interface.then(({ db, client }) => { _db = this._db = db; _client = this._client = client; return this.schema; }).then((schema) => { const col = _db.collection(tableSlug); if (col) { return Promise.resolve(col); } else { return Promise.reject(`Table with name ${tableSlug} does not exist`); } }); const Model = this.Model = class Model extends DynamicRecord_1.Model { constructor(data, _preserveOriginal) { let id = null; // Preserve mongodb _id if exist for faster saving if (_.has(data, "_id")) { id = data._id; delete data._id; } super(data, _preserveOriginal); this._savePromise = null; this._id = id; } save() { return __awaiter(this, void 0, void 0, function* () { yield _ready; const saveData = () => __awaiter(this, void 0, void 0, function* () { const col = yield _ready; if (this._id) { // Update existing entry yield validateData(this.data); yield col.updateOne({ _id: this._id }, { $set: this.data }); this._original = _.cloneDeep(this.data); return this; } else { // Create new entry // Check if collection contains index that needs auto incrementing return _db.collection("_counters").findOne({ _$id: tableSlug }).then((res) => __awaiter(this, void 0, void 0, function* () { const promises = []; if (res !== null) { // Auto incrementing index exist _.each(res.sequences, (el, columnLabel) => { promises.push(_schema.then((schema) => { return schema._incrementCounter(tableSlug, columnLabel).then((newSequence) => { this.data[columnLabel] = newSequence; return Promise.resolve(newSequence); }); })); }); yield Promise.all(promises); } yield validateData(this.data); // Save data into the database yield col.insertOne(this.data); if (_.has(this.data, "_id")) { this._id = this.data._id; delete this.data._id; } this._original = _.cloneDeep(this.data); return this; })).catch((err) => __awaiter(this, void 0, void 0, function* () { // Reverse database actions try { yield Promise.all([ // 1. Decrement autoincrement counter _db.collection("_counters").findOne({ _$id: tableSlug }).then((res) => { const promises = []; if (res) { _.each(res.sequences, (el, columnLabel) => { promises.push(_schema.then((schema) => { return schema._decrementCounter(tableSlug, columnLabel); })); }); } return Promise.all(promises); }) ]); return Promise.reject(err); } catch (e) { return Promise.reject(e); } })); } }); // Wait for current queue of saves before continuing if (this._savePromise) { yield this._savePromise; this._savePromise = saveData(); } else { this._savePromise = saveData(); } return this._savePromise; function validateData(data) { return __awaiter(this, void 0, void 0, function* () { const schema = yield _schema; const validate = yield _schemaValidator.compileAsync({ $ref: schema.tableSlug }); if (validate(data)) { return Promise.resolve(); } else { return Promise.reject(new Error(JSON.stringify(validate.errors, null, 2))); } }); } }); } destroy() { return __awaiter(this, void 0, void 0, function* () { const col = yield _ready; const destroyData = () => __awaiter(this, void 0, void 0, function* () { if (this._original) { yield col.deleteOne(this._original); this._original = null; this.data = null; return this; } else { throw new Error("Model not saved in database yet."); } }); // Wait for current queue of saves before continuing if (this._savePromise) { yield this._savePromise; this._savePromise = destroyData(); } else { this._savePromise = destroyData(); } return this._savePromise; }); } validate(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; } }; } closeConnection() { return __awaiter(this, void 0, void 0, function* () { // Should only ever be called to terminate the node process try { yield this._ready; this._client.close(); } catch (e) { // BY ANY MEANS NECESSARY this._client.close(); } }); } findBy(query) { return __awaiter(this, void 0, void 0, function* () { const col = yield this._ready; const model = yield col.findOne(query); if (model !== null) { return new this.Model(model, true); } else { return null; } }); } where(query, options) { return __awaiter(this, void 0, void 0, function* () { const col = yield this._ready; let models = yield col.find(query) .limit((options === null || options === void 0 ? void 0 : options.limit) || 0) .skip((options === null || options === void 0 ? void 0 : options.offset) || 0) .sort(_.mapValues(options === null || options === void 0 ? void 0 : options.sort, (val) => { if (val === "ASC") { return 1; } else if (val === "DESC") { return -1; } else { return 0; } })) .toArray(); const results = new DynamicCollection_1.DynamicCollection(this.Model, ...models); results.forEach((result) => { result._original = _.cloneDeep(result.data); }); return results; }); } all() { return __awaiter(this, void 0, void 0, function* () { const col = yield this._ready; let models = yield col.find().toArray(); const results = new DynamicCollection_1.DynamicCollection(this.Model, ...models); results.forEach((result) => { result._original = _.cloneDeep(result.data); }); return results; }); } first(options) { return __awaiter(this, void 0, void 0, function* () { const col = yield this._ready; const models = yield col.find({}) .limit((options === null || options === void 0 ? void 0 : options.limit) || 1) .skip((options === null || options === void 0 ? void 0 : options.offset) || 0) .sort(_.assign(_.mapValues(options === null || options === void 0 ? void 0 : options.sort, (val) => { if (val === "ASC") { return 1; } else if (val === "DESC") { return -1; } else { return 0; } }), { _id: 1 })) .toArray(); if (models.length === 0 && _.isUndefined(options === null || options === void 0 ? void 0 : options.limit)) { return null; } else if (models.length === 1 && _.isUndefined(options === null || options === void 0 ? void 0 : options.limit)) { return new this.Model(models[0], true); } else { return new DynamicCollection_1.DynamicCollection(this.Model, ...models); } }); } last(options) { return __awaiter(this, void 0, void 0, function* () { const col = yield this._ready; const models = yield col.find({}) .limit((options === null || options === void 0 ? void 0 : options.limit) || 1) .skip((options === null || options === void 0 ? void 0 : options.offset) || 0) .sort(_.assign(_.mapValues(options === null || options === void 0 ? void 0 : options.sort, (val) => { if (val === "ASC") { return 1; } else if (val === "DESC") { return -1; } else { return 0; } }), { _id: -1 })) .toArray(); if (models.length === 0 && _.isUndefined(options === null || options === void 0 ? void 0 : options.limit)) { return null; } else if (models.length === 1 && _.isUndefined(options === null || options === void 0 ? void 0 : options.limit)) { return new this.Model(models[0], true); } else { return new DynamicCollection_1.DynamicCollection(this.Model, ...models); } }); } } exports.DynamicRecord = DynamicRecord;