@itrocks/mysql
Version:
Transforms model objects to and from MySQL database records
299 lines • 14.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mysql = exports.DEBUG = void 0;
exports.joinTableName = joinTableName;
exports.mysqlDependsOn = mysqlDependsOn;
const class_type_1 = require("@itrocks/class-type");
const class_type_2 = require("@itrocks/class-type");
const class_type_3 = require("@itrocks/class-type");
const property_type_1 = require("@itrocks/property-type");
const reflect_1 = require("@itrocks/reflect");
const reflect_2 = require("@itrocks/reflect");
const storage_1 = require("@itrocks/storage");
const mariadb_1 = require("mariadb");
exports.DEBUG = false;
const depends = {
applyReadTransformer: (record, property) => record[property],
applySaveTransformer: (object, property) => object[property],
columnOf: name => name.toLowerCase(),
componentOf: () => false,
ignoreTransformedValue: Symbol('ignoreTransformedValue'),
QueryFunction: class {
},
queryFunctionCall: () => [undefined, ' = ?'],
storeOf: target => (0, class_type_3.typeOf)(target).name.toLowerCase()
};
function joinTableName(object1, object2) {
if (typeof object1 !== 'string')
object1 = depends.storeOf(object1);
if (typeof object2 !== 'string')
object2 = depends.storeOf(object2);
return [object1, object2].sort().join('_');
}
function mysqlDependsOn(dependencies) {
Object.assign(depends, dependencies);
}
class Mysql extends storage_1.DataSource {
config;
connection;
constructor(config) {
super();
this.config = config;
}
async connect() {
const mariaDbConfig = Object.assign(this.config, {
allowPublicKeyRetrieval: true,
dateStrings: false
});
return this.connection = await (0, mariadb_1.createConnection)(mariaDbConfig);
}
async delete(object, property = 'id') {
await this.deleteId(object, object[property], property);
return this.disconnectObject(object);
}
async deleteId(type, id, property = 'id') {
const connection = this.connection ?? await this.connect();
if (exports.DEBUG)
console.log('DELETE FROM `' + depends.storeOf(type) + '` WHERE `' + depends.columnOf(property) + '` = ?', [id]);
await connection.query('DELETE FROM `' + depends.storeOf(type) + '` WHERE `' + depends.columnOf(property) + '` = ?', [id]);
}
async deleteRelatedId(object, property, id) {
const connection = this.connection ?? await this.connect();
const objectTable = depends.storeOf(object);
const propertyTable = depends.storeOf(new reflect_2.ReflectProperty(object, property).collectionType.elementType.type);
if (!objectTable || !propertyTable) {
throw 'Collection objects are not stored';
}
const joinTable = joinTableName(objectTable, propertyTable);
const query = 'DELETE FROM `' + joinTable + '` WHERE ' + objectTable + '_id = ? AND ' + propertyTable + '_id = ?';
const values = [object.id, id];
if (exports.DEBUG)
console.log(query, JSON.stringify(values));
connection.query(query, values);
}
async insert(object) {
const connection = this.connection ?? await this.connect();
const [values, deferred] = await this.valuesToDb(object);
const sql = this.propertiesToSql(values);
const query = 'INSERT INTO `' + depends.storeOf(object) + '` SET ' + sql;
if (exports.DEBUG)
console.log(query, JSON.stringify(Object.values(values)));
const result = await connection.query(query, Object.values(values));
const id = result.insertId;
const entity = this.connectObject(object, ((id >= Number.MIN_SAFE_INTEGER) && (id <= Number.MAX_SAFE_INTEGER)) ? Number(id) : id);
for (const callback of deferred) {
callback(object);
}
return entity;
}
async insertRelatedId(object, property, id) {
const connection = this.connection ?? await this.connect();
const objectTable = depends.storeOf(object);
const propertyTable = depends.storeOf(new reflect_2.ReflectProperty(object, property).collectionType.elementType.type);
if (!objectTable || !propertyTable) {
throw 'Collection objects are not stored';
}
const joinTable = joinTableName(objectTable, propertyTable);
const query = 'INSERT INTO `' + joinTable + '` SET ' + objectTable + '_id = ?, ' + propertyTable + '_id = ?';
const values = [object.id, id];
if (exports.DEBUG)
console.log(query, JSON.stringify(values));
connection.query(query, values);
}
propertiesToSearchSql(search) {
const sql = Object.entries(search)
.map(([name, value]) => {
let sql;
if (value instanceof depends.QueryFunction) {
[search[name], sql] = depends.queryFunctionCall(value);
}
else {
sql = ' = ?';
}
return '`' + depends.columnOf(name) + '`' + sql;
})
.join(' AND ');
return sql.length
? ' WHERE ' + sql
: '';
}
propertiesToSql(object) {
return Object.keys(object).map(name => '`' + depends.columnOf(name) + '` = ?').join(', ');
}
propertiesToSqlSelect(type) {
const sql = ['id'];
for (const property of new reflect_1.ReflectClass(type).properties) {
const propertyType = property.type;
if (propertyType instanceof property_type_1.CollectionType)
continue;
const propertyName = ((0, class_type_2.isAnyType)(propertyType.type) && depends.storeOf(propertyType.type))
? property.name + 'Id'
: property.name;
const columnName = depends.columnOf(propertyName);
sql.push((columnName.length !== propertyName.length)
? columnName + ' ' + propertyName
: propertyName);
}
return sql.join(', ');
}
async read(type, id) {
const connection = this.connection ?? await this.connect();
const propertiesSql = this.propertiesToSqlSelect(type);
if (exports.DEBUG)
console.log('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '` WHERE id = ?', [id]);
const rows = await connection.query('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '` WHERE id = ?', [id]);
return this.valuesFromDb(rows[0], type);
}
async readCollection(object, property, type = new reflect_2.ReflectProperty(object, property).collectionType.elementType.type) {
const connection = this.connection ?? await this.connect();
const propertiesSql = this.propertiesToSqlSelect(type);
const objectTable = depends.storeOf(object);
const propertyTable = depends.storeOf(type);
if (!objectTable || !propertyTable) {
throw 'Collection objects are not stored';
}
let query;
if (depends.componentOf(object, property)) {
query = 'SELECT ' + propertiesSql + ' FROM `' + propertyTable + '` WHERE ' + objectTable + '_id = ?';
}
else {
const joinTable = joinTableName(objectTable, propertyTable);
query = 'SELECT `' + propertyTable + '`.' + propertiesSql + ' FROM `' + propertyTable + '`'
+ ' INNER JOIN `' + joinTable + '` ON `' + joinTable + '`.' + propertyTable + '_id = `' + propertyTable + '`.id'
+ ' WHERE `' + joinTable + '`.' + objectTable + '_id = ?';
}
const rows = await connection.query(query, [object.id]);
return Promise.all(rows.map(row => this.valuesFromDb(row, type)));
}
async readCollectionIds(object, property, type = new reflect_2.ReflectProperty(object, property).collectionType.elementType.type) {
const connection = this.connection ?? await this.connect();
const objectTable = depends.storeOf(object);
const propertyTable = depends.storeOf(type);
if (!objectTable || !propertyTable) {
throw 'Collection objects are not stored';
}
let query;
if (depends.componentOf(object, property)) {
query = 'SELECT id FROM `' + propertyTable + '` WHERE ' + objectTable + '_id = ?';
}
else {
const joinTable = joinTableName(objectTable, propertyTable);
query = 'SELECT ' + propertyTable + '_id id FROM `' + joinTable + '`'
+ ' WHERE `' + joinTable + '`.' + objectTable + '_id = ?';
}
const rows = await connection.query(query, [object.id]);
return Promise.all(rows.map(row => row.id));
}
async readMultiple(type, ids) {
if (!ids.length)
return [];
const connection = this.connection ?? await this.connect();
const propertiesSql = this.propertiesToSqlSelect(type);
const questionMarks = Array(ids.length).fill('?').join(', ');
if (exports.DEBUG)
console.log('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '` WHERE id IN (' + questionMarks + ')', ids);
const rows = await connection.query('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '` WHERE id IN (' + questionMarks + ')', ids);
return Promise.all(rows.map(row => this.valuesFromDb(row, type)));
}
async save(object) {
return this.isObjectConnected(object)
? this.update(object)
: this.insert(object);
}
async saveCollection(object, property, value) {
return depends.componentOf(object, property.endsWith('Ids') ? property.slice(0, -3) : property)
? this.saveComponents(object, property, value)
: this.saveLinks(object, property, value);
}
async saveComponents(object, property, components) {
// TODO
}
async saveLinks(object, property, links) {
property = property.endsWith('Ids') ? property.slice(0, -3) : property;
const connection = this.connection ?? await this.connect();
const objectTable = depends.storeOf(object);
const propertyType = new reflect_2.ReflectProperty(object, property).collectionType.elementType.type;
const propertyTable = depends.storeOf(propertyType);
const linkColumn = depends.columnOf(propertyTable) + '_id';
const linkTable = joinTableName(objectTable, propertyTable);
const objectColumn = depends.columnOf(objectTable) + '_id';
const objectId = object.id;
const stored = await this.readCollectionIds(object, property, propertyType);
const saved = [];
for (const link of links) {
const linkId = (typeof link === 'object')
? (this.isObjectConnected(link) ? link.id : (await this.save(link)).id)
: link;
saved.push(linkId);
if (stored.includes(linkId))
continue;
await connection.query('INSERT INTO `' + linkTable + '` SET ' + objectColumn + ' = ?, ' + linkColumn + ' = ?', [objectId, linkId]);
stored.push(linkId);
}
for (const storedId of stored) {
if (saved.includes(storedId))
continue;
await connection.query('DELETE FROM `' + linkTable + '` WHERE ' + objectColumn + ' = ? AND ' + linkColumn + ' = ?', [objectId, storedId]);
}
}
async search(type, search = {}) {
const connection = this.connection ?? await this.connect();
const propertiesSql = this.propertiesToSqlSelect(type);
Object.setPrototypeOf(search, type.prototype);
const sql = this.propertiesToSearchSql(search);
const [values] = await this.valuesToDb(search);
if (exports.DEBUG)
console.log('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '`' + sql, '[', values, ']');
const rows = await connection.query('SELECT ' + propertiesSql + ' FROM `' + depends.storeOf(type) + '`' + sql, Object.values(values));
return Promise.all(rows.map(row => this.valuesFromDb(row, type)));
}
async update(object) {
const connection = this.connection ?? await this.connect();
const [values, deferred] = await this.valuesToDb(object);
const sql = this.propertiesToSql(values);
const query = 'UPDATE `' + depends.storeOf(object) + '` SET ' + sql + ' WHERE id = ?';
if (exports.DEBUG)
console.log(query, JSON.stringify(Object.values(values).concat([object.id])));
await connection.query(query, Object.values(values).concat([object.id]));
for (const callback of deferred) {
callback(object);
}
return object;
}
async valuesFromDb(record, type) {
const object = (new type);
let property;
for (property in record) {
const value = await depends.applyReadTransformer(record, property, object);
if (value === depends.ignoreTransformedValue)
continue;
object[property] = value;
if (property.endsWith('Id')) {
delete object[property.slice(0, -2)];
}
}
return object;
}
async valuesToDb(object) {
const deferred = [];
const record = {};
for (const property of Object.keys(object)) {
const value = await depends.applySaveTransformer(object, property, record);
if (value === depends.ignoreTransformedValue) {
continue;
}
if (Array.isArray(value)) {
deferred.push((object) => this.saveCollection(object, property, value));
continue;
}
if ((0, class_type_1.isAnyFunction)(value)) {
deferred.push(value);
continue;
}
record[depends.columnOf(property)] = value;
}
return [record, deferred];
}
}
exports.Mysql = Mysql;
//# sourceMappingURL=mysql.js.map