UNPKG

@reldens/storage

Version:
562 lines (519 loc) 19.3 kB
/** * * Reldens - PrismaDriver * */ const { BaseDriver } = require('../base-driver'); const { PrismaTypeCaster } = require('./prisma-type-caster'); const { PrismaMetadataLoader } = require('./prisma-metadata-loader'); const { PrismaRelationResolver } = require('./prisma-relation-resolver'); const { PrismaFilterProcessor } = require('./prisma-filter-processor'); const { PrismaQueryBuilder } = require('./prisma-query-builder'); const { Logger, sc } = require('@reldens/utils'); class PrismaDriver extends BaseDriver { constructor(props) { super(props); if(!props.prisma){ Logger.critical('Missing Prisma client on Prisma driver.'); return false; } if(!props.model){ Logger.critical('Missing Prisma model on Prisma driver.'); return false; } this.prisma = props.prisma; this.model = props.model; this.allRelationMappings = sc.get(props, 'allRelationMappings', {}); this.initHelpers(); this.initModelMetadata(); } initHelpers() { this.metadataLoader = new PrismaMetadataLoader({ prisma: this.prisma, rawModel: this.rawModel }); this.typeCaster = new PrismaTypeCaster({ idFieldType: 'String', jsonFields: new Set(), fieldTypes: {}, fieldDefaults: {}, requiredFields: [], optionalFields: [], rawModel: this.rawModel }); this.relationResolver = new PrismaRelationResolver({ relationMetadata: {}, metadataLoader: this.metadataLoader, allRelationMappings: this.allRelationMappings, foreignKeyMappings: {}, foreignKeyTargetFields: {}, typeCaster: this.typeCaster }); this.filterProcessor = new PrismaFilterProcessor({ typeCaster: this.typeCaster, fieldTypes: {}, referenceFields: {}, jsonFields: new Set() }); this.queryBuilder = new PrismaQueryBuilder(); } initModelMetadata() { let metadata = this.metadataLoader.loadModelMetadata(this.tableName()); if(!metadata){ return; } this.requiredFields = metadata.requiredFields; this.optionalFields = metadata.optionalFields; this.fieldTypes = metadata.fieldTypes; this.fieldDefaults = metadata.fieldDefaults; this.idFieldType = metadata.idFieldType; this.idFieldName = sc.get(metadata, 'idFieldName', 'id'); this.referenceFields = metadata.referenceFields; this.relationMetadata = metadata.relationMetadata; this.foreignKeyMappings = metadata.foreignKeyMappings; this.foreignKeyTargetFields = sc.get(metadata, 'foreignKeyTargetFields', {}); this.jsonFields = metadata.jsonFields; this.typeCaster.updateMetadata({ idFieldType: this.idFieldType, jsonFields: this.jsonFields, fieldTypes: this.fieldTypes, fieldDefaults: this.fieldDefaults, requiredFields: this.requiredFields, optionalFields: this.optionalFields }); this.relationResolver.updateMetadata({ relationMetadata: this.relationMetadata, foreignKeyMappings: this.foreignKeyMappings, foreignKeyTargetFields: this.foreignKeyTargetFields }); this.filterProcessor.updateMetadata({ fieldTypes: this.fieldTypes, referenceFields: this.referenceFields, jsonFields: this.jsonFields }); let currentModelMappings = sc.get(this.allRelationMappings, this.tableName(), {}); this.relationResolver.setCurrentModelMappings(currentModelMappings); } databaseName() { return this.config.database || ''; } id() { return this.name || ''; } name() { return this.rawName || ''; } tableName() { return this.model.$name || this.model.name || ''; } property(propertyName) { return this.rawModel[propertyName] || null; } getAllRelations() { return this.relationResolver.getAllRelations(); } getQueryOptions() { return { select: this.select, limit: this.limit, offset: this.offset, sortBy: this.sortBy, sortDirection: this.sortDirection }; } prepareData(params, options = {}) { let skipObjects = sc.get(options, 'skipObjects', false); let convertRelations = sc.get(options, 'convertRelations', false); let isCreate = sc.get(options, 'isCreate', false); if(convertRelations){ let converted = this.relationResolver.convertForeignKeysToRelations(params, isCreate); return this.castDataFields(converted, skipObjects, isCreate); } return this.castDataFields(params, skipObjects, isCreate); } castDataFields(params, skipObjects = false, isCreate = false) { let data = {}; for(let key of Object.keys(params)){ let value = params[key]; if(skipObjects && sc.isObject(value) && !sc.isArray(value)){ continue; } if(isCreate && key === this.idFieldName && this.typeCaster.isNullOrEmpty(value)){ continue; } let fieldType = this.fieldTypes[key]; if(!fieldType){ if(!skipObjects && !this.typeCaster.isNullOrEmpty(value)){ data[key] = value; } continue; } let castedValue = this.typeCaster.castValue(value, fieldType, key); if(this.typeCaster.isValidCastedValue(castedValue)){ if(this.typeCaster.isJsonFieldType(fieldType) && null === castedValue && !skipObjects){ continue; } data[key] = castedValue; } } return data; } ensureRequiredFields(data) { let missingFields = []; for(let field of this.requiredFields){ if(sc.hasOwn(data, field)){ continue; } if(sc.hasOwn(this.foreignKeyMappings, field)){ let relationName = this.foreignKeyMappings[field]; if(sc.hasOwn(data, relationName)){ continue; } } if(sc.hasOwn(this.fieldDefaults, field)){ continue; } missingFields.push(field); } if(0 < missingFields.length){ Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', ')); } return data; } async executeWithNormalization(operation, errorMessage, defaultReturn) { try { return this.typeCaster.normalizeReturnData(await operation()); } catch(error) { Logger.error(errorMessage+error.message); return defaultReturn; } } async create(params) { let preparedData = this.prepareData(params, {convertRelations: true, isCreate: true}); this.ensureRequiredFields(preparedData); return this.executeWithNormalization( () => this.model.create({data: preparedData}), 'Create error: ', false ); } async createWithRelations(params, relations) { if(!sc.isArray(relations) || 0 === relations.length){ relations = this.getAllRelations(); } let preparedData = this.prepareData(params); this.ensureRequiredFields(preparedData); let createData = {data: preparedData}; if(0 < relations.length){ let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations); createData.include = includeData.include; let relationData = this.relationResolver.buildCreateDataWithRelations(params, relations); createData.data = {...createData.data, ...relationData}; return this.executeWithNormalization( async () => this.relationResolver.transformRelationResults( await this.model.create(createData), includeData.include, includeData.mapping ), 'Create with relations error: ', false ); } return this.executeWithNormalization( () => this.model.create(createData), 'Create with relations error: ', false ); } async update(filters, updatePatch) { let preparedData = this.prepareData(updatePatch, {skipObjects: true}); let processedFilters = this.filterProcessor.processFilters(filters); return this.executeWithNormalization( () => this.model.updateMany({where: processedFilters, data: preparedData}), 'Update error: ', false ); } async updateBy(field, fieldValue, updatePatch, operator = null) { let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator); let preparedData = this.prepareData(updatePatch, {skipObjects: true}); return this.executeWithNormalization( () => this.model.updateMany({where: filter, data: preparedData}), 'Update by error: ', false ); } async updateById(id, params) { let castedId = this.typeCaster.castToIdType(id); let found = await this.loadById(castedId); if(!found){ return false; } let preparedData = this.prepareData(params, {convertRelations: true}); return this.executeWithNormalization( () => this.model.update({where: {[this.idFieldName]: castedId}, data: preparedData}), 'Update by ID error: ', false ); } async upsert(params, filters) { let preparedData = this.prepareData(params, {convertRelations: true}); let idValue = params[this.idFieldName]; if(idValue){ let castedId = this.typeCaster.castToIdType(idValue); let existing = await this.loadById(castedId); if(existing){ let patch = Object.assign({}, preparedData); delete patch[this.idFieldName]; return this.executeWithNormalization( () => this.model.update({where: {[this.idFieldName]: castedId}, data: patch}), 'Upsert update error: ', false ); } } if(filters){ let existing = await this.loadOne(filters); if(existing){ return this.executeWithNormalization( () => this.model.update({ where: {[this.idFieldName]: this.typeCaster.castToIdType(existing[this.idFieldName])}, data: preparedData }), 'Upsert update by filter error: ', false ); } } return await this.create(preparedData); } async delete(filters = {}) { let processedFilters = this.filterProcessor.processFilters(filters); return this.executeWithNormalization( () => this.model.deleteMany({where: processedFilters}), 'Delete error: ', false ); } async deleteById(id) { let castedId = this.typeCaster.castToIdType(id); let found = await this.loadById(castedId); if(!found){ return false; } return this.executeWithNormalization( () => this.model.delete({where: {[this.idFieldName]: castedId}}), 'Delete by ID error: ', false ); } async count(filters) { let processedFilters = this.filterProcessor.processFilters(filters); return this.executeWithNormalization( () => this.model.count({where: processedFilters}), 'Count error: ', 0 ); } async countWithRelations(filters, relations) { if(!sc.isArray(relations) || 0 === relations.length){ relations = this.getAllRelations(); } let processedFilters = this.filterProcessor.processFilters(filters); let query = {where: processedFilters}; if(0 < relations.length){ query.include = this.relationResolver.buildIncludeObjectWithMapping(relations).include; } return this.executeWithNormalization( () => this.model.count(query), 'Count with relations error: ', 0 ); } async loadAll() { return this.executeWithNormalization( () => this.model.findMany(this.queryBuilder.buildQueryOptions(this.getQueryOptions())), 'Load all error: ', [] ); } async loadAllWithRelations(relations) { return this.executeQueryWithRelations( (q) => this.model.findMany(q), this.queryBuilder.buildQueryOptions(this.getQueryOptions()), relations, 'Load all with relations error: ', [] ); } async load(filters) { let processedFilters = this.filterProcessor.processFilters(filters); let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions()); query.where = processedFilters; return this.executeWithNormalization( () => this.model.findMany(query), 'Load error: ', [] ); } async loadWithRelations(filters, relations) { let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions()); query.where = this.filterProcessor.processFilters(filters); return this.executeQueryWithRelations( (q) => this.model.findMany(q), query, relations, 'Load with relations error: ', [] ); } async loadBy(field, fieldValue, operator = null) { let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator); let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions()); query.where = filter; return this.executeWithNormalization( () => this.model.findMany(query), 'Load by error: ', [] ); } async loadByWithRelations(field, fieldValue, relations, operator = null) { let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions()); query.where = this.filterProcessor.createSingleFilter(field, fieldValue, operator); return this.executeQueryWithRelations( (q) => this.model.findMany(q), query, relations, 'Load by with relations error: ', [] ); } async loadById(id) { let castedId = this.typeCaster.castToIdType(id); return this.executeWithNormalization( () => this.model.findUnique({where: {[this.idFieldName]: castedId}}), 'Load by ID error: ', null ); } async loadByIdWithRelations(id, relations) { return this.executeQueryWithRelations( (q) => this.model.findUnique(q), {where: {[this.idFieldName]: this.typeCaster.castToIdType(id)}}, relations, 'Load by ID with relations error: ', null ); } async loadByIds(ids) { let castedIds = ids.map(id => this.typeCaster.castToIdType(id)).filter(id => null !== id); if(0 === castedIds.length){ return []; } return this.executeWithNormalization( () => this.model.findMany({where: {[this.idFieldName]: {in: castedIds}}}), 'Load by IDs error: ', [] ); } async loadOne(filters) { let processedFilters = this.filterProcessor.processFilters(filters); let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false); query.where = processedFilters; return this.executeWithNormalization( () => this.model.findFirst(query), 'Load one error: ', null ); } async loadOneWithRelations(filters, relations) { let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false); query.where = this.filterProcessor.processFilters(filters); return this.executeQueryWithRelations( (q) => this.model.findFirst(q), query, relations, 'Load one with relations error: ', null ); } async loadOneBy(field, fieldValue, operator = null) { let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator); let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false); query.where = filter; return this.executeWithNormalization( () => this.model.findFirst(query), 'Load one by error: ', null ); } async loadOneByWithRelations(field, fieldValue, relations, operator = null) { let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false); query.where = this.filterProcessor.createSingleFilter(field, fieldValue, operator); return this.executeQueryWithRelations( (q) => this.model.findFirst(q), query, relations, 'Load one by with relations error: ', null ); } async executeQueryWithRelations(modelMethod, query, relations, errorMessage, defaultReturn) { if(!sc.isArray(relations) || 0 === relations.length){ relations = this.getAllRelations(); } let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations); if(0 < Object.keys(includeData.include).length){ query.include = includeData.include; } return this.executeWithNormalization( async () => this.relationResolver.transformRelationResults( await modelMethod(query), includeData.include, includeData.mapping ), errorMessage, defaultReturn ); } } module.exports.PrismaDriver = PrismaDriver;