UNPKG

@ember-data/record-data

Version:

Provides the default resource cache (RecordData) implementation for ember-data

156 lines (140 loc) 5.75 kB
import { assert, warn } from '@ember/debug'; import type { ExistingResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; import _normalizeLink from '../../normalize-link'; import type { UpdateRelationshipOperation } from '../-operations'; import { isBelongsTo, isHasMany, notifyChange } from '../-utils'; import type { Graph } from '../graph'; /* Updates the "canonical" or "remote" state of a relationship, replacing any existing state and blowing away any local changes (excepting new records). */ export default function updateRelationshipOperation(graph: Graph, op: UpdateRelationshipOperation) { const relationship = graph.get(op.record, op.field); assert(`Cannot update an implicit relationship`, isHasMany(relationship) || isBelongsTo(relationship)); const { definition, state, identifier } = relationship; const { isCollection } = definition; const payload = op.value; let hasRelationshipDataProperty: boolean = false; let hasUpdatedLink: boolean = false; if (payload.meta) { relationship.meta = payload.meta; } if (payload.data !== undefined) { hasRelationshipDataProperty = true; if (isCollection) { // TODO deprecate this case. We // have tests saying we support it. if (payload.data === null) { payload.data = []; } assert(`Expected an array`, Array.isArray(payload.data)); const cache = graph.store.identifierCache; // TODO may not need to cast to stable identifiers here since update likely does this too graph.update( { op: 'replaceRelatedRecords', record: identifier, field: op.field, value: payload.data.map((i) => cache.getOrCreateRecordIdentifier(i)), }, true ); } else { // TODO may not need to cast to stable identifiers here since update likely does this too graph.update( { op: 'replaceRelatedRecord', record: identifier, field: op.field, value: payload.data ? graph.store.identifierCache.getOrCreateRecordIdentifier(payload.data as ExistingResourceIdentifierObject) : null, }, true ); } } else if (definition.isAsync === false && !state.hasReceivedData) { hasRelationshipDataProperty = true; if (isCollection) { graph.update( { op: 'replaceRelatedRecords', record: identifier, field: op.field, value: [], }, true ); } else { graph.update( { op: 'replaceRelatedRecord', record: identifier, field: op.field, value: null, }, true ); } } if (payload.links) { let originalLinks = relationship.links; relationship.links = payload.links; if (payload.links.related) { let relatedLink = _normalizeLink(payload.links.related); let currentLink = originalLinks && originalLinks.related ? _normalizeLink(originalLinks.related) : null; let currentLinkHref = currentLink ? currentLink.href : null; if (relatedLink && relatedLink.href && relatedLink.href !== currentLinkHref) { warn( `You pushed a record of type '${identifier.type}' with a relationship '${definition.key}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. EmberData will treat this relationship as known-to-be-empty.`, definition.isAsync || state.hasReceivedData, { id: 'ds.store.push-link-for-sync-relationship', } ); assert( `You have pushed a record of type '${identifier.type}' with '${definition.key}' as a link, but the value of that link is not a string.`, typeof relatedLink.href === 'string' || relatedLink.href === null ); hasUpdatedLink = true; } } } /* Data being pushed into the relationship might contain only data or links, or a combination of both. IF contains only data IF contains both links and data state.isEmpty -> true if is empty array (has-many) or is null (belongs-to) state.hasReceivedData -> true hasDematerializedInverse -> false state.isStale -> false allInverseRecordsAreLoaded -> run-check-to-determine IF contains only links state.isStale -> true */ relationship.state.hasFailedLoadAttempt = false; if (hasRelationshipDataProperty) { let relationshipIsEmpty = payload.data === null || (Array.isArray(payload.data) && payload.data.length === 0); // we don't need to notify here as the update op we pushed in above will notify once // membership is in the correct state. relationship.state.hasReceivedData = true; relationship.state.isStale = false; relationship.state.hasDematerializedInverse = false; relationship.state.isEmpty = relationshipIsEmpty; } else if (hasUpdatedLink) { // only notify stale if we have not previously received membership data. // within this same transaction // this prevents refetching when only one side of the relationship in the // payload contains the info while the other side contains just a link // this only works when the side with just a link is a belongsTo, as we // don't know if a hasMany has full information or not. // see #7049 for context. if (isCollection || !relationship.state.hasReceivedData || relationship.transactionRef === 0) { relationship.state.isStale = true; notifyChange(graph, relationship.identifier, relationship.definition.key); } else { relationship.state.isStale = false; } } }