UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

373 lines (372 loc) • 18 kB
'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;