{"version":3,"file":"relations-orderer.mjs","sources":["../../src/entity-manager/relations-orderer.ts"],"sourcesContent":["import { castArray, maxBy } from 'lodash/fp';\nimport _ from 'lodash';\n\nimport { InvalidRelationError } from '../errors';\nimport type { ID } from '../types';\n\ninterface Link {\n id: ID;\n position?: { before?: ID; after?: ID; start?: true; end?: true };\n order?: number;\n __component?: string;\n}\n\ninterface OrderedLink extends Link {\n init?: boolean;\n order: number;\n}\n\n/**\n * When connecting relations, the order you connect them matters.\n *\n * Example, if you connect the following relations:\n * { id: 5, position: { before: 1 } }\n * { id: 1, position: { before: 2 } }\n * { id: 2, position: { end: true } }\n *\n * Going through the connect array, id 5 has to be connected before id 1,\n * so the order of id5 = id1 - 1. But the order value of id 1 is unknown.\n * The only way to know the order of id 1 is to connect it first.\n *\n * This function makes sure the relations are connected in the right order:\n * { id: 2, position: { end: true } }\n * { id: 1, position: { before: 2 } }\n * { id: 5, position: { before: 1 } }\n *\n */\nconst sortConnectArray = (connectArr: Link[], initialArr: Link[] = [], strictSort = true) => {\n const sortedConnect: Link[] = [];\n // Boolean to know if we have to recalculate the order of the relations\n let needsSorting = false;\n // Map to validate if relation is already in sortedConnect or DB.\n const relationInInitialArray = initialArr.reduce(\n (acc, rel) => ({ ...acc, [rel.id]: true }),\n {} as Record\n );\n // Map to store the first index where a relation id is connected\n const mappedRelations = connectArr.reduce(\n (mapper, relation: Link) => {\n const adjacentRelId = relation.position?.before || relation.position?.after;\n\n if (!adjacentRelId || (!relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId])) {\n needsSorting = true;\n }\n\n /**\n * We do not allow duplicate relations to be connected, so we need to check for uniqueness with components\n * Note that the id here includes the uid for polymorphic relations\n *\n * So for normal relations, the same id means the same relation\n * For component relations, it means the unique combo of (id, component name)\n */\n\n // Check if there's an existing relation with this id\n const existingRelation = mapper[relation.id];\n\n // Check if existing relation has a component or not\n const hasNoComponent = existingRelation && !('__component' in existingRelation);\n\n // Check if the existing relation has the same component as the new relation\n const hasSameComponent =\n existingRelation && existingRelation.__component === relation.__component;\n\n // If we have an existing relation that is not unique (no component or same component) we won't accept it\n if (existingRelation && (hasNoComponent || hasSameComponent)) {\n throw new InvalidRelationError(\n `The relation with id ${relation.id} is already connected. ` +\n 'You cannot connect the same relation twice.'\n );\n }\n\n return {\n [relation.id]: { ...relation, computed: false },\n ...mapper,\n };\n },\n {} as Record\n );\n\n // If we don't need to sort the connect array, we can return it as is\n if (!needsSorting) return connectArr;\n\n // Recursively compute in which order the relation should be connected\n const computeRelation = (relation: Link, relationsSeenInBranch: Record) => {\n const adjacentRelId = relation.position?.before || relation.position?.after;\n const adjacentRelation = mappedRelations[adjacentRelId as ID];\n\n // If the relation has already been seen in the current branch,\n // it means there is a circular reference\n if (adjacentRelId && relationsSeenInBranch[adjacentRelId]) {\n throw new InvalidRelationError(\n 'A circular reference was found in the connect array. ' +\n 'One relation is trying to connect before/after another one that is trying to connect before/after it'\n );\n }\n\n // This relation has already been computed\n if (mappedRelations[relation.id]?.computed) {\n return;\n }\n\n mappedRelations[relation.id].computed = true;\n\n // Relation does not have a before or after attribute or is in the initial array\n if (!adjacentRelId || relationInInitialArray[adjacentRelId]) {\n sortedConnect.push(relation);\n return;\n }\n\n // Look if id is referenced elsewhere in the array\n if (mappedRelations[adjacentRelId]) {\n computeRelation(adjacentRelation, { ...relationsSeenInBranch, [relation.id]: true });\n sortedConnect.push(relation);\n } else if (strictSort) {\n // If we reach this point, it means that the adjacent relation is not in the connect array\n // and it is not in the database.\n throw new InvalidRelationError(\n `There was a problem connecting relation with id ${\n relation.id\n } at position ${JSON.stringify(\n relation.position\n )}. The relation with id ${adjacentRelId} needs to be connected first.`\n );\n } else {\n // We are in non-strict mode so we can push the relation.\n sortedConnect.push({ id: relation.id, position: { end: true } });\n }\n };\n\n // Iterate over connectArr and populate sortedConnect\n connectArr.forEach((relation) => computeRelation(relation, {}));\n\n return sortedConnect;\n};\n\n/**\n * Responsible for calculating the relations order when connecting them.\n *\n * The connect method takes an array of relations with positional attributes:\n * - before: the id of the relation to connect before\n * - after: the id of the relation to connect after\n * - end: it should be at the end\n * - start: it should be at the start\n *\n * Example:\n * - Having a connect array like:\n * [ { id: 4, before: 2 }, { id: 4, before: 3}, {id: 5, before: 4} ]\n * - With the initial relations:\n * [ { id: 2, order: 4 }, { id: 3, order: 10 } ]\n * - Step by step, going through the connect array, the array of relations would be:\n * [ { id: 4, order: 3.5 }, { id: 2, order: 4 }, { id: 3, order: 10 } ]\n * [ { id: 2, order: 4 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]\n * [ { id: 2, order: 4 }, { id: 5, order: 3.5 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]\n * - The final step would be to recalculate fractional order values.\n * [ { id: 2, order: 4 }, { id: 5, order: 3.33 }, { id: 4, order: 3.66 }, { id: 3, order: 10 } ]\n *\n * @param {Array<*>} initArr - array of relations to initialize the class with\n * @param {string} idColumn - the column name of the id\n * @param {string} orderColumn - the column name of the order\n * @param {boolean} strict - if true, will throw an error if a relation is connected adjacent to\n * another one that does not exist\n * @return {*}\n */\nconst relationsOrderer = >(\n initArr: TRelation[],\n idColumn: keyof TRelation,\n orderColumn: keyof TRelation,\n strict?: boolean\n) => {\n const computedRelations: OrderedLink[] = castArray(initArr ?? []).map((r) => ({\n init: true,\n id: r[idColumn] as ID,\n order: Number(r[orderColumn]) || 1,\n }));\n\n const maxOrder = maxBy('order', computedRelations)?.order || 0;\n\n const findRelation = (id: ID) => {\n const idx = computedRelations.findIndex((r) => r.id === id);\n return { idx, relation: computedRelations[idx] };\n };\n\n const removeRelation = (r: Link) => {\n const { idx } = findRelation(r.id);\n if (idx >= 0) {\n computedRelations.splice(idx, 1);\n }\n };\n\n const insertRelation = (r: Link) => {\n let idx;\n\n if (r.position?.before) {\n const { idx: _idx, relation } = findRelation(r.position.before);\n if (relation.init) {\n r.order = relation.order - 0.5;\n } else {\n r.order = relation.order;\n }\n idx = _idx;\n } else if (r.position?.after) {\n const { idx: _idx, relation } = findRelation(r.position.after);\n if (relation.init) {\n r.order = relation.order + 0.5;\n } else {\n r.order = relation.order;\n }\n\n idx = _idx + 1;\n } else if (r.position?.start) {\n r.order = 0.5;\n idx = 0;\n } else {\n r.order = maxOrder + 0.5;\n idx = computedRelations.length;\n }\n\n // Insert the relation in the array\n computedRelations.splice(idx, 0, r as OrderedLink);\n };\n\n return {\n disconnect(relations: Link | Link[]) {\n castArray(relations).forEach((relation) => {\n removeRelation(relation);\n });\n return this;\n },\n connect(relations: Link | Link[]) {\n sortConnectArray(castArray(relations), computedRelations, strict).forEach((relation) => {\n this.disconnect(relation);\n\n try {\n insertRelation(relation);\n } catch (err) {\n throw new Error(\n `There was a problem connecting relation with id ${\n relation.id\n } at position ${JSON.stringify(\n relation.position\n )}. The list of connect relations is not valid`\n );\n }\n });\n return this;\n },\n get() {\n return computedRelations;\n },\n /**\n * Get a map between the relation id and its order\n */\n getOrderMap() {\n return _(computedRelations)\n .groupBy('order')\n .reduce(\n (acc, relations) => {\n if (relations[0]?.init) return acc;\n relations.forEach((relation, idx) => {\n acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);\n });\n return acc;\n },\n {} as Record\n );\n },\n };\n};\n\nexport { relationsOrderer, sortConnectArray };\n"],"names":["sortConnectArray","connectArr","initialArr","strictSort","sortedConnect","needsSorting","relationInInitialArray","reduce","acc","rel","id","mappedRelations","mapper","relation","adjacentRelId","position","before","after","existingRelation","hasNoComponent","hasSameComponent","__component","InvalidRelationError","computed","computeRelation","relationsSeenInBranch","adjacentRelation","push","JSON","stringify","end","forEach","relationsOrderer","initArr","idColumn","orderColumn","strict","computedRelations","castArray","map","r","init","order","Number","maxOrder","maxBy","findRelation","idx","findIndex","removeRelation","splice","insertRelation","_idx","start","length","disconnect","relations","connect","err","Error","get","getOrderMap","_","groupBy","Math","floor"],"mappings":"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"}