So I ended up going the denormalization route. I put an array of scenes into the GameCollection.
scenes: [{ id: random_id, next: 'next_id' || [next_ids], <other resource ids and meta> }]
Then I built this monster:
getScene = function (scenes, id) {
return _.find(scenes, function (scene) {
return (scene.id == id)
})
}
getNext = function (scene) {
if (!scene) { return null }
if (scene.type == 'dialogue') {
return scene.next
}
if (scene.type == 'choice') {
return _.pluck(scene.next, 'id')
}
}
scenesDive = function (list, next, container, limit, depth) {
if (!depth) {
depth = 0
}
myDepth = depth + 1
var scene = getScene(list, next)
if (container.indexOf(scene) != -1) { return } //This path has already been added. Go back up.
container.push(scene)
if (myDepth == limit) { return } //Don't dive deeper then this depth.
var nextAoS = getNext(scene) //DIVE! (array or string)
if (_.isArray(nextAoS)) {
nextAoS.forEach(function (n) {
scenesDive(list, n, container, limit, myDepth)
});
} else {
scenesDive(list, nextAoS, container, limit, myDepth)
}
}
I am sure there is a better way but this is what I am going with for now.