This is a WIP for work, so it's not perfect, but here's an example of a nice big parent-pointer tree (as well as an index and a collection of orphaned sub-trees). You could very easily implement multiple parents in a big efficient cyclic graph for free, thanks to persistent data structures.
const entries = [
{
id: 'hash of { meta, data } prop',
meta: {
parent: 'parentId'
},
data: {
...
}
...
},
...
];
const tree = R.reduce(
hydrate,
immutable.fromJS({
root: undefined,
index: {},
orphans: {}
}),
entries
);
import { fromJS, Map, List } from 'immutable';
import * as R from 'ramda';
const getPath = (entryId, parentId, parentPath) => {
if (parentId === null) return List([ 'root' ]);
return parentPath
? parentPath.concat([ 'tree', entryId ])
: List([ 'orphans', parentId, entryId ]);
};
const getChildIds = tree => tree.toList(
undefined
).flatMap(
child => getChildIds(
child.get('tree', Map())
).push(
child.get('id')
)
);
const updateIndexes = R.curry(
(orphan, parentPath, index) => getChildIds(
orphan
).reduce(
(index, id) => index.update(
id,
path => parentPath.push(
'tree'
).concat(
path.splice(0, 2)
)
),
index
)
);
const hydrate = (state, entry) => {
const {
id,
meta: {
parent
}
} = entry;
const path = getPath(id, parent, state.getIn([ 'index', parent ]));
const orphan = state.getIn([ 'orphans', id ], Map());
return state.withMutations(
state => state.update(
'index',
Map(),
updateIndexes(orphan, path)
).setIn(
[ 'index', id ], path // Add index
).setIn(
// Add entry
path, fromJS(entry)
).update(
// Move children
state => state.setIn(
path.push('tree'),
orphan
).deleteIn(
[ 'orphans', id ]
)
)
);
};
export default hydrate;