UNPKG

@itrocks/mysql

Version:

Transforms model objects to and from MySQL database records

299 lines 14.1 kB
"use strict"; 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