@ssb-graphql/whakapapa
Version:
GraphQL types and resolvers for the ssb-whakapapa plugin
128 lines (110 loc) • 3.55 kB
JavaScript
const set = require('lodash.set')
/*
* This is designed to help track which links you've found,
* and which parents you've checked links on
*
* Use:
* 1. initialise with starting person "targetId"
* 2. do some lookups, registering links with addChildLink / addPartnerLink
* 3. call state.newAdults to find which adults have been newly discovered
* 4. call state.resetNewAdults() to zero that out
* 5. (repeat 2-4)
* 6. get final results using state.output
*/
module.exports = function LinkStateHelper (targetId) {
const links = {
child: {
// [parentId]: {
// [childId]: link
// }
},
partner: {
// [partnerA]: {
// [partnerB]: link
// }
}
}
const adults = new Set([targetId]) // i.e. of parent generation, not child generation
const newAdults = new Set([])
return {
addChildLink (link) {
if (!adults.has(link.parent)) newAdults.add(link.parent)
set(links.child, [link.parent, link.child], link)
},
addPartnerLink (link) {
if (!adults.has(link.parent)) newAdults.add(link.parent)
if (!adults.has(link.child)) newAdults.add(link.child)
set(links.partner, [link.parent, link.child].sort(), link)
// NOTE partner links are undirected
// so instead of using saying parentId = link.parent
// we define [partnerA, partnerB] = [link.parent, link.child].sort()
},
get newAdults () {
return Array.from(newAdults)
},
resetNewAdults () {
newAdults.forEach(adult => adults.add(adult))
newAdults.clear()
},
get output () {
// if find, then mock an "unknown" partnerLink
return {
childLinks: linksOutput(links.child),
partnerLinks: [
...linksOutput(links.partner, 'partners'),
...inferredPartnerLinks(links, new Set([...adults, ...newAdults]))
]
}
}
}
}
function inferredPartnerLinks (links, adults) {
const results = []
// for each adult, check for parternLink with other adults
adults.forEach(i => {
adults.forEach(j => {
if (i === j) return
const [partnerA, partnerB] = [i, j].sort()
if (partnerA !== i) return // avoid duplication from order invariance
// if there is a partnerLink, skip
if (links.partner[partnerA] && links.partner[partnerA][partnerB]) return
// if there is not a partnerLink, see if they have any shared birth-children
if (haveSharedBirthChild(links, partnerA, partnerB)) {
// if there is, make an "inferred" partnerLink
results.push({
parent: partnerA,
child: partnerB,
relationshipType: 'inferred'
})
}
})
})
return results
}
function haveSharedBirthChild (links, partnerA, partnerB) {
const childrenOfA = Object.values(links.child[partnerA] || {})
.filter(isBirthLink)
.map(link => link.child)
const sharedBirthChild = childrenOfA.find(child => (
links.child[partnerB] &&
links.child[partnerB][child] &&
isBirthLink(links.child[partnerB][child])
))
return Boolean(sharedBirthChild)
}
function isBirthLink (link) {
return link.relationshipType === 'birth'
}
function linksOutput (links, defaultRelationshipType) {
return flatten(links)
.map(link => {
if (!link.relationshipType && defaultRelationshipType) {
link.relationshipType = defaultRelationshipType
}
return link
})
}
function flatten (links) {
return Object.values(links)
.reduce((acc, item) => acc.concat(Object.values(item)), [])
}