UNPKG

@wmfs/pg-model

Version:

Takes a relational database structure and returns model objects for noSQL-like abilities.

310 lines (259 loc) 9.83 kB
'use strict' const _ = require('lodash') const Finder = require('./actions/Finder') const Creator = require('./actions/Creator') const Destroyer = require('./actions/Destroyer') const Updater = require('./actions/Updater') const NotSet = 'NotSet' function callbackify (promise, callback) { promise .then(result => callback(null, result)) .catch(err => callback(err)) } class Model { constructor (components, options) { this.client = options.client const table = components.table this.namespace = components.namespace this.modelId = components.modelId this.fullModelId = this.namespace + '_' + this.modelId this.schemaName = components.schemaName this.tableName = components.tableName this.fullTableName = this.schemaName + '.' + this.tableName this.fkConstraints = table.fkConstraints this.columnNames = Object.keys(table.columns) this.columnToPropertyId = this.columnNames.reduce((cols, col) => { cols[col] = _.camelCase(col) return cols }, {}) this.propertyIdToColumn = Object.entries(this.columnToPropertyId).map(([col, prop]) => [prop, col]).reduce((props, [p, c]) => { props[p] = c return props }, {}) this.propertyToType = this.columnNames.reduce((cols, col) => { cols[this.columnToPropertyId[col]] = table.columns[col].dataType return cols }, {}) this.propertyIds = Object.entries(this.columnToPropertyId).filter(([col]) => col[0] !== '_').map(([col, prop]) => prop) this.pkColumnNames = table.pkColumnNames this.pkPropertyIds = this.pkColumnNames.map(column => this.columnToPropertyId[column]) this.attributeIds = _.difference(this.propertyIds, this.pkPropertyIds) this.subDocIds = [] // Populated once all state-machines are available this.fkColumnNames = Object.values(table.fkConstraints).reduce((cols, constraint) => cols.concat(constraint.sourceColumns), []) this.fkPropertyIds = this.fkColumnNames.map(fkColumnName => _.camelCase(fkColumnName)) this.attributeIdsWithoutfkPropertyIds = _.difference(this.attributeIds, this.fkPropertyIds) this.subModels = {}// Added once all state-machines are available this.deleteMissingSql = 'DELETE FROM ' + this.fullTableName + ' WHERE ' if (this.pkColumnNames.length === 1) { this.deleteMissingSql += this.pkColumnNames[0] + ' != ANY($1)' } else { // TODO: Support composite-keyed sub docs } this.deleteMissingSql = 'DELETE FROM ' + this.fullTableName + ' WHERE ' this.deleteMissingSql += this.pkColumnNames[0] + ' != ANY($1)' this.finder = new Finder(this) this.creator = new Creator(this) this.destroyer = new Destroyer(this) this.updater = new Updater(this) this.currentUserFn = () => null this.createdByField = null this.modifiedByField = null this.modifiedField = null if (options.service) { const service = options.service this.currentUserFn = service.currentUser ? () => service.currentUser() : () => null this.createdByField = service.createdByField ? service.createdByField : null this.modifiedByField = service.modifiedByField ? service.modifiedByField : null this.modifiedField = service.modifiedField ? service.modifiedField : null } } columnify (propertyIds) { if (Array.isArray(propertyIds)) { return propertyIds.map(id => this.propertyIdToColumn[id]) } return this.propertyIdToColumn[propertyIds] } create (jsonData, options = {}, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.create(jsonData, options), callback) } // if ... options.upsert = false const script = this.creator.makeStatements(jsonData, options) return this.client.run(script) } search (options, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.search(options), callback) } return this.finder.search(options) } findById (id, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.findById(id), callback) } return this.finder.findOne({ where: this.makeWhereFromId(id) }) } find (options, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.find(options), callback) } // if ... return this.finder.findAll(options) } findCount (options, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.findCount(options), callback) } // if ... return this.finder.findCount(options) } findOne (options, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.findOne(options), callback) } // if ... options.limit = 1 return this.finder.findOne(options) } extractIdFromJsonData (jsonData) { const id = [] this.pkPropertyIds.forEach( function (propertyId) { id.push(jsonData[propertyId]) } ) return id } update (doc, options = {}, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.update(doc, options), callback) } // if ... if (!Object.prototype.hasOwnProperty.call(options, 'destroyMissingSubDocs')) options.destroyMissingSubDocs = false if (!Object.prototype.hasOwnProperty.call(options, 'setMissingPropertiesToNull')) options.setMissingPropertiesToNull = true const script = this.updater.makeStatements(doc, options) return this.client.run(script) } patch (doc, options, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.patch(doc, options), callback) } // if ... options.destroyMissingSubDocs = false options.setMissingPropertiesToNull = false const script = this.updater.makeStatements(doc, options) return this.client.run(script) } upsert (jsonData, options = {}, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.upsert(jsonData, options), callback) } options.upsert = true const script = this.creator.makeStatements(jsonData, options) return this.client.run(script) } destroyById (id, callback = NotSet) { if (callback !== NotSet) { return callbackify(this.destroyById(id), callback) } const ids = Array.isArray(id) ? id : [id] const script = this.destroyer.makeStatements(ids) return this.client.run(script) } parseDoc (doc, options) { // Parse options let includeNullFks = false if (options && Object.prototype.hasOwnProperty.call(options, 'includeNullFks')) { includeNullFks = options.includeNullFks } const parsed = { keyAndAttributeProperties: {}, attributeProperties: {}, keyProperties: {}, readOnlyProperties: {}, unknownProperties: {}, subDocs: {} } _.forOwn(doc, (value, id) => { if (this.attributeIds.includes(id)) { if (this.propertyToType[id] === 'jsonb') { if (_.isArray(value)) { value = JSON.stringify(value) } } parsed.attributeProperties[id] = value parsed.keyAndAttributeProperties[id] = value } else { if (this.pkPropertyIds.includes(id)) { parsed.keyProperties[id] = value parsed.keyAndAttributeProperties[id] = value } else { if (id[0] === '_') { parsed.readOnlyProperties[id] = value } else { if (this.subDocIds.includes(id)) { parsed.subDocs[id] = value } else { parsed.unknownProperties[id] = value } } } } }) if (includeNullFks) { this.fkPropertyIds.forEach( function (propertyId) { if (!Object.prototype.hasOwnProperty.call(parsed.attributeProperties, propertyId)) { parsed.attributeProperties[propertyId] = null parsed.keyAndAttributeProperties[propertyId] = null } } ) } parsed.keyColumns = this.columnify(_.keys(parsed.keyProperties)) parsed.keyValues = _.values(parsed.keyProperties) parsed.attributeColumns = this.columnify(_.keys(parsed.attributeProperties)) parsed.attributeValues = _.values(parsed.attributeProperties) parsed.keyAndAttributeColumns = this.columnify(_.keys(parsed.keyAndAttributeProperties)) parsed.keyAndAttributeValues = _.values(parsed.keyAndAttributeProperties) parsed.missingAttributeIds = _.difference(this.attributeIdsWithoutfkPropertyIds, _.keys(parsed.attributeProperties)) parsed.missingAttributeColumnNames = this.columnify(parsed.missingAttributeIds) parsed.primaryKeyValues = {} this.pkPropertyIds.forEach( function (propertyId) { parsed.primaryKeyValues[propertyId] = doc[propertyId] } ) return parsed } makeWhereFromId (id) { const ids = Array.isArray(id) ? id : [id] const where = {} this.pkPropertyIds.forEach( (propertyId, index) => { where[propertyId] = { equals: ids[index] } } ) return where } extractPkValuesFromDoc (doc) { const pkValues = [] this.pkPropertyIds.forEach( (propertyId) => { pkValues.push(doc[propertyId]) } ) return pkValues } debug () { console.log('') console.log('------------------- ' + this.fullModelId + '-------------------') console.log(' primaryKey: ' + JSON.stringify(this.pkPropertyIds)) console.log(' propertyIds') this.propertyIds.forEach(propertyId => console.log(' + ' + propertyId)) console.log(' Table') console.log(' fullTableName: ' + this.fullTableName) console.log(' primaryKey: ' + JSON.stringify(this.pkColumnNames)) if (this.subDocIds.length > 0) { console.log(' SubDocs: ' + Object.keys(this.subModels).join()) } } // debug } module.exports = Model