UNPKG

quaerateum

Version:

Simple typescript ORM for node.js based on data-mapper, unit-of-work and identity-map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JS.

73 lines (56 loc) 2.98 kB
import { Utils } from '../utils'; import { MetadataStorage } from '../metadata'; import { EntityData, EntityProperty, IEntity, IEntityType } from '../decorators'; import { ChangeSet, ChangeSetType } from './ChangeSet'; import { Collection, EntityIdentifier, EntityValidator, ReferenceType } from '../entity'; export class ChangeSetComputer { private readonly metadata = MetadataStorage.getMetadata(); constructor(private readonly validator: EntityValidator, private readonly originalEntityData: Record<string, EntityData<IEntity>>, private readonly identifierMap: Record<string, EntityIdentifier>) { } computeChangeSet<T extends IEntityType<T>>(entity: T): ChangeSet<T> | null { const changeSet = { entity } as ChangeSet<T>; const meta = this.metadata[entity.constructor.name]; changeSet.name = meta.name; changeSet.type = this.originalEntityData[entity.__uuid] ? ChangeSetType.UPDATE : ChangeSetType.CREATE; changeSet.collection = meta.collection; changeSet.payload = this.computePayload(entity); this.validator.validate<T>(changeSet.entity, changeSet.payload, meta); for (const prop of Object.values(meta.properties)) { this.processReference(changeSet, prop); } if (changeSet.type === ChangeSetType.UPDATE && Object.keys(changeSet.payload).length === 0) { return null; } return changeSet; } private computePayload<T extends IEntityType<T>>(entity: T): EntityData<T> { if (this.originalEntityData[entity.__uuid]) { return Utils.diffEntities<T>(this.originalEntityData[entity.__uuid] as T, entity); } else { return Utils.prepareEntity(entity); } } private processReference<T extends IEntityType<T>>(changeSet: ChangeSet<T>, prop: EntityProperty): void { const isToOneOwner = prop.reference === ReferenceType.MANY_TO_ONE || (prop.reference === ReferenceType.ONE_TO_ONE && prop.owner); if (prop.reference === ReferenceType.MANY_TO_MANY && prop.owner) { this.processManyToMany(changeSet, prop, changeSet.entity[prop.name as keyof T]); } else if (isToOneOwner && changeSet.entity[prop.name as keyof T]) { this.processManyToOne(prop, changeSet); } } private processManyToOne<T extends IEntityType<T>>(prop: EntityProperty, changeSet: ChangeSet<T>): void { const pk = this.metadata[prop.type].primaryKey as keyof T; const entity = changeSet.entity[prop.name as keyof T] as T; if (!entity[pk]) { changeSet.payload[prop.name] = this.identifierMap[entity.__uuid]; } } private processManyToMany<T extends IEntityType<T>>(changeSet: ChangeSet<T>, prop: EntityProperty, collection: Collection<IEntity>): void { if (prop.owner && collection.isDirty()) { const pk = this.metadata[prop.type].primaryKey as keyof IEntity; changeSet.payload[prop.name] = collection.getItems().map(item => item[pk] || this.identifierMap[item.__uuid]); collection.setDirty(false); } } }