relay-hooks
Version:
609 lines (471 loc) • 23.4 kB
JavaScript
"use strict";
var __assign = this && this.__assign || function () {
__assign = Object.assign || function (t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FragmentResolver = void 0;
var areEqual = require("fbjs/lib/areEqual");
var invariant = require("fbjs/lib/invariant");
var warning = require("fbjs/lib/warning");
var relay_runtime_1 = require("relay-runtime");
var FetchResolver_1 = require("./FetchResolver");
var getConnectionState_1 = require("./getConnectionState");
var RelayHooksTypes_1 = require("./RelayHooksTypes");
var Utils_1 = require("./Utils");
var getPromiseForActiveRequest = relay_runtime_1.__internal.getPromiseForActiveRequest; // eslint-disable-next-line @typescript-eslint/no-empty-function
function emptyVoid() {}
function lookupFragment(environment, selector) {
return selector.kind === 'PluralReaderSelector' ? selector.selectors.map(function (s) {
return environment.lookup(s);
}) : environment.lookup(selector);
}
function getFragmentResult(snapshot) {
var missData = isMissingData(snapshot);
if (Array.isArray(snapshot)) {
return {
snapshot: snapshot,
data: snapshot.map(function (s) {
return s.data;
}),
isMissingData: missData
};
}
return {
snapshot: snapshot,
data: snapshot.data,
isMissingData: missData
};
}
function isMissingData(snapshot) {
if (Array.isArray(snapshot)) {
return snapshot.some(function (s) {
return s.isMissingData;
});
}
return snapshot.isMissingData;
}
function _getAndSavePromiseForFragmentRequestInFlight(fragmentNode, fragmentOwner, env) {
var _a, _b;
var networkPromise = getPromiseForActiveRequest(env, fragmentOwner);
var pendingOperationName;
if (networkPromise != null) {
pendingOperationName = fragmentOwner.node.params.name;
} else {
var result = env.getOperationTracker().getPendingOperationsAffectingOwner(fragmentOwner);
var pendingOperations = result === null || result === void 0 ? void 0 : result.pendingOperations;
networkPromise = (_a = result === null || result === void 0 ? void 0 : result.promise) !== null && _a !== void 0 ? _a : null;
pendingOperationName = (_b = pendingOperations === null || pendingOperations === void 0 ? void 0 : pendingOperations.map(function (op) {
return op.node.params.name;
}).join(',')) !== null && _b !== void 0 ? _b : null;
}
if (!networkPromise) {
return null;
}
if (pendingOperationName == null || pendingOperationName.length === 0) {
pendingOperationName = 'Unknown pending operation';
} // When the Promise for the request resolves, we need to make sure to
// update the cache with the latest data available in the store before
// resolving the Promise
var fragmentName = fragmentNode.name;
var promiseDisplayName = pendingOperationName === fragmentName ? "Relay(".concat(pendingOperationName, ")") : "Relay(".concat(pendingOperationName, ":").concat(fragmentName, ")");
networkPromise.displayName = promiseDisplayName;
return networkPromise;
}
var FragmentResolver =
/** @class */
function () {
function FragmentResolver(name) {
var _this = this;
this.unmounted = false;
this.refetchable = false;
this.pagination = false;
this.refetch = function (variables, options) {
var _a, _b, _c, _d;
if (options === void 0) {
options = {};
}
var name = _this.name;
if (_this.unmounted === true) {
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `refetch` on unmounted component for fragment ' + '`%s` in `%s`. It looks like some instances of your component are ' + 'still trying to fetch data but they already unmounted. ' + 'Please make sure you clear all timers, intervals, ' + 'async calls, etc that may trigger a fetch.', _this._fragment.name, name) : void 0;
return {
dispose: emptyVoid
};
}
if (_this._selector == null) {
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected call to `refetch` while using a null fragment ref ' + 'for fragment `%s` in `%s`. When calling `refetch`, we expect ' + "initial fragment data to be non-null. Please make sure you're " + 'passing a valid fragment ref to `%s` before calling ' + '`refetch`, or make sure you pass all required variables to `refetch`.', _this._fragment.name, name, name) : void 0;
}
var _e = (0, relay_runtime_1.getRefetchMetadata)(_this._fragment, name),
fragmentRefPathInResponse = _e.fragmentRefPathInResponse,
identifierInfo = _e.identifierInfo,
refetchableRequest = _e.refetchableRequest;
var fragmentData = _this.getData().data;
var identifierValue = (identifierInfo === null || identifierInfo === void 0 ? void 0 : identifierInfo.identifierField) != null && fragmentData != null && typeof fragmentData === 'object' ? fragmentData[identifierInfo.identifierField] : null;
var parentVariables;
var fragmentVariables;
if (_this._selector == null) {
parentVariables = {};
fragmentVariables = {};
} else if (_this._selector.kind === 'PluralReaderSelector') {
parentVariables = (_b = (_a = _this._selector.selectors[0]) === null || _a === void 0 ? void 0 : _a.owner.variables) !== null && _b !== void 0 ? _b : {};
fragmentVariables = (_d = (_c = _this._selector.selectors[0]) === null || _c === void 0 ? void 0 : _c.variables) !== null && _d !== void 0 ? _d : {};
} else {
parentVariables = _this._selector.owner.variables;
fragmentVariables = _this._selector.variables;
} // NOTE: A user of `useRefetchableFragment()` may pass a subset of
// all variables required by the fragment when calling `refetch()`.
// We fill in any variables not passed by the call to `refetch()` with the
// variables from the original parent fragment owner.
/* $FlowFixMe[cannot-spread-indexer] (>=0.123.0) This comment suppresses
* an error found when Flow v0.123.0 was deployed. To see the error
* delete this comment and run Flow. */
var refetchVariables = __assign(__assign(__assign({}, parentVariables), fragmentVariables), variables);
if (identifierInfo != null && !variables.hasOwnProperty(identifierInfo.identifierQueryVariableName)) {
// @refetchable fragments are guaranteed to have an `id` selection
// if the type is Node, implements Node, or is @fetchable. Double-check
// that there actually is a value at runtime.
if (typeof identifierValue !== 'string') {
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected result to have a string ' + '`%s` in order to refetch, got `%s`.', identifierInfo, identifierValue) : void 0;
}
refetchVariables[identifierInfo.identifierQueryVariableName] = identifierValue;
}
var onNext = function (operation, snapshot, doUpdate) {
var fragmentRef = (0, relay_runtime_1.getValueAtPath)(snapshot.data, fragmentRefPathInResponse);
var isEquals = _this.isEqualsFragmentRef(_this._fragmentRefRefetch || _this._fragmentRef, fragmentRef);
var missData = isMissingData(snapshot); //fromStore && isMissingData(snapshot);
if (!isEquals || missData) {
_this._fragmentRefRefetch = fragmentRef;
_this._idfragmentrefetch = (0, relay_runtime_1.getFragmentIdentifier)(_this._fragment, fragmentRef);
_this.lookup(_this._fragment, fragmentRef);
_this.subscribe();
/*if (!missData) {
this.subscribe();
}*/
_this.resolverData.isMissingData = missData;
_this.resolverData.owner = operation.request;
doUpdate && _this.refreshHooks();
}
};
if (_this.pagination) {
_this.fetcherNext.dispose();
_this.fetcherPrevious.dispose();
}
var complete = function (error, doUpdate) {
doUpdate && _this.refreshHooks();
options.onComplete && options.onComplete(error);
};
var operation = (0, Utils_1.createOperation)(refetchableRequest, refetchVariables, Utils_1.forceCache);
var disposable = _this.fetcherRefecth.fetch(_this._environment, operation, options.fetchPolicy, complete, onNext, options.onResponse, options.UNSTABLE_renderPolicy);
_this.refreshHooks();
return disposable;
};
this.loadPrevious = function (count, options) {
return _this.loadMore('backward', count, options);
};
this.loadNext = function (count, options) {
return _this.loadMore('forward', count, options);
};
this.loadMore = function (direction, count, options) {
var _a;
if (options === void 0) {
options = {};
}
var onComplete = (_a = options.onComplete) !== null && _a !== void 0 ? _a : emptyVoid;
var fragmentData = _this.getData().data;
var emptyDispose = {
dispose: emptyVoid
};
var fetcher = direction === 'backward' ? _this.fetcherPrevious : _this.fetcherNext;
if (_this.unmounted === true) {
// Bail out and warn if we're trying to paginate after the component
// has unmounted
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected fetch on unmounted component for fragment ' + '`%s` in `%s`. It looks like some instances of your component are ' + 'still trying to fetch data but they already unmounted. ' + 'Please make sure you clear all timers, intervals, ' + 'async calls, etc that may trigger a fetch.', _this._fragment.name, _this.name) : void 0;
return emptyDispose;
}
if (_this._selector == null) {
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Unexpected fetch while using a null fragment ref ' + 'for fragment `%s` in `%s`. When fetching more items, we expect ' + "initial fragment data to be non-null. Please make sure you're " + 'passing a valid fragment ref to `%s` before paginating.', _this._fragment.name, _this.name, _this.name) : void 0;
onComplete(null);
return emptyDispose;
}
var isRequestActive = _this._environment.isRequestActive(_this._selector.owner.identifier);
if (isRequestActive || fetcher.getData().isLoading === true || fragmentData == null) {
onComplete(null);
return emptyDispose;
}
!(_this._selector != null && _this._selector.kind !== 'PluralReaderSelector') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected to be able to find a non-plural fragment owner for ' + "fragment `%s` when using `%s`. If you're seeing this, " + 'this is likely a bug in Relay.', _this._fragment.name, _this.name) : invariant(false) : void 0;
var _b = (0, relay_runtime_1.getPaginationMetadata)(_this._fragment, _this.name),
paginationRequest = _b.paginationRequest,
paginationMetadata = _b.paginationMetadata,
connectionPathInFragmentData = _b.connectionPathInFragmentData;
var identifierInfo = (0, relay_runtime_1.getRefetchMetadata)(_this._fragment, _this._fragment.name).identifierInfo;
var identifierValue = identifierInfo != null && fragmentData != null && typeof fragmentData === 'object' ? fragmentData[identifierInfo.identifierField] : null;
var parentVariables = _this._selector.owner.variables;
var fragmentVariables = _this._selector.variables;
var extraVariables = options.UNSTABLE_extraVariables;
var baseVariables = __assign(__assign({}, parentVariables), fragmentVariables);
var cursor = (0, getConnectionState_1.getConnectionState)(direction, _this._fragment, fragmentData, connectionPathInFragmentData).cursor;
var paginationVariables = (0, relay_runtime_1.getPaginationVariables)(direction, count, cursor, baseVariables, __assign({}, extraVariables), paginationMetadata); // If the query needs an identifier value ('id' or similar) and one
// was not explicitly provided, read it from the fragment data.
if (identifierInfo != null) {
// @refetchable fragments are guaranteed to have an `id` selection
// if the type is Node, implements Node, or is @fetchable. Double-check
// that there actually is a value at runtime.
if (typeof identifierValue !== 'string') {
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected result to have a string ' + '`%s` in order to refetch, got `%s`.', identifierInfo, identifierValue) : void 0;
}
paginationVariables[identifierInfo.identifierQueryVariableName] = identifierValue;
}
var complete = function (error, doUpdate) {
if (doUpdate) _this.refreshHooks();
onComplete(error);
};
var operation = (0, Utils_1.createOperation)(paginationRequest, paginationVariables, Utils_1.forceCache);
var disposable = fetcher.fetch(_this._environment, operation, undefined, //options?.fetchPolicy,
complete, emptyVoid, options.onResponse);
_this.refreshHooks();
return disposable;
};
this.name = name;
this.pagination = name === RelayHooksTypes_1.PAGINATION_NAME;
this.refetchable = name === RelayHooksTypes_1.REFETCHABLE_NAME || this.pagination;
if (this.refetchable) {
this.fetcherRefecth = (0, FetchResolver_1.fetchResolver)({
doRetain: true
});
}
if (this.pagination) {
this.fetcherNext = (0, FetchResolver_1.fetchResolver)({});
this.fetcherPrevious = (0, FetchResolver_1.fetchResolver)({});
}
this.setForceUpdate();
this.refreshHooks = function () {
_this.resolveResult();
_this.forceUpdate();
};
}
FragmentResolver.prototype.setForceUpdate = function (forceUpdate) {
if (forceUpdate === void 0) {
forceUpdate = emptyVoid;
}
this.forceUpdate = forceUpdate;
};
FragmentResolver.prototype.subscribeResolve = function (subscribeResolve) {
if (this._subscribeResolve && this._subscribeResolve != subscribeResolve) {
subscribeResolve(this.getData());
}
this._subscribeResolve = subscribeResolve;
};
FragmentResolver.prototype.setUnmounted = function () {
this.unmounted = true;
};
FragmentResolver.prototype.isEqualsFragmentRef = function (prevFragment, fragmentRef) {
if (this._fragmentRef !== fragmentRef) {
var prevIDs = (0, relay_runtime_1.getDataIDsFromFragment)(this._fragment, prevFragment);
var nextIDs = (0, relay_runtime_1.getDataIDsFromFragment)(this._fragment, fragmentRef);
if (!areEqual(prevIDs, nextIDs) || !areEqual(this.getFragmentVariables(fragmentRef), this.getFragmentVariables(prevFragment))) {
return false;
}
}
return true;
};
FragmentResolver.prototype.dispose = function () {
this.unsubscribe();
this.fetcherNext && this.fetcherNext.dispose();
this.fetcherPrevious && this.fetcherPrevious.dispose();
this._idfragmentrefetch = null;
this._fragmentRefRefetch = null;
this.fetcherRefecth && this.fetcherRefecth.dispose();
};
FragmentResolver.prototype.getFragmentVariables = function (fRef) {
if (fRef === void 0) {
fRef = this._fragmentRef;
}
return (0, relay_runtime_1.getVariablesFromFragment)(this._fragment, fRef);
};
FragmentResolver.prototype.resolve = function (environment, idfragment, fragment, fragmentRef) {
if (!this.resolverData || this._environment !== environment || idfragment !== this._idfragment && (!this._idfragmentrefetch || this._idfragmentrefetch && idfragment !== this._idfragmentrefetch)) {
this._fragment = fragment;
this._fragmentRef = fragmentRef;
this._idfragment = idfragment;
this._selector = null;
this.dispose();
this._environment = environment;
this.lookup(fragment, this._fragmentRef);
this.resolveResult();
}
};
FragmentResolver.prototype.lookup = function (fragment, fragmentRef) {
if (fragmentRef == null) {
this.resolverData = {
data: null
};
return;
}
var isPlural = fragment.metadata && fragment.metadata.plural && fragment.metadata.plural === true;
if (isPlural) {
if (fragmentRef.length === 0) {
this.resolverData = {
data: []
};
return;
}
}
this._selector = (0, relay_runtime_1.getSelector)(fragment, fragmentRef);
var snapshot = lookupFragment(this._environment, this._selector);
this.resolverData = getFragmentResult(snapshot);
var owner = this._selector ? this._selector.kind === 'PluralReaderSelector' ? this._selector.selectors[0].owner : this._selector.owner : null;
this.resolverData.owner = owner; //this.subscribe();
};
FragmentResolver.prototype.checkAndSuspense = function (suspense) {
var _this = this;
var _a;
if (suspense && this.resolverData.isMissingData && this.resolverData.owner) {
var fragmentOwner = this.resolverData.owner;
var networkPromise = _getAndSavePromiseForFragmentRequestInFlight(this._fragment, fragmentOwner, this._environment);
var parentQueryName = (_a = fragmentOwner.node.params.name) !== null && _a !== void 0 ? _a : 'Unknown Parent Query';
if (networkPromise != null) {
// When the Promise for the request resolves, we need to make sure to
// update the cache with the latest data available in the store before
// resolving the Promise
var promise = networkPromise.then(function () {
if (_this._idfragmentrefetch) {
_this.resolveResult();
} else {
_this._idfragment = null;
_this.dispose();
} //;
}).catch(function (_error) {
if (_this._idfragmentrefetch) {
_this.resolveResult();
} else {
_this._idfragment = null;
_this.dispose();
}
}); // $FlowExpectedError[prop-missing] Expando to annotate Promises.
promise.displayName = 'Relay(' + parentQueryName + ')';
this.unsubscribe();
this.refreshHooks = emptyVoid;
throw promise;
}
process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Tried reading fragment `%s` declared in ' + '`%s`, but it has missing data and its parent query `%s` is not ' + 'being fetched.\n' + 'This might be fixed by by re-running the Relay Compiler. ' + ' Otherwise, make sure of the following:\n' + '* You are correctly fetching `%s` if you are using a ' + '"store-only" `fetchPolicy`.\n' + "* Other queries aren't accidentally fetching and overwriting " + 'the data for this fragment.\n' + '* Any related mutations or subscriptions are fetching all of ' + 'the data for this fragment.\n' + "* Any related store updaters aren't accidentally deleting " + 'data for this fragment.', this._fragment.name, this.name, parentQueryName, parentQueryName) : void 0;
}
this.fetcherRefecth && this.fetcherRefecth.checkAndSuspense(suspense);
};
FragmentResolver.prototype.getData = function () {
return this.result;
};
FragmentResolver.prototype.resolveResult = function () {
var data = this.resolverData.data;
if (this.refetchable || this.pagination) {
var _a = this.fetcherRefecth.getData(),
isLoading = _a.isLoading,
error = _a.error;
var refetch = this.refetch;
if (!this.pagination) {
// useRefetchable
if ('production' !== process.env.NODE_ENV) {
(0, relay_runtime_1.getRefetchMetadata)(this._fragment, this.name);
}
this.result = {
data: data,
isLoading: isLoading,
error: error,
refetch: refetch
};
} else {
// usePagination
var connectionPathInFragmentData = (0, relay_runtime_1.getPaginationMetadata)(this._fragment, this.name).connectionPathInFragmentData;
var connection = (0, relay_runtime_1.getValueAtPath)(data, connectionPathInFragmentData);
var hasNext = (0, getConnectionState_1.getStateFromConnection)('forward', this._fragment, connection).hasMore;
var hasPrevious = (0, getConnectionState_1.getStateFromConnection)('backward', this._fragment, connection).hasMore;
var _b = this.fetcherNext.getData(),
isLoadingNext = _b.isLoading,
errorNext = _b.error;
var _c = this.fetcherPrevious.getData(),
isLoadingPrevious = _c.isLoading,
errorPrevious = _c.error;
this.result = {
data: data,
hasNext: hasNext,
isLoadingNext: isLoadingNext,
hasPrevious: hasPrevious,
isLoadingPrevious: isLoadingPrevious,
isLoading: isLoading,
errorNext: errorNext,
errorPrevious: errorPrevious,
error: error,
refetch: refetch,
loadNext: this.loadNext,
loadPrevious: this.loadPrevious
};
}
} else {
// useFragment
this.result = data;
}
var snap = this.resolverData.snapshot;
if (snap) {
this._throwOrLogErrorsInSnapshot(snap);
}
this._subscribeResolve && this._subscribeResolve(this.result);
};
FragmentResolver.prototype.unsubscribe = function () {
this._disposable && this._disposable.dispose();
};
FragmentResolver.prototype.subscribe = function () {
var _this = this;
var environment = this._environment;
var renderedSnapshot = this.resolverData.snapshot;
this.unsubscribe();
var dataSubscriptions = [];
if (renderedSnapshot) {
if (Array.isArray(renderedSnapshot)) {
renderedSnapshot.forEach(function (snapshot, idx) {
dataSubscriptions.push(environment.subscribe(snapshot, function (latestSnapshot) {
_this.resolverData.snapshot[idx] = latestSnapshot;
_this.resolverData.data[idx] = latestSnapshot.data;
_this.resolverData.isMissingData = isMissingData(_this.resolverData.snapshot);
_this.refreshHooks();
}));
});
} else {
dataSubscriptions.push(environment.subscribe(renderedSnapshot, function (latestSnapshot) {
_this.resolverData = getFragmentResult(latestSnapshot);
_this.refreshHooks();
}));
}
}
this._disposable = {
dispose: function () {
dataSubscriptions.map(function (s) {
return s.dispose();
});
_this._disposable = undefined;
}
};
};
FragmentResolver.prototype._throwOrLogErrorsInSnapshot = function (snapshot) {
var _this = this;
if (Array.isArray(snapshot)) {
snapshot.forEach(function (s) {
if (s.missingRequiredFields) {
(0, relay_runtime_1.handlePotentialSnapshotErrors)(_this._environment, s.missingRequiredFields, s.relayResolverErrors);
}
});
} else {
if (snapshot.missingRequiredFields) {
(0, relay_runtime_1.handlePotentialSnapshotErrors)(this._environment, snapshot.missingRequiredFields, snapshot.relayResolverErrors);
}
}
};
return FragmentResolver;
}();
exports.FragmentResolver = FragmentResolver;