UNPKG

dynamic-record

Version:

A bare minimum Javascript implementation of the Active Record pattern

363 lines (362 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.DynamicSchema = void 0; const _ = require("lodash"); const DynamicSchema_1 = require("../DynamicSchema"); let connect; class DynamicSchema extends DynamicSchema_1.DynamicSchema { constructor({ connection }) { super({ connection }); connect = connection.interface; } createTable(schemaInput) { return __awaiter(this, void 0, void 0, function* () { const { db } = yield connect; const schema = _.cloneDeep(schemaInput); const tableSlug = schema.$id; const tableName = schema.title || schema.$id; const columns = schema.properties; const required = _.cloneDeep(schema.required) || []; const description = schema.description || ""; try { // Create the collection, ensuring that is doesn't already exist // in the database yield db.createCollection(tableSlug); const col = yield db.collection("_counters"); yield col.insertOne({ _$id: tableSlug, sequences: {} }); const databaseInsert = schema; schema._$schema = schema.$schema; schema._$id = schema.$id; delete schema.$schema; delete schema.$id; yield db.collection("_schema").insertOne(databaseInsert); this.definition = columns; this.tableName = tableName; this.tableSlug = tableSlug; this.required = required; this.description = description; this.jsonSchema = schema; yield this._writeSchema(); // Handle index columns let promises = []; _.each(columns, (column, key) => { if (column.isAutoIncrement) { column.isIndex = true; column.isUnique = true; } if (column.isIndex) { promises.push(this.addIndex({ name: key, unique: column.isUnique, autoIncrement: column.isAutoIncrement })); } }); yield Promise.all(promises); return this; } catch (err) { this.tableName = null; this.tableSlug = null; this.required = []; this.description = ""; this.jsonSchema = {}; // Reverse database actions return Promise.all([ // 1. Remove collection from database db.collection(tableSlug).drop(), // 2. Remove entry from _schema collection db.collection("_schema").deleteOne({ "_$id": tableSlug }), // 3. Remove entry from _counters collection db.collection("_counters").deleteOne({ "_$id": tableSlug }) ]).then(() => { return Promise.reject(err); }).catch((e) => { return Promise.reject(e); }); } }); } dropTable() { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; yield db.collection("_schema").deleteOne({ "_$id": this.tableSlug }); yield db.collection(this.tableSlug).drop(); yield db.collection("_counters").deleteOne({ "_$id": this.tableSlug }); this.tableName = null; this.tableSlug = null; this.definition = {}; return this; } catch (err) { return Promise.reject(err); } }); } renameTable(newSlug, newName) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; const promises = []; promises.push(db.collection("_schema").findOneAndUpdate({ "_$id": this.tableSlug }, { $set: { "_$id": newSlug, "title": newName || newSlug } })); promises.push(db.collection("_counters").findOneAndUpdate({ "_$id": this.tableSlug }, { $set: { "_$id": newSlug } })); promises.push(db.renameCollection(this.tableSlug, newSlug)); yield Promise.all(promises); this.tableSlug = newSlug; this.tableName = newName || newSlug; return this; } catch (err) { return Promise.reject(err); } }); } addIndex(options) { return __awaiter(this, void 0, void 0, function* () { const { db } = yield connect; const columnName = options.name; const isAutoIncrement = options.autoIncrement; let unique = options.unique; if (isAutoIncrement && unique === false) { console.warn("Auto increment index must be unique, setting to unique."); unique = true; } if (typeof unique === "undefined") { unique = true; } try { yield db.collection(this.tableSlug).createIndex(columnName, { unique: unique, name: columnName }); if (isAutoIncrement) { yield this._setCounter(this.tableSlug, columnName); } // Update schema entry in database const indexKey = `properties.${columnName}.isIndex`; const uniqueKey = `properties.${columnName}.isUnique`; const autoIncrementKey = `properties.${columnName}.isAutoIncrement`; yield db.collection("_schema").updateOne({ _$id: this.tableSlug }, { $set: { [indexKey]: true, [uniqueKey]: !!unique, [autoIncrementKey]: !!isAutoIncrement } }); return this; } catch (err) { return Promise.reject(err); } }); } removeIndex(columnName) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; yield db.collection(this.tableSlug).dropIndex(columnName); const counter = yield db.collection("_counters").findOne({ _$id: this.tableSlug }); delete counter.sequences[columnName]; yield db.collection("_counters").findOneAndUpdate({ _$id: this.tableSlug }, { $set: { sequences: counter.sequences } }); // Update schema entry in database const indexKey = `properties.${columnName}.isIndex`; const uniqueKey = `properties.${columnName}.isUnique`; const autoIncrementKey = `properties.${columnName}.isAutoIncrement`; yield db.collection("_schema").updateOne({ _$id: this.tableSlug }, { $set: { [indexKey]: false, [uniqueKey]: false, [autoIncrementKey]: false } }); return this; } catch (err) { return Promise.reject(err); } }); } read(tableSlug) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; const data = yield db.collection("_schema").findOne({ _$id: tableSlug }); if (data) { this.tableName = data.title; this.tableSlug = data._$id; this.definition = data.properties; this.required = data.required; const jsonSchema = _.cloneDeep(data); jsonSchema.$schema = data._$schema; jsonSchema.$id = data._$id; delete jsonSchema._$schema; delete jsonSchema._$id; delete jsonSchema._id; this.jsonSchema = jsonSchema; } return this; } catch (err) { return Promise.reject(err); } }); } define(def, required = []) { return __awaiter(this, void 0, void 0, function* () { const { db } = yield connect; const oldDef = _.cloneDeep(this.definition); this.definition = def; // Create schema in RMDB, do nothing in NoSQL try { yield db.collection("_schema").findOneAndUpdate({ _$id: this.tableSlug, }, { $set: { properties: def, required: required } }, { upsert: true }); return this; } catch (err) { this.definition = oldDef; return Promise.reject(err); } }); } renameColumn(name, newName) { return __awaiter(this, void 0, void 0, function* () { const { db } = yield connect; this.definition[newName] = _.cloneDeep(this.definition[name]); delete this.definition[name]; try { yield this._writeSchema(); const entry = yield db.collection("_counters").findOne({ "_$id": this.tableSlug }); if (entry) { const sequences = _.cloneDeep(entry.sequences); sequences[newName] = sequences[name]; delete sequences[name]; yield db.collection("_counters").findOneAndUpdate({ "_$id": this.tableSlug }, { $set: { sequences: sequences } }); } return this; } catch (err) { this.definition[name] = _.cloneDeep(this.definition[newName]); delete this.definition[newName]; return Promise.reject(err); } }); } // Utils -------------------------------------------------------- _writeSchema() { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; yield db.collection("_schema").findOneAndUpdate({ _$id: this.tableSlug }, { $set: { properties: this.definition } }); return this; } catch (err) { return Promise.reject(err); } }); } _setCounter(collection, columnLabel) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; const sequenceKey = `sequences.${columnLabel}`; yield db.collection("_counters").findOneAndUpdate({ _$id: collection }, { $set: { [sequenceKey]: 0 } }); return this; } catch (err) { return Promise.reject(err); } }); } _incrementCounter(collection, columnLabel) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; const result = yield db.collection("_counters").findOne({ _$id: collection }); const newSequence = result.sequences[columnLabel] + 1; const sequenceKey = `sequences.${columnLabel}`; yield db.collection("_counters").findOneAndUpdate({ _$id: collection }, { $set: { [sequenceKey]: newSequence } }); return newSequence; } catch (err) { return Promise.reject(err); } }); } _decrementCounter(collection, columnLabel) { return __awaiter(this, void 0, void 0, function* () { try { const { db } = yield connect; const result = yield db.collection("_counters").findOne({ _$id: collection }); const newSequence = result.sequences[columnLabel] - 1; const sequenceKey = `sequences.${columnLabel}`; yield db.collection("_counters").findOneAndUpdate({ _$id: collection }, { $set: { [sequenceKey]: newSequence } }); return newSequence; } catch (err) { return Promise.reject(err); } }); } } exports.DynamicSchema = DynamicSchema;