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>, private readonly identifierMap: Record) { } computeChangeSet>(entity: T): ChangeSet | null { const changeSet = { entity } as ChangeSet; 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(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>(entity: T): EntityData { if (this.originalEntityData[entity.__uuid]) { return Utils.diffEntities(this.originalEntityData[entity.__uuid] as T, entity); } else { return Utils.prepareEntity(entity); } } private processReference>(changeSet: ChangeSet, 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>(prop: EntityProperty, changeSet: ChangeSet): 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>(changeSet: ChangeSet, prop: EntityProperty, collection: Collection): 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); } } }