{"version":3,"sources":["../../src/persistence/subject-builder/OneToManySubjectBuilder.ts"],"names":[],"mappings":"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","file":"OneToManySubjectBuilder.js","sourcesContent":["import { Subject } from \"../Subject\"\nimport { OrmUtils } from \"../../util/OrmUtils\"\nimport { ObjectLiteral } from \"../../common/ObjectLiteral\"\nimport { EntityMetadata } from \"../../metadata/EntityMetadata\"\nimport { RelationMetadata } from \"../../metadata/RelationMetadata\"\n\n/**\n * Builds operations needs to be executed for one-to-many relations of the given subjects.\n *\n * by example: post contains one-to-many relation with category in the property called \"categories\", e.g.\n * @OneToMany(type => Category, category => category.post) categories: Category[]\n * If user adds categories into the post and saves post we need to bind them.\n * This operation requires updation of category table since its owner of the relation and contains a join column.\n *\n * note: this class shares lot of things with OneToOneInverseSideOperationBuilder, so when you change this class\n * make sure to reflect changes there as well.\n */\nexport class OneToManySubjectBuilder {\n // ---------------------------------------------------------------------\n // Constructor\n // ---------------------------------------------------------------------\n\n constructor(protected subjects: Subject[]) {}\n\n // ---------------------------------------------------------------------\n // Public Methods\n // ---------------------------------------------------------------------\n\n /**\n * Builds all required operations.\n */\n build(): void {\n this.subjects.forEach((subject) => {\n subject.metadata.oneToManyRelations.forEach((relation) => {\n // skip relations for which persistence is disabled\n if (relation.persistenceEnabled === false) return\n\n this.buildForSubjectRelation(subject, relation)\n })\n })\n }\n\n // ---------------------------------------------------------------------\n // Protected Methods\n // ---------------------------------------------------------------------\n\n /**\n * Builds operations for a given subject and relation.\n *\n * by example: subject is \"post\" entity we are saving here and relation is \"categories\" inside it here.\n */\n protected buildForSubjectRelation(\n subject: Subject,\n relation: RelationMetadata,\n ) {\n // prepare objects (relation id maps) for the database entity\n // by example: since subject is a post, we are expecting to get all post's categories saved in the database here,\n // particularly their relation ids, e.g. category ids stored in the database\n\n // in most cases relatedEntityDatabaseValues will contain only the entity key properties.\n // this is because subject.databaseEntity contains relations with loaded relation ids only.\n // however if the entity uses the afterLoad hook to calculate any properties, the fetched \"key object\" might include ADDITIONAL properties.\n // to handle such situations, we pass the data to relation.inverseEntityMetadata.getEntityIdMap to extract the key without any other properties.\n\n let relatedEntityDatabaseRelationIds: ObjectLiteral[] = []\n if (subject.databaseEntity) {\n // related entities in the database can exist only if this entity (post) is saved\n const relatedEntityDatabaseRelation: ObjectLiteral[] | undefined =\n relation.getEntityValue(subject.databaseEntity)\n if (relatedEntityDatabaseRelation) {\n relatedEntityDatabaseRelationIds =\n relatedEntityDatabaseRelation.map(\n (entity) =>\n relation.inverseEntityMetadata.getEntityIdMap(\n entity,\n )!,\n )\n }\n }\n\n // get related entities of persisted entity\n // by example: get categories from the passed to persist post entity\n let relatedEntities: ObjectLiteral[] = relation.getEntityValue(\n subject.entity!,\n )\n if (relatedEntities === null)\n // we treat relations set to null as removed, so we don't skip it\n relatedEntities = [] as ObjectLiteral[]\n if (relatedEntities === undefined)\n // if relation is undefined then nothing to update\n return\n\n // extract only relation ids from the related entities, since we only need them for comparison\n // by example: extract from categories only relation ids (category id, or let's say category title, depend on join column options)\n const relatedPersistedEntityRelationIds: ObjectLiteral[] = []\n relatedEntities.forEach((relatedEntity) => {\n // by example: relatedEntity is a category here\n let relationIdMap =\n relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity) // by example: relationIdMap is category.id map here, e.g. { id: ... }\n\n // try to find a subject of this related entity, maybe it was loaded or was marked for persistence\n let relatedEntitySubject = this.subjects.find((subject) => {\n return subject.entity === relatedEntity\n })\n\n // if subject with entity was found take subject identifier as relation id map since it may contain extra properties resolved\n if (relatedEntitySubject)\n relationIdMap = relatedEntitySubject.identifier\n\n // if relationIdMap is undefined then it means user binds object which is not saved in the database yet\n // by example: if post contains categories which does not have ids yet (because they are new)\n // it means they are always newly inserted and relation update operation always must be created for them\n // it does not make sense to perform difference operation for them for both add and remove actions\n if (!relationIdMap) {\n // we decided to remove this error because it brings complications when saving object with non-saved entities\n // if (!relatedEntitySubject)\n // throw new TypeORMError(`One-to-many relation \"${relation.entityMetadata.name}.${relation.propertyPath}\" contains ` +\n // `entities which do not exist in the database yet, thus they cannot be bind in the database. ` +\n // `Please setup cascade insertion or save entities before binding it.`);\n if (!relatedEntitySubject) return\n\n // okay, so related subject exist and its marked for insertion, then add a new change map\n // by example: this will tell category to insert into its post relation our post we are working with\n // relatedEntitySubject is newly inserted CategorySubject\n // relation.inverseRelation is ManyToOne relation inside Category\n // subject is Post needs to be inserted into Category\n relatedEntitySubject.changeMaps.push({\n relation: relation.inverseRelation!,\n value: subject,\n })\n\n return\n }\n\n // check if this binding really exist in the database\n // by example: find our category if its already bind in the database\n const relationIdInDatabaseSubjectRelation =\n relatedEntityDatabaseRelationIds.find(\n (relatedDatabaseEntityRelationId) => {\n return OrmUtils.compareIds(\n relationIdMap,\n relatedDatabaseEntityRelationId,\n )\n },\n )\n\n // if relationIdMap DOES NOT exist in the subject's relation in the database it means its a new relation and we need to \"bind\" them\n // by example: this will tell category to insert into its post relation our post we are working with\n // relatedEntitySubject is newly inserted CategorySubject\n // relation.inverseRelation is ManyToOne relation inside Category\n // subject is Post needs to be inserted into Category\n if (!relationIdInDatabaseSubjectRelation) {\n // if there is no relatedEntitySubject then it means \"category\" wasn't persisted,\n // but since we are going to update \"category\" table (since its an owning side of relation with join column)\n // we create a new subject here:\n if (!relatedEntitySubject) {\n relatedEntitySubject = new Subject({\n metadata: relation.inverseEntityMetadata,\n parentSubject: subject,\n canBeUpdated: true,\n identifier: relationIdMap,\n })\n this.subjects.push(relatedEntitySubject)\n }\n\n relatedEntitySubject.changeMaps.push({\n relation: relation.inverseRelation!,\n value: subject,\n })\n }\n\n // if related entity has relation id then we add it to the list of relation ids\n // this list will be used later to compare with database relation ids to find a difference\n // what exist in this array and does not exist in the database are newly inserted relations\n // what does not exist in this array, but exist in the database are removed relations\n // removed relations are set to null from inverse side of relation\n relatedPersistedEntityRelationIds.push(relationIdMap)\n })\n\n // find what related entities were added and what were removed based on difference between what we save and what database has\n if (relation.inverseRelation?.orphanedRowAction !== \"disable\") {\n EntityMetadata.difference(\n relatedEntityDatabaseRelationIds,\n relatedPersistedEntityRelationIds,\n ).forEach((removedRelatedEntityRelationId) => {\n // by example: removedRelatedEntityRelationId is category that was bind in the database before, but now its unbind\n\n // todo: probably we can improve this in the future by finding entity with column those values,\n // todo: maybe it was already in persistence process. This is possible due to unique requirements of join columns\n // we create a new subject which operations will be executed in subject operation executor\n const removedRelatedEntitySubject = new Subject({\n metadata: relation.inverseEntityMetadata,\n parentSubject: subject,\n identifier: removedRelatedEntityRelationId,\n })\n\n if (\n !relation.inverseRelation ||\n relation.inverseRelation.orphanedRowAction === \"nullify\"\n ) {\n removedRelatedEntitySubject.canBeUpdated = true\n removedRelatedEntitySubject.changeMaps = [\n {\n relation: relation.inverseRelation!,\n value: null,\n },\n ]\n } else if (\n relation.inverseRelation.orphanedRowAction === \"delete\"\n ) {\n removedRelatedEntitySubject.mustBeRemoved = true\n } else if (\n relation.inverseRelation.orphanedRowAction === \"soft-delete\"\n ) {\n removedRelatedEntitySubject.canBeSoftRemoved = true\n }\n\n this.subjects.push(removedRelatedEntitySubject)\n })\n }\n }\n}\n"],"sourceRoot":"../.."}