UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

263 lines (240 loc) • 7.58 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format * @oncall relay */ 'use strict'; import type {TRelayFieldError} from '../store/RelayErrorTrie'; import type {RecordState} from '../store/RelayRecordState'; import type { MutableRecordSource, Record, RecordSource, } from '../store/RelayStoreTypes'; import type {DataID} from '../util/RelayRuntimeTypes'; const RelayModernRecord = require('../store/RelayModernRecord'); const {EXISTENT} = require('../store/RelayRecordState'); const invariant = require('invariant'); /** * @internal * * Wrapper API that is an amalgam of the `RelayModernRecord` API and * `MutableRecordSource` interface, implementing copy-on-write semantics for records * in a record source. * * Modifications are applied to fresh copies of records: * - Records in `base` are never modified. * - Modifications cause a fresh version of a record to be created in `sink`. * These sink records contain only modified fields. */ class RelayRecordSourceMutator { __sources: Array<RecordSource>; _base: RecordSource; _sink: MutableRecordSource; constructor(base: RecordSource, sink: MutableRecordSource) { this.__sources = [sink, base]; this._base = base; this._sink = sink; } /** * **UNSTABLE** * This method is likely to be removed in an upcoming release * and should not be relied upon. * TODO T41593196: Remove unstable_getRawRecordWithChanges */ unstable_getRawRecordWithChanges(dataID: DataID): ?Record { const baseRecord = this._base.get(dataID); const sinkRecord = this._sink.get(dataID); if (sinkRecord === undefined) { if (baseRecord == null) { return baseRecord; } const nextRecord = RelayModernRecord.clone(baseRecord); if (__DEV__) { // Prevent mutation of a record from outside the store. RelayModernRecord.freeze(nextRecord); } return nextRecord; } else if (sinkRecord === null) { return null; } else if (baseRecord != null) { const nextRecord = RelayModernRecord.update(baseRecord, sinkRecord); if (__DEV__) { if (nextRecord !== baseRecord) { // Prevent mutation of a record from outside the store. RelayModernRecord.freeze(nextRecord); } } return nextRecord; } else { const nextRecord = RelayModernRecord.clone(sinkRecord); if (__DEV__) { // Prevent mutation of a record from outside the store. RelayModernRecord.freeze(nextRecord); } return nextRecord; } } _getSinkRecord(dataID: DataID): Record { let sinkRecord = this._sink.get(dataID); if (!sinkRecord) { const baseRecord = this._base.get(dataID); invariant( baseRecord, 'RelayRecordSourceMutator: Cannot modify non-existent record `%s`.', dataID, ); sinkRecord = RelayModernRecord.create( dataID, RelayModernRecord.getType(baseRecord), ); this._sink.set(dataID, sinkRecord); } return sinkRecord; } copyFields(sourceID: DataID, sinkID: DataID): void { const sinkSource = this._sink.get(sourceID); const baseSource = this._base.get(sourceID); invariant( sinkSource || baseSource, 'RelayRecordSourceMutator#copyFields(): Cannot copy fields from ' + 'non-existent record `%s`.', sourceID, ); const sink = this._getSinkRecord(sinkID); if (baseSource) { RelayModernRecord.copyFields(baseSource, sink); } if (sinkSource) { RelayModernRecord.copyFields(sinkSource, sink); } } copyFieldsFromRecord(record: Record, sinkID: DataID): void { const sink = this._getSinkRecord(sinkID); RelayModernRecord.copyFields(record, sink); } create(dataID: DataID, typeName: string): void { invariant( this._base.getStatus(dataID) !== EXISTENT && this._sink.getStatus(dataID) !== EXISTENT, 'RelayRecordSourceMutator#create(): Cannot create a record with id ' + '`%s`, this record already exists.', dataID, ); const record = RelayModernRecord.create(dataID, typeName); this._sink.set(dataID, record); } delete(dataID: DataID): void { this._sink.delete(dataID); } getStatus(dataID: DataID): RecordState { return this._sink.has(dataID) ? this._sink.getStatus(dataID) : this._base.getStatus(dataID); } getType(dataID: DataID): ?string { for (let ii = 0; ii < this.__sources.length; ii++) { const record = this.__sources[ii].get(dataID); if (record) { return RelayModernRecord.getType(record); } else if (record === null) { return null; } } } getValue(dataID: DataID, storageKey: string): mixed { for (let ii = 0; ii < this.__sources.length; ii++) { const record = this.__sources[ii].get(dataID); if (record) { const value = RelayModernRecord.getValue(record, storageKey); if (value !== undefined) { return value; } } else if (record === null) { return null; } } } setValue(dataID: DataID, storageKey: string, value: mixed): void { const sinkRecord = this._getSinkRecord(dataID); RelayModernRecord.setValue(sinkRecord, storageKey, value); } getErrors( dataID: DataID, storageKey: string, ): ?$ReadOnlyArray<TRelayFieldError> { for (let ii = 0; ii < this.__sources.length; ii++) { const record = this.__sources[ii].get(dataID); if (record) { const value = RelayModernRecord.getErrors(record, storageKey); if (value !== undefined) { return value; } } else if (record === null) { return null; } } } setErrors( dataID: DataID, storageKey: string, errors?: $ReadOnlyArray<TRelayFieldError>, ): void { const sinkRecord = this._getSinkRecord(dataID); RelayModernRecord.setErrors(sinkRecord, storageKey, errors); } getLinkedRecordID(dataID: DataID, storageKey: string): ?DataID { for (let ii = 0; ii < this.__sources.length; ii++) { const record = this.__sources[ii].get(dataID); if (record) { const linkedID = RelayModernRecord.getLinkedRecordID( record, storageKey, ); if (linkedID !== undefined) { return linkedID; } } else if (record === null) { return null; } } } setLinkedRecordID( dataID: DataID, storageKey: string, linkedID: DataID, ): void { const sinkRecord = this._getSinkRecord(dataID); RelayModernRecord.setLinkedRecordID(sinkRecord, storageKey, linkedID); } getLinkedRecordIDs(dataID: DataID, storageKey: string): ?Array<?DataID> { for (let ii = 0; ii < this.__sources.length; ii++) { const record = this.__sources[ii].get(dataID); if (record) { const linkedIDs = RelayModernRecord.getLinkedRecordIDs( record, storageKey, ); if (linkedIDs !== undefined) { return linkedIDs; } } else if (record === null) { return null; } } } setLinkedRecordIDs( dataID: DataID, storageKey: string, linkedIDs: Array<?DataID>, ): void { const sinkRecord = this._getSinkRecord(dataID); RelayModernRecord.setLinkedRecordIDs(sinkRecord, storageKey, linkedIDs); } } module.exports = RelayRecordSourceMutator;