relay-runtime
Version:
A core runtime for building GraphQL-driven applications.
198 lines (176 loc) • 5.73 kB
Flow
/**
* 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 {RecordProxy} from '../store/RelayStoreTypes';
import type {Arguments} from '../store/RelayStoreUtils';
import type {DataID} from '../util/RelayRuntimeTypes';
import type RelayRecordSourceMutator from './RelayRecordSourceMutator';
import type RelayRecordSourceProxy from './RelayRecordSourceProxy';
const {generateClientID} = require('../store/ClientID');
const {getStableStorageKey} = require('../store/RelayStoreUtils');
const invariant = require('invariant');
/**
* @internal
*
* A helper class for manipulating a given record from a record source via an
* imperative/OO-style API.
*/
class RelayRecordProxy implements RecordProxy {
_dataID: DataID;
_mutator: RelayRecordSourceMutator;
_source: RelayRecordSourceProxy;
constructor(
source: RelayRecordSourceProxy,
mutator: RelayRecordSourceMutator,
dataID: DataID,
) {
this._dataID = dataID;
this._mutator = mutator;
this._source = source;
}
copyFieldsFrom(source: RecordProxy): void {
this._mutator.copyFields(source.getDataID(), this._dataID);
}
getDataID(): DataID {
return this._dataID;
}
getType(): string {
const type = this._mutator.getType(this._dataID);
invariant(
type != null,
'RelayRecordProxy: Cannot get the type of deleted record `%s`.',
this._dataID,
);
return type;
}
getValue(name: string, args?: ?Arguments): mixed {
const storageKey = getStableStorageKey(name, args);
return this._mutator.getValue(this._dataID, storageKey);
}
setValue(
value: mixed,
name: string,
args?: ?Arguments,
errors?: ?$ReadOnlyArray<TRelayFieldError>,
): RecordProxy {
invariant(
isValidLeafValue(value),
'RelayRecordProxy#setValue(): Expected a scalar or array of scalars, ' +
'got `%s`.',
JSON.stringify(value),
);
return this.setValue__UNSAFE(value, name, args, errors);
}
getErrors(
name: string,
args?: ?Arguments,
): ?$ReadOnlyArray<TRelayFieldError> {
const storageKey = getStableStorageKey(name, args);
return this._mutator.getErrors(this._dataID, storageKey);
}
// This is used in the typesafe updaters.
// We already validated that the value has the correct type
// so it should be safe to store complex structures as scalar values (custom scalars)
setValue__UNSAFE(
value: mixed,
name: string,
args?: ?Arguments,
errors?: ?$ReadOnlyArray<TRelayFieldError>,
): RecordProxy {
const storageKey = getStableStorageKey(name, args);
this._mutator.setValue(this._dataID, storageKey, value);
if (errors != null) {
if (errors.length === 0) {
this._mutator.setErrors(this._dataID, storageKey);
} else {
this._mutator.setErrors(this._dataID, storageKey, errors);
}
}
return this;
}
getLinkedRecord(name: string, args?: ?Arguments): ?RecordProxy {
const storageKey = getStableStorageKey(name, args);
const linkedID = this._mutator.getLinkedRecordID(this._dataID, storageKey);
return linkedID != null ? this._source.get(linkedID) : linkedID;
}
setLinkedRecord(
record: RecordProxy,
name: string,
args?: ?Arguments,
): RecordProxy {
invariant(
record instanceof RelayRecordProxy,
'RelayRecordProxy#setLinkedRecord(): Expected a record, got `%s`.',
record,
);
const storageKey = getStableStorageKey(name, args);
const linkedID = record.getDataID();
this._mutator.setLinkedRecordID(this._dataID, storageKey, linkedID);
return this;
}
getOrCreateLinkedRecord(
name: string,
typeName: string,
args?: ?Arguments,
): RecordProxy {
let linkedRecord = this.getLinkedRecord(name, args);
if (!linkedRecord) {
const storageKey = getStableStorageKey(name, args);
const clientID = generateClientID(this.getDataID(), storageKey);
// NOTE: it's possible that a client record for this field exists
// but the field itself was unset.
linkedRecord =
this._source.get(clientID) ?? this._source.create(clientID, typeName);
this.setLinkedRecord(linkedRecord, name, args);
}
return linkedRecord;
}
getLinkedRecords(name: string, args?: ?Arguments): ?Array<?RecordProxy> {
const storageKey = getStableStorageKey(name, args);
const linkedIDs = this._mutator.getLinkedRecordIDs(
this._dataID,
storageKey,
);
if (linkedIDs == null) {
return linkedIDs;
}
return linkedIDs.map(linkedID => {
return linkedID != null ? this._source.get(linkedID) : linkedID;
});
}
setLinkedRecords(
records: $ReadOnlyArray<?RecordProxy>,
name: string,
args?: ?Arguments,
): RecordProxy {
invariant(
Array.isArray(records),
'RelayRecordProxy#setLinkedRecords(): Expected records to be an array, got `%s`.',
records,
);
const storageKey = getStableStorageKey(name, args);
const linkedIDs = records.map(record => record && record.getDataID());
this._mutator.setLinkedRecordIDs(this._dataID, storageKey, linkedIDs);
return this;
}
invalidateRecord(): void {
this._source.markIDForInvalidation(this._dataID);
}
}
function isValidLeafValue(value: mixed): boolean {
return (
value == null ||
typeof value !== 'object' ||
(Array.isArray(value) && value.every(isValidLeafValue))
);
}
module.exports = RelayRecordProxy;