relay-runtime
Version:
A core runtime for building GraphQL-driven applications.
373 lines (372 loc) • 18 kB
JavaScript
'use strict';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"];
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _require = require('../util/stableCopy'),
stableCopy = _require.stableCopy;
var _require2 = require('./ClientID'),
generateClientID = _require2.generateClientID;
var defaultGetDataID = require('./defaultGetDataID');
var RelayModernRecord = require('./RelayModernRecord');
var _require3 = require('./RelayModernSelector'),
createNormalizationSelector = _require3.createNormalizationSelector;
var _require4 = require('./RelayStoreUtils'),
ROOT_ID = _require4.ROOT_ID,
ROOT_TYPE = _require4.ROOT_TYPE,
getStorageKey = _require4.getStorageKey;
function err(message) {
var e = new Error(message);
void e.stack;
return e;
}
function stableStringify(value) {
var _JSON$stringify;
return (_JSON$stringify = JSON.stringify(stableCopy(value))) !== null && _JSON$stringify !== void 0 ? _JSON$stringify : '';
}
var NormalizationEngine = /*#__PURE__*/function () {
function NormalizationEngine(config) {
var _config$operationLoad, _config$getDataID, _config$treatMissingF, _ref, _config$operation$use, _config$operation$exe;
this._normalizeResponse = config.normalizeResponse;
this._operationLoader = (_config$operationLoad = config.operationLoader) !== null && _config$operationLoad !== void 0 ? _config$operationLoad : null;
this._rootSelector = {
dataID: ROOT_ID,
node: config.operation,
variables: config.variables
};
this._options = {
deferDeduplicatedFields: false,
getDataID: (_config$getDataID = config.getDataID) !== null && _config$getDataID !== void 0 ? _config$getDataID : defaultGetDataID,
log: null,
path: [],
treatMissingFieldsAsNull: (_config$treatMissingF = config.treatMissingFieldsAsNull) !== null && _config$treatMissingF !== void 0 ? _config$treatMissingF : false
};
this._useExecTimeResolvers = (_ref = (_config$operation$use = config.operation.use_exec_time_resolvers) !== null && _config$operation$use !== void 0 ? _config$operation$use : ((_config$operation$exe = config.operation.exec_time_resolvers_enabled_provider) === null || _config$operation$exe === void 0 ? void 0 : _config$operation$exe.get()) === true) !== null && _ref !== void 0 ? _ref : false;
this._placeholders = new Map();
this._bufferedResponses = new Map();
this._parentRecords = new Map();
this._serverComplete = false;
}
var _proto = NormalizationEngine.prototype;
_proto.processResponse = function processResponse(response) {
var payload = this._normalizeResponse(response, this._rootSelector, ROOT_TYPE, this._options, this._useExecTimeResolvers);
var extraPayloads = [];
var pendingModules = [];
if (payload.incrementalPlaceholders != null && payload.incrementalPlaceholders.length > 0) {
this._registerPlaceholders(payload.incrementalPlaceholders, payload.source, payload.fieldPayloads, extraPayloads, pendingModules);
}
if (payload.followupPayloads != null && payload.followupPayloads.length > 0) {
this._processFollowups(payload.followupPayloads, extraPayloads, pendingModules);
}
var primaryPayload = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, payload), {}, {
followupPayloads: null,
incrementalPlaceholders: null,
isFinal: this._computeIsFinal(payload.isFinal),
isPreNormalized: true
});
return {
payloads: [primaryPayload].concat(extraPayloads),
pendingModules: pendingModules
};
};
_proto.processIncrementalResponse = function processIncrementalResponse(response) {
var label = response.label;
var path = response.path;
if (label == null || path == null) {
throw err('NormalizationEngine: Expected incremental response to have ' + '`label` and `path` properties.');
}
var isDefer = label.indexOf('$defer$') !== -1;
var pathKey = isDefer ? path.map(String).join('.') : path.slice(0, -2).map(String).join('.');
var key = makeKey(label, pathKey);
var placeholder = this._placeholders.get(key);
if (placeholder == null) {
var buffer = this._bufferedResponses.get(key);
if (buffer == null) {
buffer = [];
this._bufferedResponses.set(key, buffer);
}
buffer.push(response);
return null;
}
if (placeholder.kind === 'defer') {
return this._processDefer(response, path, placeholder);
} else {
return this._processStream(response, path, placeholder);
}
};
_proto.setServerComplete = function setServerComplete() {
this._serverComplete = true;
};
_proto.isFinal = function isFinal() {
return this._serverComplete && this._bufferedResponses.size === 0 && this._placeholders.size === 0;
};
_proto._processDefer = function _processDefer(response, _path, placeholder) {
var payload = this._normalizeResponse(response, placeholder.selector, placeholder.typeName, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, this._options), {}, {
deferDeduplicatedFields: true,
path: placeholder.path
}), this._useExecTimeResolvers);
var extraPayloads = [];
var pendingModules = [];
if (payload.incrementalPlaceholders != null && payload.incrementalPlaceholders.length > 0) {
this._registerPlaceholders(payload.incrementalPlaceholders, payload.source, payload.fieldPayloads, extraPayloads, pendingModules);
}
if (payload.followupPayloads != null && payload.followupPayloads.length > 0) {
this._processFollowups(payload.followupPayloads, extraPayloads, pendingModules);
}
var parentID = placeholder.selector.dataID;
var parentEntry = this._parentRecords.get(parentID);
var fieldPayloads = payload.fieldPayloads;
if (parentEntry != null && parentEntry.fieldPayloads.length > 0) {
var _fieldPayloads;
fieldPayloads = ((_fieldPayloads = fieldPayloads) !== null && _fieldPayloads !== void 0 ? _fieldPayloads : []).concat(parentEntry.fieldPayloads);
}
var primaryPayload = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, payload), {}, {
fieldPayloads: fieldPayloads,
followupPayloads: null,
incrementalPlaceholders: null,
isFinal: this._computeIsFinal(payload.isFinal),
isPreNormalized: true
});
return {
payloads: [primaryPayload].concat(extraPayloads),
pendingModules: pendingModules
};
};
_proto._processStream = function _processStream(response, path, placeholder) {
var node = placeholder.node,
parentID = placeholder.parentID,
variables = placeholder.variables;
var field = node.selections[0];
if (field == null || field.kind !== 'LinkedField' || field.plural !== true) {
throw err('NormalizationEngine: Expected @stream to be used on a plural field.');
}
var _this$_normalizeStrea = this._normalizeStreamItem(response, parentID, field, variables, path, placeholder.path),
fieldPayloads = _this$_normalizeStrea.fieldPayloads,
itemID = _this$_normalizeStrea.itemID,
itemIndex = _this$_normalizeStrea.itemIndex,
prevIDs = _this$_normalizeStrea.prevIDs,
relayPayload = _this$_normalizeStrea.relayPayload,
storageKey = _this$_normalizeStrea.storageKey;
var storeUpdater = function storeUpdater(store) {
var currentParentRecord = store.get(parentID);
if (currentParentRecord == null) {
return;
}
var currentItems = currentParentRecord.getLinkedRecords(storageKey);
if (currentItems == null) {
return;
}
if (currentItems.length !== prevIDs.length || currentItems.some(function (item, i) {
return prevIDs[i] !== (item && item.getDataID());
})) {
return;
}
var nextItems = (0, _toConsumableArray2["default"])(currentItems);
nextItems[itemIndex] = store.get(itemID);
currentParentRecord.setLinkedRecords(nextItems, storageKey);
};
var mergedFieldPayloads = relayPayload.fieldPayloads;
if (fieldPayloads.length > 0) {
var _mergedFieldPayloads;
mergedFieldPayloads = ((_mergedFieldPayloads = mergedFieldPayloads) !== null && _mergedFieldPayloads !== void 0 ? _mergedFieldPayloads : []).concat(fieldPayloads);
}
return {
payloads: [(0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, relayPayload), {}, {
fieldPayloads: mergedFieldPayloads,
followupPayloads: null,
incrementalPlaceholders: null,
isFinal: this._computeIsFinal(relayPayload.isFinal),
isPreNormalized: true,
storeUpdater: storeUpdater
})],
pendingModules: []
};
};
_proto._normalizeStreamItem = function _normalizeStreamItem(response, parentID, field, variables, path, normalizationPath) {
var _field$alias, _field$concreteType, _ref2, _ref3;
var data = response.data;
if (typeof data !== 'object') {
throw err('NormalizationEngine: Expected the GraphQL @stream payload `data` ' + 'value to be an object.');
}
var responseKey = (_field$alias = field.alias) !== null && _field$alias !== void 0 ? _field$alias : field.name;
var storageKey = getStorageKey(field, variables);
var parentEntry = this._parentRecords.get(parentID);
if (parentEntry == null) {
throw err('NormalizationEngine: Expected the parent record `' + parentID + '` for @stream data to exist.');
}
var fieldPayloads = parentEntry.fieldPayloads,
parentRecord = parentEntry.record;
var prevIDs = RelayModernRecord.getLinkedRecordIDs(parentRecord, storageKey);
if (prevIDs == null) {
throw err('NormalizationEngine: Expected record `' + parentID + '` to have fetched field `' + field.name + '` with @stream.');
}
var finalPathEntry = path[path.length - 1];
var itemIndex = parseInt(finalPathEntry, 10);
if (itemIndex !== finalPathEntry || itemIndex < 0) {
throw err('NormalizationEngine: Expected path for @stream to end in a ' + 'positive integer index, got `' + String(finalPathEntry) + '`');
}
var typeName = (_field$concreteType = field.concreteType) !== null && _field$concreteType !== void 0 ? _field$concreteType : data.__typename;
if (typeof typeName !== 'string') {
throw err('NormalizationEngine: Expected @stream field `' + field.name + '` to have a __typename.');
}
var getDataID = this._options.getDataID;
var itemID = (_ref2 = (_ref3 = typeof getDataID === 'function' ? getDataID(data, typeName) : null) !== null && _ref3 !== void 0 ? _ref3 : prevIDs === null || prevIDs === void 0 ? void 0 : prevIDs[itemIndex]) !== null && _ref2 !== void 0 ? _ref2 : generateClientID(parentID, storageKey, itemIndex);
if (typeof itemID !== 'string') {
throw err('NormalizationEngine: Expected id of elements of field `' + storageKey + '` to be strings.');
}
var selector = createNormalizationSelector(field, itemID, variables);
var nextParentRecord = RelayModernRecord.clone(parentRecord);
var nextIDs = (0, _toConsumableArray2["default"])(prevIDs);
nextIDs[itemIndex] = itemID;
RelayModernRecord.setLinkedRecordIDs(nextParentRecord, storageKey, nextIDs);
this._parentRecords.set(parentID, {
fieldPayloads: fieldPayloads,
record: nextParentRecord
});
var relayPayload = this._normalizeResponse(response, selector, typeName, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, this._options), {}, {
path: [].concat((0, _toConsumableArray2["default"])(normalizationPath), [responseKey, String(itemIndex)])
}), this._useExecTimeResolvers);
return {
fieldPayloads: fieldPayloads,
itemID: itemID,
itemIndex: itemIndex,
prevIDs: prevIDs,
relayPayload: relayPayload,
storageKey: storageKey
};
};
_proto._registerPlaceholders = function _registerPlaceholders(placeholders, source, fieldPayloads, outPayloads, outPendingModules) {
var _this = this;
var _loop = function _loop(i) {
var placeholder = placeholders[i];
var label = placeholder.label,
path = placeholder.path;
var pathKey = path.map(String).join('.');
var key = makeKey(label, pathKey);
_this._placeholders.set(key, placeholder);
var parentID = void 0;
if (placeholder.kind === 'stream') {
parentID = placeholder.parentID;
} else {
parentID = placeholder.selector.dataID;
}
var parentRecord = source.get(parentID);
if (parentRecord == null) {
throw err('NormalizationEngine: Expected record `' + parentID + '` to exist.');
}
var parentPayloads = (fieldPayloads !== null && fieldPayloads !== void 0 ? fieldPayloads : []).filter(function (fieldPayload) {
var fieldID = generateClientID(fieldPayload.dataID, fieldPayload.fieldKey);
return fieldPayload.dataID === parentID || fieldID === parentID;
});
var previousEntry = _this._parentRecords.get(parentID);
if (previousEntry != null) {
var nextRecord = RelayModernRecord.update(previousEntry.record, parentRecord);
var handlePayloads = new Map();
for (var j = 0; j < previousEntry.fieldPayloads.length; j++) {
var p = previousEntry.fieldPayloads[j];
handlePayloads.set(stableStringify(p), p);
}
for (var _j = 0; _j < parentPayloads.length; _j++) {
var _p = parentPayloads[_j];
handlePayloads.set(stableStringify(_p), _p);
}
_this._parentRecords.set(parentID, {
fieldPayloads: Array.from(handlePayloads.values()),
record: nextRecord
});
} else {
_this._parentRecords.set(parentID, {
fieldPayloads: parentPayloads,
record: parentRecord
});
}
var buffered = _this._bufferedResponses.get(key);
if (buffered != null) {
_this._bufferedResponses["delete"](key);
for (var _j2 = 0; _j2 < buffered.length; _j2++) {
var resp = buffered[_j2];
var result = void 0;
if (placeholder.kind === 'defer') {
result = _this._processDefer(resp, (_resp$path = resp.path) !== null && _resp$path !== void 0 ? _resp$path : [], placeholder);
} else {
result = _this._processStream(resp, (_resp$path2 = resp.path) !== null && _resp$path2 !== void 0 ? _resp$path2 : [], placeholder);
}
if (result != null) {
outPayloads.push.apply(outPayloads, (0, _toConsumableArray2["default"])(result.payloads));
outPendingModules.push.apply(outPendingModules, (0, _toConsumableArray2["default"])(result.pendingModules));
}
}
}
};
for (var i = 0; i < placeholders.length; i++) {
var _resp$path;
var _resp$path2;
_loop(i);
}
};
_proto._processFollowups = function _processFollowups(followups, outPayloads, outPendingModules) {
for (var i = 0; i < followups.length; i++) {
var followup = followups[i];
if (followup.kind === 'ModuleImportPayload') {
this._processModuleImport(followup, outPayloads, outPendingModules);
}
}
};
_proto._processModuleImport = function _processModuleImport(followup, outPayloads, outPendingModules) {
var _this2 = this;
var operationLoader = this._operationLoader;
if (operationLoader == null) {
return;
}
var node = operationLoader.get(followup.operationReference);
if (node != null) {
var result = this._normalizeFollowup(followup, node);
outPayloads.push.apply(outPayloads, (0, _toConsumableArray2["default"])(result.payloads));
outPendingModules.push.apply(outPendingModules, (0, _toConsumableArray2["default"])(result.pendingModules));
return;
}
var emptyResult = {
payloads: [],
pendingModules: []
};
outPendingModules.push(operationLoader.load(followup.operationReference).then(function (loadedNode) {
return loadedNode != null ? _this2._normalizeFollowup(followup, loadedNode) : emptyResult;
})["catch"](function (_error) {
return emptyResult;
}));
};
_proto._normalizeFollowup = function _normalizeFollowup(followup, node) {
var operationNode = node.kind === 'SplitOperation' ? node : node.operation;
var selector = createNormalizationSelector(operationNode, followup.dataID, followup.variables);
var payload = this._normalizeResponse({
data: followup.data
}, selector, followup.typeName, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, this._options), {}, {
path: followup.path
}), this._useExecTimeResolvers);
var extraPayloads = [];
var pendingModules = [];
if (payload.incrementalPlaceholders != null && payload.incrementalPlaceholders.length > 0) {
this._registerPlaceholders(payload.incrementalPlaceholders, payload.source, payload.fieldPayloads, extraPayloads, pendingModules);
}
if (payload.followupPayloads != null && payload.followupPayloads.length > 0) {
this._processFollowups(payload.followupPayloads, extraPayloads, pendingModules);
}
var primaryPayload = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, payload), {}, {
followupPayloads: null,
incrementalPlaceholders: null,
isFinal: this._computeIsFinal(payload.isFinal),
isPreNormalized: true
});
return {
payloads: [primaryPayload].concat(extraPayloads),
pendingModules: pendingModules
};
};
_proto._computeIsFinal = function _computeIsFinal(serverIsFinal) {
return (serverIsFinal || this._serverComplete) && this._bufferedResponses.size === 0;
};
return NormalizationEngine;
}();
function makeKey(label, pathKey) {
return "".concat(label, "::").concat(pathKey);
}
module.exports = NormalizationEngine;