@ember-data/record-data
Version:
Provides the default resource cache (RecordData) implementation for ember-data
138 lines (114 loc) • 4.82 kB
text/typescript
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
import { assertPolymorphicType } from '../../debug/assert-polymorphic-type';
import type { ReplaceRelatedRecordOperation } from '../-operations';
import { isBelongsTo, isNew, notifyChange } from '../-utils';
import type { Graph } from '../graph';
import { addToInverse, notifyInverseOfPotentialMaterialization, removeFromInverse } from './replace-related-records';
export default function replaceRelatedRecord(graph: Graph, op: ReplaceRelatedRecordOperation, isRemote = false) {
const relationship = graph.get(op.record, op.field);
assert(
`You can only '${op.op}' on a belongsTo relationship. ${op.record.type}.${op.field} is a ${relationship.definition.kind}`,
isBelongsTo(relationship)
);
if (isRemote) {
graph._addToTransaction(relationship);
}
const { definition, state } = relationship;
const prop = isRemote ? 'remoteState' : 'localState';
const existingState: StableRecordIdentifier | null = relationship[prop];
/*
case 1:1
========
In a bi-directional graph with 1:1 edges, replacing a value
results in up-to 4 discrete value transitions.
If: A <-> B, C <-> D is the initial state,
and: A <-> C, B, D is the final state
then we would undergo the following 4 transitions.
remove A from B
add C to A
remove C from D
add A to C
case 1:many
===========
In a bi-directional graph with 1:Many edges, replacing a value
results in up-to 3 discrete value transitions.
If: A<->>B<<->D, C<<->D is the initial state (double arrows representing the many side)
And: A<->>C<<->D, B<<->D is the final state
Then we would undergo three transitions.
remove A from B
add C to A.
add A to C
case 1:?
========
In a uni-directional graph with 1:? edges (modeled in EmberData with `inverse:null`) with
artificial (implicit) inverses, replacing a value results in up-to 3 discrete value transitions.
This is because a 1:? relationship is effectively 1:many.
If: A->B, C->B is the initial state
And: A->C, C->B is the final state
Then we would undergo three transitions.
Remove A from B
Add C to A
Add A to C
*/
// nothing for us to do
if (op.value === existingState) {
// if we were empty before but now know we are empty this needs to be true
state.hasReceivedData = true;
// if this is a remote update we still sync
if (isRemote) {
const { localState } = relationship;
// don't sync if localState is a new record and our remoteState is null
if (localState && isNew(localState) && !existingState) {
return;
}
if (existingState && localState === existingState) {
notifyInverseOfPotentialMaterialization(graph, existingState, definition.inverseKey, op.record, isRemote);
} else {
relationship.localState = existingState;
notifyChange(graph, relationship.identifier, relationship.definition.key);
}
}
return;
}
// remove this value from the inverse if required
if (existingState) {
removeFromInverse(graph, existingState, definition.inverseKey, op.record, isRemote);
}
// update value to the new value
relationship[prop] = op.value;
state.hasReceivedData = true;
state.isEmpty = op.value === null;
state.isStale = false;
state.hasFailedLoadAttempt = false;
if (op.value) {
if (definition.type !== op.value.type) {
assert(
`The '<${definition.inverseType}>.${op.field}' relationship expects only '${definition.type}' records since it is not polymorphic. Received a Record of type '${op.value.type}'`,
definition.isPolymorphic
);
// TODO this should now handle the deprecation warning if isPolymorphic is not set
// but the record does turn out to be polymorphic
// this should still assert if the user is relying on legacy inheritance/mixins to
// provide polymorphic behavior and has not yet added the polymorphic flags
if (DEBUG) {
assertPolymorphicType(relationship.identifier, definition, op.value, graph.store);
}
graph.registerPolymorphicType(definition.type, op.value.type);
}
addToInverse(graph, op.value, definition.inverseKey, op.record, isRemote);
}
if (isRemote) {
const { localState, remoteState } = relationship;
if (localState && isNew(localState) && !remoteState) {
return;
}
if (localState !== remoteState) {
relationship.localState = remoteState;
notifyChange(graph, relationship.identifier, relationship.definition.key);
}
} else {
notifyChange(graph, relationship.identifier, relationship.definition.key);
}
}