in packages/database/src/core/view/ViewProcessor.ts [220:345]
function viewProcessorGenerateEventCacheAfterServerEvent(
viewProcessor: ViewProcessor,
viewCache: ViewCache,
changePath: Path,
writesCache: WriteTreeRef,
source: CompleteChildSource,
accumulator: ChildChangeAccumulator
): ViewCache {
const oldEventSnap = viewCache.eventCache;
if (writeTreeRefShadowingWrite(writesCache, changePath) != null) {
// we have a shadowing write, ignore changes
return viewCache;
} else {
let newEventCache, serverNode;
if (pathIsEmpty(changePath)) {
// TODO: figure out how this plays with "sliding ack windows"
assert(
viewCache.serverCache.isFullyInitialized(),
'If change path is empty, we must have complete server data'
);
if (viewCache.serverCache.isFiltered()) {
// We need to special case this, because we need to only apply writes to complete children, or
// we might end up raising events for incomplete children. If the server data is filtered deep
// writes cannot be guaranteed to be complete
const serverCache = viewCacheGetCompleteServerSnap(viewCache);
const completeChildren =
serverCache instanceof ChildrenNode
? serverCache
: ChildrenNode.EMPTY_NODE;
const completeEventChildren = writeTreeRefCalcCompleteEventChildren(
writesCache,
completeChildren
);
newEventCache = viewProcessor.filter.updateFullNode(
viewCache.eventCache.getNode(),
completeEventChildren,
accumulator
);
} else {
const completeNode = writeTreeRefCalcCompleteEventCache(
writesCache,
viewCacheGetCompleteServerSnap(viewCache)
);
newEventCache = viewProcessor.filter.updateFullNode(
viewCache.eventCache.getNode(),
completeNode,
accumulator
);
}
} else {
const childKey = pathGetFront(changePath);
if (childKey === '.priority') {
assert(
pathGetLength(changePath) === 1,
"Can't have a priority with additional path components"
);
const oldEventNode = oldEventSnap.getNode();
serverNode = viewCache.serverCache.getNode();
// we might have overwrites for this priority
const updatedPriority = writeTreeRefCalcEventCacheAfterServerOverwrite(
writesCache,
changePath,
oldEventNode,
serverNode
);
if (updatedPriority != null) {
newEventCache = viewProcessor.filter.updatePriority(
oldEventNode,
updatedPriority
);
} else {
// priority didn't change, keep old node
newEventCache = oldEventSnap.getNode();
}
} else {
const childChangePath = pathPopFront(changePath);
// update child
let newEventChild;
if (oldEventSnap.isCompleteForChild(childKey)) {
serverNode = viewCache.serverCache.getNode();
const eventChildUpdate =
writeTreeRefCalcEventCacheAfterServerOverwrite(
writesCache,
changePath,
oldEventSnap.getNode(),
serverNode
);
if (eventChildUpdate != null) {
newEventChild = oldEventSnap
.getNode()
.getImmediateChild(childKey)
.updateChild(childChangePath, eventChildUpdate);
} else {
// Nothing changed, just keep the old child
newEventChild = oldEventSnap.getNode().getImmediateChild(childKey);
}
} else {
newEventChild = writeTreeRefCalcCompleteChild(
writesCache,
childKey,
viewCache.serverCache
);
}
if (newEventChild != null) {
newEventCache = viewProcessor.filter.updateChild(
oldEventSnap.getNode(),
childKey,
newEventChild,
childChangePath,
source,
accumulator
);
} else {
// no complete child available or no change
newEventCache = oldEventSnap.getNode();
}
}
}
return viewCacheUpdateEventSnap(
viewCache,
newEventCache,
oldEventSnap.isFullyInitialized() || pathIsEmpty(changePath),
viewProcessor.filter.filtersNodes()
);
}
}