vue-use-query
Version:
vue use query
339 lines (338 loc) • 14.8 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.Query = void 0;
var utils_1 = require("./utils");
var notifyManager_1 = require("./notifyManager");
var logger_1 = require("./logger");
var retryer_1 = require("./retryer");
// CLASS
var Query = /** @class */ (function () {
function Query(config) {
this.defaultOptions = config.defaultOptions;
this.setOptions(config.options);
this.observers = [];
this.cache = config.cache;
this.queryKey = config.queryKey;
this.queryHash = config.queryHash;
this.initialState = config.state || this.getDefaultState(this.options);
this.state = this.initialState;
this.scheduleGc();
}
Query.prototype.setOptions = function (options) {
var _a;
this.options = __assign(__assign({}, this.defaultOptions), options);
// Default to 5 minutes if not cache time is set
this.cacheTime = Math.max(this.cacheTime || 0, (_a = this.options.cacheTime) !== null && _a !== void 0 ? _a : 5 * 60 * 1000);
};
Query.prototype.setDefaultOptions = function (options) {
this.defaultOptions = options;
};
Query.prototype.scheduleGc = function () {
var _this = this;
this.clearGcTimeout();
if (utils_1.isValidTimeout(this.cacheTime)) {
this.gcTimeout = setTimeout(function () {
_this.optionalRemove();
}, this.cacheTime);
}
};
Query.prototype.clearGcTimeout = function () {
clearTimeout(this.gcTimeout);
this.gcTimeout = undefined;
};
Query.prototype.optionalRemove = function () {
if (!this.observers.length && !this.state.isFetching) {
this.cache.remove(this);
}
};
Query.prototype.setData = function (updater, options) {
var _a, _b;
var prevData = this.state.data;
// Get the new data
var data = utils_1.functionalUpdate(updater, prevData);
// Use prev data if an isDataEqual function is defined and returns `true`
if ((_b = (_a = this.options).isDataEqual) === null || _b === void 0 ? void 0 : _b.call(_a, prevData, data)) {
data = prevData;
}
else if (this.options.structuralSharing !== false) {
// Structurally share data between prev and new data if needed
data = utils_1.replaceEqualDeep(prevData, data);
}
// Set data and mark it as cached
this.dispatch({
data: data,
type: 'success',
dataUpdatedAt: options === null || options === void 0 ? void 0 : options.updatedAt,
});
return data;
};
Query.prototype.setState = function (state, setStateOptions) {
this.dispatch({ type: 'setState', state: state, setStateOptions: setStateOptions });
};
Query.prototype.cancel = function (options) {
var _a;
var promise = this.promise;
(_a = this.retryer) === null || _a === void 0 ? void 0 : _a.cancel(options);
return promise ? promise.then(utils_1.noop).catch(utils_1.noop) : Promise.resolve();
};
Query.prototype.destroy = function () {
this.clearGcTimeout();
this.cancel({ silent: true });
};
Query.prototype.reset = function () {
this.destroy();
this.setState(this.initialState);
};
Query.prototype.isActive = function () {
return this.observers.some(function (observer) { return observer.options.enabled !== false; });
};
Query.prototype.isFetching = function () {
return this.state.isFetching;
};
Query.prototype.isStale = function () {
return (this.state.isInvalidated ||
!this.state.dataUpdatedAt ||
this.observers.some(function (observer) { return observer.getCurrentResult().isStale; }));
};
Query.prototype.isStaleByTime = function (staleTime) {
if (staleTime === void 0) { staleTime = 0; }
return (this.state.isInvalidated ||
!this.state.dataUpdatedAt ||
!utils_1.timeUntilStale(this.state.dataUpdatedAt, staleTime));
};
Query.prototype.onFocus = function () {
var _a;
var observer = this.observers.find(function (x) { return x.shouldFetchOnWindowFocus(); });
if (observer) {
observer.refetch();
}
// Continue fetch if currently paused
(_a = this.retryer) === null || _a === void 0 ? void 0 : _a.continue();
};
Query.prototype.onOnline = function () {
var _a;
var observer = this.observers.find(function (x) { return x.shouldFetchOnReconnect(); });
if (observer) {
observer.refetch();
}
// Continue fetch if currently paused
(_a = this.retryer) === null || _a === void 0 ? void 0 : _a.continue();
};
Query.prototype.addObserver = function (observer) {
if (this.observers.indexOf(observer) === -1) {
this.observers.push(observer);
// Stop the query from being garbage collected
this.clearGcTimeout();
this.cache.notify({ type: 'observerAdded', query: this, observer: observer });
}
};
Query.prototype.removeObserver = function (observer) {
if (this.observers.indexOf(observer) !== -1) {
this.observers = this.observers.filter(function (x) { return x !== observer; });
if (!this.observers.length) {
// If the transport layer does not support cancellation
// we'll let the query continue so the result can be cached
if (this.retryer) {
if (this.retryer.isTransportCancelable) {
this.retryer.cancel({ revert: true });
}
else {
this.retryer.cancelRetry();
}
}
if (this.cacheTime) {
this.scheduleGc();
}
else {
this.cache.remove(this);
}
}
this.cache.notify({ type: 'observerRemoved', query: this, observer: observer });
}
};
Query.prototype.getObserversCount = function () {
return this.observers.length;
};
Query.prototype.invalidate = function () {
if (!this.state.isInvalidated) {
this.dispatch({ type: 'invalidate' });
}
};
Query.prototype.fetch = function (options, fetchOptions) {
var _this = this;
var _a, _b, _c, _d;
if (this.state.isFetching) {
if (this.state.dataUpdatedAt && (fetchOptions === null || fetchOptions === void 0 ? void 0 : fetchOptions.cancelRefetch)) {
// Silently cancel current fetch if the user wants to cancel refetches
this.cancel({ silent: true });
}
else if (this.promise) {
// Return current promise if we are already fetching
return this.promise;
}
}
// Update config if passed, otherwise the config from the last execution is used
if (options) {
this.setOptions(options);
}
// Use the options from the first observer with a query function if no function is found.
// This can happen when the query is hydrated or created with setQueryData.
if (!this.options.queryFn) {
var observer = this.observers.find(function (x) { return x.options.queryFn; });
if (observer) {
this.setOptions(observer.options);
}
}
var queryKey = utils_1.ensureQueryKeyArray(this.queryKey);
// Create query function context
var queryFnContext = {
queryKey: queryKey,
pageParam: undefined,
};
// Create fetch function
var fetchFn = function () {
return _this.options.queryFn
? _this.options.queryFn(queryFnContext)
: Promise.reject('Missing queryFn');
};
// Trigger behavior hook
var context = {
fetchOptions: fetchOptions,
options: this.options,
queryKey: queryKey,
state: this.state,
fetchFn: fetchFn,
};
if ((_a = this.options.behavior) === null || _a === void 0 ? void 0 : _a.onFetch) {
(_b = this.options.behavior) === null || _b === void 0 ? void 0 : _b.onFetch(context);
}
// Store state in case the current fetch needs to be reverted
this.revertState = this.state;
// Set to fetching state if not already in it
if (!this.state.isFetching ||
this.state.fetchMeta !== ((_c = context.fetchOptions) === null || _c === void 0 ? void 0 : _c.meta)) {
this.dispatch({ type: 'fetch', meta: (_d = context.fetchOptions) === null || _d === void 0 ? void 0 : _d.meta });
}
// Try to fetch the data
this.retryer = new retryer_1.Retryer({
fn: context.fetchFn,
onSuccess: function (data) {
_this.setData(data);
// Remove query after fetching if cache time is 0
if (_this.cacheTime === 0) {
_this.optionalRemove();
}
},
onError: function (error) {
// Optimistically update state if needed
if (!(retryer_1.isCancelledError(error) && error.silent)) {
_this.dispatch({
type: 'error',
error: error,
});
}
if (!retryer_1.isCancelledError(error)) {
// Notify cache callback
if (_this.cache.config.onError) {
_this.cache.config.onError(error, _this);
}
// Log error
logger_1.getLogger().error(error);
}
// Remove query after fetching if cache time is 0
if (_this.cacheTime === 0) {
_this.optionalRemove();
}
},
onFail: function () {
_this.dispatch({ type: 'failed' });
},
onPause: function () {
_this.dispatch({ type: 'pause' });
},
onContinue: function () {
_this.dispatch({ type: 'continue' });
},
retry: context.options.retry,
retryDelay: context.options.retryDelay,
});
this.promise = this.retryer.promise;
return this.promise;
};
Query.prototype.dispatch = function (action) {
var _this = this;
this.state = this.reducer(this.state, action);
notifyManager_1.notifyManager.batch(function () {
_this.observers.forEach(function (observer) {
observer.onQueryUpdate(action);
});
_this.cache.notify({ query: _this, type: 'queryUpdated', action: action });
});
};
Query.prototype.getDefaultState = function (options) {
var data = typeof options.initialData === 'function'
? options.initialData()
: options.initialData;
var hasInitialData = typeof options.initialData !== 'undefined';
var initialDataUpdatedAt = hasInitialData
? typeof options.initialDataUpdatedAt === 'function'
? options.initialDataUpdatedAt()
: options.initialDataUpdatedAt
: 0;
var hasData = typeof data !== 'undefined';
return {
data: data,
dataUpdateCount: 0,
dataUpdatedAt: hasData ? initialDataUpdatedAt !== null && initialDataUpdatedAt !== void 0 ? initialDataUpdatedAt : Date.now() : 0,
error: null,
errorUpdateCount: 0,
errorUpdatedAt: 0,
fetchFailureCount: 0,
fetchMeta: null,
isFetching: false,
isInvalidated: false,
isPaused: false,
status: hasData ? 'success' : 'idle',
};
};
Query.prototype.reducer = function (state, action) {
var _a, _b;
switch (action.type) {
case 'failed':
return __assign(__assign({}, state), { fetchFailureCount: state.fetchFailureCount + 1 });
case 'pause':
return __assign(__assign({}, state), { isPaused: true });
case 'continue':
return __assign(__assign({}, state), { isPaused: false });
case 'fetch':
return __assign(__assign({}, state), { fetchFailureCount: 0, fetchMeta: (_a = action.meta) !== null && _a !== void 0 ? _a : null, isFetching: true, isPaused: false, status: !state.dataUpdatedAt ? 'loading' : state.status });
case 'success':
return __assign(__assign({}, state), { data: action.data, dataUpdateCount: state.dataUpdateCount + 1, dataUpdatedAt: (_b = action.dataUpdatedAt) !== null && _b !== void 0 ? _b : Date.now(), error: null, fetchFailureCount: 0, isFetching: false, isInvalidated: false, isPaused: false, status: 'success' });
case 'error':
var error = action.error;
if (retryer_1.isCancelledError(error) && error.revert && this.revertState) {
return __assign({}, this.revertState);
}
return __assign(__assign({}, state), { error: error, errorUpdateCount: state.errorUpdateCount + 1, errorUpdatedAt: Date.now(), fetchFailureCount: state.fetchFailureCount + 1, isFetching: false, isPaused: false, status: 'error' });
case 'invalidate':
return __assign(__assign({}, state), { isInvalidated: true });
case 'setState':
return __assign(__assign({}, state), action.state);
default:
return state;
}
};
return Query;
}());
exports.Query = Query;