dynamic-record
Version:
A bare minimum Javascript implementation of the Active Record pattern
363 lines (362 loc) • 14.5 kB
JavaScript
"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;