@bucky24/database
Version:
A simple model system for node that allows connecting to multiple types of data source
409 lines (408 loc) • 16.1 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.Model = void 0;
const yup_1 = require("yup");
const connections_1 = require("./connections");
const whereBuilder_1 = require("./whereBuilder");
const types_1 = require("./types");
const modelSchema = (0, yup_1.object)({
table: (0, yup_1.string)().required(),
fields: (0, yup_1.lazy)((obj) => {
return (0, yup_1.object)(Object.assign({}, Object.keys(obj).reduce((obj, key) => {
return Object.assign(Object.assign({}, obj), { [key]: (0, yup_1.object)({
type: (0, yup_1.mixed)().oneOf(Object.values(types_1.FIELD_TYPE)).required(),
meta: (0, yup_1.array)().of((0, yup_1.mixed)().oneOf(Object.values(types_1.FIELD_META)).required()),
size: (0, yup_1.number)().positive().optional(),
foreign: (0, yup_1.object)({
table: (0, yup_1.object)().required(),
field: (0, yup_1.string)().required(),
}).default(undefined),
}) });
}, {})));
}),
version: (0, yup_1.number)().required().integer(),
});
;
class Model {
constructor(settings) {
this.table = settings.table;
this.fields = settings.fields;
this.fieldList = [];
Object.keys(settings.fields).forEach((key) => {
const field = settings.fields[key];
this.fieldList.push(Object.assign(Object.assign({}, field), { id: key }));
});
this.version = settings.version;
}
static create(settings) {
try {
const { table, fields, version } = modelSchema.validateSync(settings);
const model = new Model({
table,
fields: Object.assign({ id: {
type: types_1.FIELD_TYPE.INT,
meta: [types_1.FIELD_META.AUTO],
} }, fields),
version,
});
return model;
}
catch (error) {
throw new Error(`${error.message} with data ${JSON.stringify(settings)}`);
}
}
createCrudApis(app, options = {}) {
let middleware = options.middleware || [];
if (!Array.isArray(middleware)) {
middleware = [middleware];
}
const processBody = (req, res) => {
if (!req.body) {
res.status(400).json({
error: 'No json body detected',
});
return false;
}
const missingFields = [];
for (const field of this.fieldList) {
const fieldData = this.getFieldData(field.id);
if (req.body[field.id] === undefined && (fieldData === null || fieldData === void 0 ? void 0 : fieldData.meta.includes(types_1.FIELD_META.REQUIRED))) {
missingFields.push(field);
}
}
const extraFields = [];
for (const field in req.body) {
if (!this.getFieldData(field)) {
extraFields.push(field);
}
}
if (missingFields.length > 0 || extraFields.length > 0) {
res.status(400).json({
missingFields,
extraFields,
});
return false;
}
return true;
};
app.get('/' + this.table, ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const objects = yield this.search({});
const filteredObjects = this.filterForExport(objects);
res.status(200).json(filteredObjects);
}
catch (e) {
console.error(e);
res.status(500).end();
}
}));
app.get('/' + this.table + "/:id", ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const object = yield this.get(req.params.id);
if (!object) {
res.status(404).end();
return;
}
const filteredObject = this.filterForExport(object);
res.status(200).json(filteredObject);
}
catch (e) {
console.error(e);
res.status(500).end();
}
}));
app.delete('/' + this.table + "/:id", ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const object = yield this.get(req.params.id);
if (!object) {
res.status(404).end();
return;
}
yield this.delete(req.params.id);
res.status(200).json({
id: req.params.id,
});
}
catch (e) {
console.error(e);
res.status(500).end();
}
}));
app.post('/' + this.table, ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () {
if (!processBody(req, res)) {
return;
}
try {
const id = yield this.insert(req.body);
const newData = yield this.get(id);
res.status(200).json(newData);
}
catch (e) {
console.error(e);
res.status(500).end();
}
}));
app.put('/' + this.table + '/:id', ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () {
const { id } = req.params;
if (!processBody(req, res)) {
return;
}
try {
yield this.update(id, req.body);
const newData = yield this.get(id);
res.status(200).json(newData);
}
catch (e) {
console.error(e);
res.status(500).end();
}
}));
}
static _getColumnFromType(type) {
if (type === types_1.FIELD_TYPE.INT) {
return 'INT';
}
else if (type === types_1.FIELD_TYPE.STRING) {
return 'TEXT';
}
else if (type === types_1.FIELD_TYPE.JSON) {
return 'TEXT';
}
else if (type === types_1.FIELD_TYPE.BIGINT) {
return 'BIGINT';
}
else if (type === types_1.FIELD_TYPE.BOOLEAN) {
return 'BOOLEAN';
}
}
static _getValueForType(type, value) {
if (type === types_1.FIELD_TYPE.STRING) {
return `"${value}"`;
}
return value;
}
_getFieldsWithMeta(meta) {
return this.fieldList.filter((field) => {
if (!field.meta) {
// if it has no meta, no way it can match any meta
return false;
}
return field.meta.includes(meta);
});
}
static _getFieldCreationString(fieldName, data, ticks) {
let fieldRow = ticks + fieldName + ticks + " " + Model._getColumnFromType(data.type);
if (data.meta) {
if (data.meta.includes(types_1.FIELD_META.REQUIRED)) {
fieldRow += ' NOT NULL';
}
if (data.meta.includes(types_1.FIELD_META.AUTO)) {
fieldRow += ' AUTO_INCREMENT';
}
}
return fieldRow;
}
init() {
return __awaiter(this, void 0, void 0, function* () {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
return connection.initializeTable(this.table, this.fields, this.version);
});
}
getFieldData(field) {
if (!this.fields[field]) {
return null;
}
return Object.assign(Object.assign({}, this.fields[field]), { meta: this.fields[field].meta || [] });
}
getTable() {
return this.table;
}
processResult(result) {
Object.keys(result).forEach((key) => {
const value = result[key];
const data = this.getFieldData(key);
if (!data) {
// remove the field-it's not part of our fieldset anymore
delete result[key];
return;
}
if (data.type === types_1.FIELD_TYPE.JSON) {
result[key] = JSON.parse(value);
}
else if (data.type === types_1.FIELD_TYPE.BOOLEAN) {
// assume it's 1 or 0
result[key] = !!value;
}
});
return result;
}
processForSave(value, field) {
const data = this.getFieldData(field);
if ((data === null || data === void 0 ? void 0 : data.type) === types_1.FIELD_TYPE.JSON) {
return JSON.stringify(value);
}
return value;
}
get(id) {
return __awaiter(this, void 0, void 0, function* () {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
const result = yield this.search({
id,
});
if (result.length === 0) {
return null;
}
return result[0];
});
}
search() {
return __awaiter(this, arguments, void 0, function* (queryData = {}, order, limit, offset) {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
let keys = [];
// for some weird reason when we convert to JS, the instanceof is no longer working,
// so fall back to checking the constructor name
if (queryData instanceof whereBuilder_1.WhereBuilder || queryData.constructor.name === "WhereBuilder") {
keys = queryData.getAllFields();
}
else {
keys = Object.keys(queryData);
}
for (let i = 0; i < keys.length; i++) {
const key = keys[0];
const data = this.getFieldData(key);
if (data === null) {
throw new Error(`No such field '${key}'`);
}
}
const result = yield connection.search(this.table, queryData, order, limit, offset);
return result.map((item) => {
return this.processResult(item);
});
});
}
count() {
return __awaiter(this, arguments, void 0, function* (queryData = {}) {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
const keys = Object.keys(queryData);
for (let i = 0; i < keys.length; i++) {
const key = keys[0];
const data = this.getFieldData(key);
if (data === null) {
throw new Error(`No such field '${key}'`);
}
}
const result = yield connection.count(this.table, queryData);
return result;
});
}
update(id, fields) {
return __awaiter(this, void 0, void 0, function* () {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
const keys = Object.keys(fields);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const data = this.getFieldData(key);
if (data === null) {
throw new Error(`No such field '${key}'`);
}
const value = fields[key];
if (value === null && data.meta.includes(types_1.FIELD_META.REQUIRED)) {
throw new Error(`Field '${key}' cannot be set to null`);
}
fields[key] = this.processForSave(value, key);
}
const tableFields = Object.keys(this.fields);
const handledFields = tableFields.reduce((obj, field) => {
return Object.assign(Object.assign({}, obj), { [field]: this.getFieldData(field) });
}, {});
return yield connection.update(this.table, id, fields, handledFields);
});
}
insert(insertData) {
return __awaiter(this, void 0, void 0, function* () {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
const keys = Object.keys(insertData);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const data = this.getFieldData(key);
if (data === null) {
throw new Error(`No such field '${key}'`);
}
const value = insertData[key];
if (value === null && data.meta.includes(types_1.FIELD_META.REQUIRED)) {
throw new Error(`Field '${key}' cannot be set to null`);
}
insertData[key] = this.processForSave(value, key);
}
const tableFields = Object.keys(this.fields);
for (let i = 0; i < tableFields.length; i++) {
const key = tableFields[i];
const fieldData = this.getFieldData(key);
if (!fieldData) {
throw new Error(`Field '${key}' not found in model`);
}
if (fieldData.meta.includes(types_1.FIELD_META.REQUIRED) && (insertData[key] === null || insertData[key] === undefined)) {
throw new Error(`Required field '${key}' not found`);
}
}
return connection.insert(this.table, tableFields.reduce((obj, field) => {
return Object.assign(Object.assign({}, obj), { [field]: this.getFieldData(field) });
}, {}), insertData);
});
}
delete(id) {
return __awaiter(this, void 0, void 0, function* () {
const connection = (0, connections_1.getDefaultConnection)();
if (connection === null) {
throw new Error('No default connection set');
}
return connection.delete(this.table, id);
});
}
filterForExport(data) {
if (Array.isArray(data)) {
return data.map((item) => {
return this.filterForExport(item);
});
}
const result = Object.assign({}, data);
const filterFields = this._getFieldsWithMeta(types_1.FIELD_META.FILTERED);
const filterFieldsIds = filterFields.map((field) => {
return field.id;
});
for (const key in result) {
if (filterFieldsIds.includes(key)) {
delete result[key];
}
}
return result;
}
}
exports.Model = Model;