query.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var utils = require('./utils.js');
  4. var logger = require('./logger');
  5. var notifyManager = require('./notifyManager.js');
  6. var retryer = require('./retryer.js');
  7. var removable = require('./removable.js');
  8. // CLASS
  9. class Query extends removable.Removable {
  10. constructor(config) {
  11. super();
  12. this.abortSignalConsumed = false;
  13. this.defaultOptions = config.defaultOptions;
  14. this.setOptions(config.options);
  15. this.observers = [];
  16. this.cache = config.cache;
  17. this.logger = config.logger || logger.defaultLogger;
  18. this.queryKey = config.queryKey;
  19. this.queryHash = config.queryHash;
  20. this.initialState = config.state || getDefaultState(this.options);
  21. this.state = this.initialState;
  22. this.scheduleGc();
  23. }
  24. get meta() {
  25. return this.options.meta;
  26. }
  27. setOptions(options) {
  28. this.options = { ...this.defaultOptions,
  29. ...options
  30. };
  31. this.updateCacheTime(this.options.cacheTime);
  32. }
  33. optionalRemove() {
  34. if (!this.observers.length && this.state.fetchStatus === 'idle') {
  35. this.cache.remove(this);
  36. }
  37. }
  38. setData(newData, options) {
  39. const data = utils.replaceData(this.state.data, newData, this.options); // Set data and mark it as cached
  40. this.dispatch({
  41. data,
  42. type: 'success',
  43. dataUpdatedAt: options == null ? void 0 : options.updatedAt,
  44. manual: options == null ? void 0 : options.manual
  45. });
  46. return data;
  47. }
  48. setState(state, setStateOptions) {
  49. this.dispatch({
  50. type: 'setState',
  51. state,
  52. setStateOptions
  53. });
  54. }
  55. cancel(options) {
  56. var _this$retryer;
  57. const promise = this.promise;
  58. (_this$retryer = this.retryer) == null ? void 0 : _this$retryer.cancel(options);
  59. return promise ? promise.then(utils.noop).catch(utils.noop) : Promise.resolve();
  60. }
  61. destroy() {
  62. super.destroy();
  63. this.cancel({
  64. silent: true
  65. });
  66. }
  67. reset() {
  68. this.destroy();
  69. this.setState(this.initialState);
  70. }
  71. isActive() {
  72. return this.observers.some(observer => observer.options.enabled !== false);
  73. }
  74. isDisabled() {
  75. return this.getObserversCount() > 0 && !this.isActive();
  76. }
  77. isStale() {
  78. return this.state.isInvalidated || !this.state.dataUpdatedAt || this.observers.some(observer => observer.getCurrentResult().isStale);
  79. }
  80. isStaleByTime(staleTime = 0) {
  81. return this.state.isInvalidated || !this.state.dataUpdatedAt || !utils.timeUntilStale(this.state.dataUpdatedAt, staleTime);
  82. }
  83. onFocus() {
  84. var _this$retryer2;
  85. const observer = this.observers.find(x => x.shouldFetchOnWindowFocus());
  86. if (observer) {
  87. observer.refetch({
  88. cancelRefetch: false
  89. });
  90. } // Continue fetch if currently paused
  91. (_this$retryer2 = this.retryer) == null ? void 0 : _this$retryer2.continue();
  92. }
  93. onOnline() {
  94. var _this$retryer3;
  95. const observer = this.observers.find(x => x.shouldFetchOnReconnect());
  96. if (observer) {
  97. observer.refetch({
  98. cancelRefetch: false
  99. });
  100. } // Continue fetch if currently paused
  101. (_this$retryer3 = this.retryer) == null ? void 0 : _this$retryer3.continue();
  102. }
  103. addObserver(observer) {
  104. if (!this.observers.includes(observer)) {
  105. this.observers.push(observer); // Stop the query from being garbage collected
  106. this.clearGcTimeout();
  107. this.cache.notify({
  108. type: 'observerAdded',
  109. query: this,
  110. observer
  111. });
  112. }
  113. }
  114. removeObserver(observer) {
  115. if (this.observers.includes(observer)) {
  116. this.observers = this.observers.filter(x => x !== observer);
  117. if (!this.observers.length) {
  118. // If the transport layer does not support cancellation
  119. // we'll let the query continue so the result can be cached
  120. if (this.retryer) {
  121. if (this.abortSignalConsumed) {
  122. this.retryer.cancel({
  123. revert: true
  124. });
  125. } else {
  126. this.retryer.cancelRetry();
  127. }
  128. }
  129. this.scheduleGc();
  130. }
  131. this.cache.notify({
  132. type: 'observerRemoved',
  133. query: this,
  134. observer
  135. });
  136. }
  137. }
  138. getObserversCount() {
  139. return this.observers.length;
  140. }
  141. invalidate() {
  142. if (!this.state.isInvalidated) {
  143. this.dispatch({
  144. type: 'invalidate'
  145. });
  146. }
  147. }
  148. fetch(options, fetchOptions) {
  149. var _this$options$behavio, _context$fetchOptions;
  150. if (this.state.fetchStatus !== 'idle') {
  151. if (this.state.dataUpdatedAt && fetchOptions != null && fetchOptions.cancelRefetch) {
  152. // Silently cancel current fetch if the user wants to cancel refetches
  153. this.cancel({
  154. silent: true
  155. });
  156. } else if (this.promise) {
  157. var _this$retryer4;
  158. // make sure that retries that were potentially cancelled due to unmounts can continue
  159. (_this$retryer4 = this.retryer) == null ? void 0 : _this$retryer4.continueRetry(); // Return current promise if we are already fetching
  160. return this.promise;
  161. }
  162. } // Update config if passed, otherwise the config from the last execution is used
  163. if (options) {
  164. this.setOptions(options);
  165. } // Use the options from the first observer with a query function if no function is found.
  166. // This can happen when the query is hydrated or created with setQueryData.
  167. if (!this.options.queryFn) {
  168. const observer = this.observers.find(x => x.options.queryFn);
  169. if (observer) {
  170. this.setOptions(observer.options);
  171. }
  172. }
  173. if (process.env.NODE_ENV !== 'production') {
  174. if (!Array.isArray(this.options.queryKey)) {
  175. this.logger.error("As of v4, queryKey needs to be an Array. If you are using a string like 'repoData', please change it to an Array, e.g. ['repoData']");
  176. }
  177. }
  178. const abortController = utils.getAbortController(); // Create query function context
  179. const queryFnContext = {
  180. queryKey: this.queryKey,
  181. pageParam: undefined,
  182. meta: this.meta
  183. }; // Adds an enumerable signal property to the object that
  184. // which sets abortSignalConsumed to true when the signal
  185. // is read.
  186. const addSignalProperty = object => {
  187. Object.defineProperty(object, 'signal', {
  188. enumerable: true,
  189. get: () => {
  190. if (abortController) {
  191. this.abortSignalConsumed = true;
  192. return abortController.signal;
  193. }
  194. return undefined;
  195. }
  196. });
  197. };
  198. addSignalProperty(queryFnContext); // Create fetch function
  199. const fetchFn = () => {
  200. if (!this.options.queryFn) {
  201. return Promise.reject("Missing queryFn for queryKey '" + this.options.queryHash + "'");
  202. }
  203. this.abortSignalConsumed = false;
  204. return this.options.queryFn(queryFnContext);
  205. }; // Trigger behavior hook
  206. const context = {
  207. fetchOptions,
  208. options: this.options,
  209. queryKey: this.queryKey,
  210. state: this.state,
  211. fetchFn
  212. };
  213. addSignalProperty(context);
  214. (_this$options$behavio = this.options.behavior) == null ? void 0 : _this$options$behavio.onFetch(context); // Store state in case the current fetch needs to be reverted
  215. this.revertState = this.state; // Set to fetching state if not already in it
  216. if (this.state.fetchStatus === 'idle' || this.state.fetchMeta !== ((_context$fetchOptions = context.fetchOptions) == null ? void 0 : _context$fetchOptions.meta)) {
  217. var _context$fetchOptions2;
  218. this.dispatch({
  219. type: 'fetch',
  220. meta: (_context$fetchOptions2 = context.fetchOptions) == null ? void 0 : _context$fetchOptions2.meta
  221. });
  222. }
  223. const onError = error => {
  224. // Optimistically update state if needed
  225. if (!(retryer.isCancelledError(error) && error.silent)) {
  226. this.dispatch({
  227. type: 'error',
  228. error: error
  229. });
  230. }
  231. if (!retryer.isCancelledError(error)) {
  232. var _this$cache$config$on, _this$cache$config, _this$cache$config$on2, _this$cache$config2;
  233. // Notify cache callback
  234. (_this$cache$config$on = (_this$cache$config = this.cache.config).onError) == null ? void 0 : _this$cache$config$on.call(_this$cache$config, error, this);
  235. (_this$cache$config$on2 = (_this$cache$config2 = this.cache.config).onSettled) == null ? void 0 : _this$cache$config$on2.call(_this$cache$config2, this.state.data, error, this);
  236. if (process.env.NODE_ENV !== 'production') {
  237. this.logger.error(error);
  238. }
  239. }
  240. if (!this.isFetchingOptimistic) {
  241. // Schedule query gc after fetching
  242. this.scheduleGc();
  243. }
  244. this.isFetchingOptimistic = false;
  245. }; // Try to fetch the data
  246. this.retryer = retryer.createRetryer({
  247. fn: context.fetchFn,
  248. abort: abortController == null ? void 0 : abortController.abort.bind(abortController),
  249. onSuccess: data => {
  250. var _this$cache$config$on3, _this$cache$config3, _this$cache$config$on4, _this$cache$config4;
  251. if (typeof data === 'undefined') {
  252. if (process.env.NODE_ENV !== 'production') {
  253. this.logger.error("Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: " + this.queryHash);
  254. }
  255. onError(new Error(this.queryHash + " data is undefined"));
  256. return;
  257. }
  258. this.setData(data); // Notify cache callback
  259. (_this$cache$config$on3 = (_this$cache$config3 = this.cache.config).onSuccess) == null ? void 0 : _this$cache$config$on3.call(_this$cache$config3, data, this);
  260. (_this$cache$config$on4 = (_this$cache$config4 = this.cache.config).onSettled) == null ? void 0 : _this$cache$config$on4.call(_this$cache$config4, data, this.state.error, this);
  261. if (!this.isFetchingOptimistic) {
  262. // Schedule query gc after fetching
  263. this.scheduleGc();
  264. }
  265. this.isFetchingOptimistic = false;
  266. },
  267. onError,
  268. onFail: (failureCount, error) => {
  269. this.dispatch({
  270. type: 'failed',
  271. failureCount,
  272. error
  273. });
  274. },
  275. onPause: () => {
  276. this.dispatch({
  277. type: 'pause'
  278. });
  279. },
  280. onContinue: () => {
  281. this.dispatch({
  282. type: 'continue'
  283. });
  284. },
  285. retry: context.options.retry,
  286. retryDelay: context.options.retryDelay,
  287. networkMode: context.options.networkMode
  288. });
  289. this.promise = this.retryer.promise;
  290. return this.promise;
  291. }
  292. dispatch(action) {
  293. const reducer = state => {
  294. var _action$meta, _action$dataUpdatedAt;
  295. switch (action.type) {
  296. case 'failed':
  297. return { ...state,
  298. fetchFailureCount: action.failureCount,
  299. fetchFailureReason: action.error
  300. };
  301. case 'pause':
  302. return { ...state,
  303. fetchStatus: 'paused'
  304. };
  305. case 'continue':
  306. return { ...state,
  307. fetchStatus: 'fetching'
  308. };
  309. case 'fetch':
  310. return { ...state,
  311. fetchFailureCount: 0,
  312. fetchFailureReason: null,
  313. fetchMeta: (_action$meta = action.meta) != null ? _action$meta : null,
  314. fetchStatus: retryer.canFetch(this.options.networkMode) ? 'fetching' : 'paused',
  315. ...(!state.dataUpdatedAt && {
  316. error: null,
  317. status: 'loading'
  318. })
  319. };
  320. case 'success':
  321. return { ...state,
  322. data: action.data,
  323. dataUpdateCount: state.dataUpdateCount + 1,
  324. dataUpdatedAt: (_action$dataUpdatedAt = action.dataUpdatedAt) != null ? _action$dataUpdatedAt : Date.now(),
  325. error: null,
  326. isInvalidated: false,
  327. status: 'success',
  328. ...(!action.manual && {
  329. fetchStatus: 'idle',
  330. fetchFailureCount: 0,
  331. fetchFailureReason: null
  332. })
  333. };
  334. case 'error':
  335. const error = action.error;
  336. if (retryer.isCancelledError(error) && error.revert && this.revertState) {
  337. return { ...this.revertState,
  338. fetchStatus: 'idle'
  339. };
  340. }
  341. return { ...state,
  342. error: error,
  343. errorUpdateCount: state.errorUpdateCount + 1,
  344. errorUpdatedAt: Date.now(),
  345. fetchFailureCount: state.fetchFailureCount + 1,
  346. fetchFailureReason: error,
  347. fetchStatus: 'idle',
  348. status: 'error'
  349. };
  350. case 'invalidate':
  351. return { ...state,
  352. isInvalidated: true
  353. };
  354. case 'setState':
  355. return { ...state,
  356. ...action.state
  357. };
  358. }
  359. };
  360. this.state = reducer(this.state);
  361. notifyManager.notifyManager.batch(() => {
  362. this.observers.forEach(observer => {
  363. observer.onQueryUpdate(action);
  364. });
  365. this.cache.notify({
  366. query: this,
  367. type: 'updated',
  368. action
  369. });
  370. });
  371. }
  372. }
  373. function getDefaultState(options) {
  374. const data = typeof options.initialData === 'function' ? options.initialData() : options.initialData;
  375. const hasData = typeof data !== 'undefined';
  376. const initialDataUpdatedAt = hasData ? typeof options.initialDataUpdatedAt === 'function' ? options.initialDataUpdatedAt() : options.initialDataUpdatedAt : 0;
  377. return {
  378. data,
  379. dataUpdateCount: 0,
  380. dataUpdatedAt: hasData ? initialDataUpdatedAt != null ? initialDataUpdatedAt : Date.now() : 0,
  381. error: null,
  382. errorUpdateCount: 0,
  383. errorUpdatedAt: 0,
  384. fetchFailureCount: 0,
  385. fetchFailureReason: null,
  386. fetchMeta: null,
  387. isInvalidated: false,
  388. status: hasData ? 'success' : 'loading',
  389. fetchStatus: 'idle'
  390. };
  391. }
  392. exports.Query = Query;
  393. //# sourceMappingURL=query.js.map