utils tests created

TQueryFnData, TData & TQueryData now only can be Maps or subclass of Map
This commit is contained in:
Kingkor Roy Tirtho
2022-02-14 12:02:30 +06:00
parent 75af8f5cdd
commit 0b4c030806
9 changed files with 533 additions and 122 deletions
+3 -2
View File
@@ -29,7 +29,8 @@ void main() async {
var key = QueryKey("TEST");
QueryClient queryClient = QueryClient();
queryClient.mount();
var data = await queryClient.fetchQuery<Map, dynamic, Map>(
var data = await queryClient
.fetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (context) {
return Future.value(todos.first);
@@ -39,7 +40,7 @@ void main() async {
print(data);
print("======CACHED DATA======");
print(queryClient.getQueryData(key));
queryClient.setQueryData<Map>(key, (prevData) {
queryClient.setQueryData<Map<String, dynamic>>(key, (prevData) {
return {
...(prevData) ?? {},
"title": "Yehi aloh heh",
+19 -7
View File
@@ -9,16 +9,19 @@ typedef QueryKeyHashFunction = String Function(QueryKey queryKey);
typedef QueryFunction<T, TPageParam> = FutureOr<T> Function(
QueryFunctionContext<TPageParam> context,
);
typedef GetPreviousPageParamFunction<TQueryFnData> = Function(
typedef GetPreviousPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage,
List<TQueryFnData> allPages,
);
typedef GetNextPageParamFunction<TQueryFnData> = Function(
typedef GetNextPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage,
List<TQueryFnData> allPages,
);
class QueryOptions<TQueryFnData, TError, TData> {
class QueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
ShouldRetryFunction<TError>? retry;
RetryDelayFunction<TError>? retryDelay;
Duration? cacheTime;
@@ -259,7 +262,7 @@ enum QueryStatus {
success,
}
class QueryObserverResult<TData, TError> {
class QueryObserverResult<TData extends Map<String, dynamic>, TError> {
TData? data;
DateTime? dataUpdatedAt;
TError? error;
@@ -373,7 +376,11 @@ class QueryObserverResult<TData, TError> {
}
}
typedef RefetchIntervalFunction<TQueryFnData, TError, TQueryData, TData>
typedef RefetchIntervalFunction<
TQueryFnData extends Map<String, dynamic>,
TError,
TQueryData extends Map<String, dynamic>,
TData extends Map<String, dynamic>>
= Duration? Function(
TData? data,
Query<TQueryFnData, TError, TQueryData> query,
@@ -391,7 +398,11 @@ enum RefetchOnMount {
always,
}
class QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
class QueryObserverOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>
extends QueryOptions<TQueryFnData, TError, TQueryData> {
bool? enabled;
Duration? staleTime;
@@ -520,7 +531,8 @@ class DefaultOptions<TError> {
});
}
class FetchQueryOptions<TQueryFnData, TError, TData>
class FetchQueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>
extends QueryOptions<TQueryFnData, TError, TData> {
/// The time after data is considered stale.
/// If the data is fresh it will be returned from the cache.
+11 -8
View File
@@ -17,7 +17,8 @@ class FetchOptions {
FetchOptions({this.cancelRefetch, this.meta});
}
class FetchContext<TQueryFnData, TError, TData> {
class FetchContext<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
FutureOr<TQueryFnData> Function() fetchFn;
FetchOptions? fetchOptions;
QueryOptions<TQueryFnData, TError, TData> options;
@@ -35,12 +36,13 @@ class FetchContext<TQueryFnData, TError, TData> {
});
}
class QueryBehavior<TQueryFnData, TError, TData> {
class QueryBehavior<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
void Function(FetchContext<TQueryFnData, TError, TData> context) onFetch;
QueryBehavior({required this.onFetch});
}
class QueryState<TData, TError> {
class QueryState<TData extends Map<String, dynamic>, TError> {
TData? data;
TError? error;
QueryStatus status;
@@ -120,7 +122,7 @@ class SetStateOptions {
}
}
class Action<TData, TError> {
class Action<TData extends Map<String, dynamic>, TError> {
ActionType type;
Object? meta;
TData? data;
@@ -160,7 +162,8 @@ class Action<TData, TError> {
}
}
class Query<TQueryFnData, TError, TData> {
class Query<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
QueryKey queryKey;
String queryHash;
late QueryOptions<TQueryFnData, TError, TData> options;
@@ -277,7 +280,7 @@ class Query<TQueryFnData, TError, TData> {
data = prevData as TData;
} else if (this.options.structuralSharing != false) {
// Structurally share data between prev and new data if needed
data = replaceEqualDeep<TData>(prevData ?? {} as TData, data);
data = replaceEqualDeep(prevData ?? {} as TData, data);
}
// Set data and mark it as cached
_dispatch(Action(
@@ -360,7 +363,7 @@ class Query<TQueryFnData, TError, TData> {
var observer =
_observers.firstWhereOrNull((x) => x.options.queryFn != null);
if (observer != null) {
_setOptions(QueryOptions(
_setOptions(QueryOptions<TQueryFnData, TError, TData>(
queryKey: observer.options.queryKey,
queryKeyHashFn: observer.options.queryKeyHashFn,
cacheTime: observer.options.cacheTime,
@@ -368,7 +371,7 @@ class Query<TQueryFnData, TError, TData> {
queryFn:
observer.options.queryFn as QueryFunction<TQueryFnData, dynamic>,
queryHash: observer.options.queryHash,
initialData: observer.options.initialData,
initialData: observer.options.initialData as TData?,
initialDataUpdatedAt: observer.options.initialDataUpdatedAt,
meta: observer.options.meta,
structuralSharing: observer.options.structuralSharing,
@@ -59,7 +59,10 @@ class QueryCache extends Subscribable<QueryCacheListener> {
_queriesMap = {},
super();
Query<TQueryFnData, TError, TData> build<TQueryFnData, TError, TData>(
Query<TQueryFnData, TError, TData> build<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(
QueryClient client,
QueryOptions<TQueryFnData, TError, TData> options, [
QueryState<TData, TError>? state,
@@ -124,8 +127,10 @@ class QueryCache extends Subscribable<QueryCacheListener> {
});
}
Query<TQueryFnData, TError, TData>? get<TQueryFnData, TError, TData>(
String queryHash) {
Query<TQueryFnData, TError, TData>? get<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(String queryHash) {
return _queriesMap[queryHash] as Query<TQueryFnData, TError, TData>?;
}
@@ -133,13 +138,16 @@ class QueryCache extends Subscribable<QueryCacheListener> {
return _queries;
}
Query? find<TQueryFnData, TError, TData>(
Query<TQueryFnData, TError, TData>? find<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(
QueryKey queryKey,
QueryFilters queryFilters,
) {
queryFilters.exact ??= true;
return _queries
.firstWhereOrNull((query) => matchQuery(queryFilters, query));
return _queries.firstWhereOrNull((query) => matchQuery(queryFilters, query))
as Query<TQueryFnData, TError, TData>?;
}
List<Query> findAll(QueryKey? queryKey, [QueryFilters? filters]) {
@@ -26,7 +26,7 @@ class MutationDefaults {
// });
}
class QueryData<TData> {
class QueryData<TData extends Map<String, dynamic>> {
QueryKey queryKey;
TData data;
QueryData({
@@ -85,17 +85,18 @@ class QueryClient {
// return _mutationCache.findAll({ ...filters, fetching: true }).length
// }
TData? getQueryData<TData>(
TData? getQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, dynamic, dynamic>(queryKey, filters ?? QueryFilters())
.find<TData, dynamic, Map<String, dynamic>>(
queryKey, filters ?? QueryFilters())
?.state
.data;
.data as TData?;
}
List<QueryData<TData>> getQueriesData<TData>({
List<QueryData<TData>> getQueriesData<TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryFilters? filters,
}) {
@@ -107,26 +108,27 @@ class QueryClient {
}).toList();
}
TData setQueryData<TData>(
TData setQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey,
DataUpdateFunction<TData?, TData> updater, [
DateTime? updatedAt,
]) {
final QueryOptions<dynamic, dynamic, TData> defaultedOptions =
QueryOptions<dynamic, dynamic, TData>.fromJson(
defaultQueryOptions<dynamic, dynamic, TData, dynamic>(
QueryObserverOptions<dynamic, dynamic, TData, dynamic>(
queryKey: queryKey))
final QueryOptions<Map<String, dynamic>, dynamic, TData> defaultedOptions =
QueryOptions<Map<String, dynamic>, dynamic, TData>.fromJson(
defaultQueryOptions<Map<String, dynamic>, dynamic, TData,
Map<String, dynamic>>(
QueryObserverOptions<Map<String, dynamic>, dynamic, TData,
Map<String, dynamic>>(queryKey: queryKey))
.toJson());
return _queryCache
.build<dynamic, dynamic, TData>(this, defaultedOptions)
.build<Map<String, dynamic>, dynamic, TData>(this, defaultedOptions)
.setData(
updater,
updatedAt: updatedAt,
);
}
List<QueryData> setQueriesData<TData>({
List<QueryData> setQueriesData<TData extends Map<String, dynamic>>({
required DataUpdateFunction<TData?, TData> updater,
QueryKey? queryKey,
QueryFilters? filters,
@@ -149,12 +151,13 @@ class QueryClient {
.toList();
}
QueryState<TData, TError>? getQueryState<TData, TError>(
QueryState<TData, TError>?
getQueryState<TData extends Map<String, dynamic>, TError>(
QueryKey queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, TError, dynamic>(
.find<TData, TError, Map<String, dynamic>>(
queryKey,
filters ?? QueryFilters(),
)
@@ -257,13 +260,15 @@ class QueryClient {
return future;
}
Future<TData> fetchQuery<TQueryFnData, TError, TData>({
Future<TData> fetchQuery<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
var defaultedOptions = this.defaultQueryOptions(
QueryObserverOptions(
QueryObserverOptions<Map<String, dynamic>, dynamic, Map<String, dynamic>,
TData>(
queryFn: queryFn,
queryKey: queryKey,
staleTime: options?.staleTime,
@@ -280,28 +285,33 @@ class QueryClient {
);
// returning 0 indicates turing off retry
defaultedOptions.retry ??= (_, __) => 0;
var query = _queryCache.build(this, defaultedOptions);
var query = _queryCache.build<Map<String, dynamic>, dynamic, TData>(
this, defaultedOptions);
return query.isStaleByTime(defaultedOptions.staleTime)
? query.fetch(defaultedOptions)
: Future.value(query.state.data as TData);
}
Future<void> prefetchQuery<TQueryFnData, TError, TData>({
Future<void> prefetchQuery<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
return fetchQuery(
return fetchQuery<TQueryFnData, dynamic, TData>(
queryKey: queryKey,
queryFn: queryFn,
options: options,
).then(noop).catchError(noop);
}
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
defaultQueryOptions<TQueryFnData, TError, TData, TQueryData>(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>?
options) {
QueryObserverOptions<TQueryFnData, TError, TData,
TQueryData> defaultQueryOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options) {
if (options?.defaulted == true) return options!;
var defaultedOptions =
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
@@ -322,7 +332,11 @@ class QueryClient {
}
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
defaultQueryObserverOptions<TQueryFnData, TError, TData, TQueryData>([
defaultQueryObserverOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>([
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options,
]) {
return this.defaultQueryOptions(options);
@@ -15,8 +15,8 @@ import 'package:fl_query/src/core/subscribable.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:meta/meta.dart';
typedef QueryObserverListener<TData, TError> = void Function(
QueryObserverResult<TData, TError> result);
typedef QueryObserverListener<TData extends Map<String, dynamic>, TError> = void
Function(QueryObserverResult<TData, TError> result);
class NotifyOptions {
bool? cache;
@@ -52,13 +52,18 @@ class ObserverFetchOptions extends FetchOptions {
}) : super(cancelRefetch: cancelRefetch, meta: meta);
}
class SelectQuery<TQueryData, TData> {
class SelectQuery<TQueryData extends Map<String, dynamic>,
TData extends Map<String, dynamic>> {
TData Function(TQueryData data) fn;
TData result;
SelectQuery(this.fn, this.result);
}
class QueryObserver<TQueryFnData, TError, TData, TQueryData>
class QueryObserver<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>
extends Subscribable<QueryObserverListener> {
late QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options;
QueryClient _client;
@@ -70,7 +75,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
_currentResultOptions;
QueryObserverResult<TData, TError>? _previousQueryResult;
Exception? _previousSelectError;
SelectQuery? _previousSelect;
SelectQuery<TQueryData, TData>? _previousSelect;
Timer? _staleTimeout;
Timer? _refetchInterval;
Duration? _currentRefetchInterval;
@@ -119,8 +124,8 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options, [
NotifyOptions? notifyOptions,
]) {
var prevOptions = this.options;
var prevQuery = _currentQuery;
final prevOptions = this.options;
final prevQuery = _currentQuery;
this.options = this._client.defaultQueryObserverOptions(options);
@@ -190,7 +195,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Future<QueryObserverResult<TData, TError>> getNextResult(
bool? throwOnError,
) {
var completer = Completer<QueryObserverResult<TData, TError>>();
final completer = Completer<QueryObserverResult<TData, TError>>();
var unsubscribe;
unsubscribe = subscribe((result) {
if (!result.isFetching) {
@@ -215,8 +220,8 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Future<QueryObserverResult<TData, TError>> fetchOptimistic(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options) {
var defaultedOptions = _client.defaultQueryObserverOptions(options);
var query = _client.getQueryCache().build(_client, defaultedOptions);
final defaultedOptions = _client.defaultQueryObserverOptions(options);
final query = _client.getQueryCache().build(_client, defaultedOptions);
return query.fetch().then((val) {
return createResult(query, defaultedOptions);
@@ -269,7 +274,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Map<String, dynamic> prevResultMap = prevResult.toJson();
return resultMap.keys.any((key) {
var changed = resultMap[key] != prevResultMap[key];
final changed = resultMap[key] != prevResultMap[key];
bool? isIncluded = includedProps?.any((x) => x == key);
bool isExcluded =
options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false;
@@ -280,7 +285,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
void updateResult([NotifyOptions? notifyOptions]) {
QueryObserverResult<TData, TError>? prevResult = _currentResult;
final QueryObserverResult<TData, TError>? prevResult = _currentResult;
if (_currentQuery != null)
_currentResult = this.createResult(_currentQuery!, this.options);
@@ -288,7 +293,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
_currentResultOptions = this.options;
// Only notify if something has changed
if (shallowEqualMap(_currentResult.toJson(), prevResult.toJson())) {
if (shallowEqualMap(_currentResult.toJson(), prevResult?.toJson())) {
return;
}
NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true);
@@ -304,11 +309,12 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
void _updateQuery() {
var query = this._client.getQueryCache().build(this._client, this.options);
final query =
this._client.getQueryCache().build(this._client, this.options);
if (query == _currentQuery) return;
var prevQuery = _currentQuery;
final prevQuery = _currentQuery;
_currentQuery = query;
_currentQueryInitialState = query.state;
_previousQueryResult = _currentResult;
@@ -339,22 +345,22 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
var prevQuery = _currentQuery;
var prevOptions = this.options;
var prevResult = _currentResult;
var prevResultState = _currentResultState;
var prevResultOptions = _currentResultOptions;
bool queryChange = query != prevQuery;
var queryInitialState =
final prevQuery = _currentQuery;
final prevOptions = this.options;
final prevResult = _currentResult;
final prevResultState = _currentResultState;
final prevResultOptions = _currentResultOptions;
final bool queryChange = query != prevQuery;
final queryInitialState =
queryChange ? query.state : _currentQueryInitialState;
var prevQueryResult = queryChange ? _currentResult : _previousQueryResult;
final prevQueryResult = queryChange ? _currentResult : _previousQueryResult;
var state = query.state;
var dataUpdatedAt = state.dataUpdatedAt;
var error = state.error;
var errorUpdatedAt = state.errorUpdatedAt;
var isFetching = state.isFetching;
var status = state.status;
final state = query.state;
DateTime? dataUpdatedAt = state.dataUpdatedAt;
TError? error = state.error;
DateTime? errorUpdatedAt = state.errorUpdatedAt;
bool isFetching = state.isFetching;
QueryStatus status = state.status;
bool isPreviousData = false;
bool isPlaceholderData = false;
@@ -362,9 +368,9 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
// Optimistically set result in fetching state if needed
if (options.optimisticResults == true) {
var mounted = hasListeners();
final bool mounted = hasListeners();
var fetchOnMount = !mounted && shouldFetchOnMount(query, options);
final bool fetchOnMount = !mounted && shouldFetchOnMount(query, options);
bool fetchOptionally = mounted &&
prevQuery != null &&
@@ -401,7 +407,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
try {
data = options.select?.call(state.data);
if (options.structuralSharing != false) {
data = replaceEqualDeep(prevResult?.data, data);
data = replaceEqualDeep(prevResult.data, data);
}
if (options.select != null && data != null) {
_previousSelect = SelectQuery<TQueryData, TData>(
@@ -429,7 +435,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
(status == QueryStatus.loading || status == QueryStatus.idle)) {
var placeholderData;
if (prevResult?.isPlaceholderData == true &&
if (prevResult.isPlaceholderData == true &&
options.placeholderData == prevResultOptions?.placeholderData) {
placeholderData = prevResult.data;
} else {
@@ -439,7 +445,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
placeholderData = options.select?.call(placeholderData);
if (options.structuralSharing != false) {
placeholderData =
replaceEqualDeep(prevResult?.data, placeholderData);
replaceEqualDeep(prevResult.data, placeholderData);
}
_previousSelectError = null;
} catch (selectError) {
@@ -459,7 +465,8 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
}
QueryObserverResult<TData, TError> result = QueryObserverResult(
final QueryObserverResult<TData, TError> result =
QueryObserverResult<TData, TError>(
status: status,
dataUpdatedAt: dataUpdatedAt,
isLoading: status == QueryStatus.loading,
@@ -602,7 +609,11 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
}
bool shouldLoadOnMount<TQueryFnData, TError, TData, TQueryData>(
bool shouldLoadOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
@@ -612,7 +623,11 @@ bool shouldLoadOnMount<TQueryFnData, TError, TData, TQueryData>(
options.retryOnMount == false));
}
bool shouldRefetchOnMount<TQueryFnData, TError, TData, TQueryData>(
bool shouldRefetchOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
@@ -623,7 +638,11 @@ bool shouldRefetchOnMount<TQueryFnData, TError, TData, TQueryData>(
isStale(query, options))));
}
bool shouldFetchOnMount<TQueryFnData, TError, TData, TQueryData>(
bool shouldFetchOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
@@ -631,17 +650,26 @@ bool shouldFetchOnMount<TQueryFnData, TError, TData, TQueryData>(
shouldRefetchOnMount(query, options));
}
bool shouldFetchOnReconnect<TQueryFnData, TError, TData, TQueryData>(
bool shouldFetchOnReconnect<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return (options.enabled != false &&
(options.refetchOnReconnect == RefetchOnReconnect.always ||
(options.refetchOnReconnect != RefetchOnReconnect.off &&
isStale(query, options))));
isStale<TQueryFnData, TError, TData, TQueryData>(
query, options))));
}
bool shouldFetchOptionally<TQueryFnData, TError, TData, TQueryData>(
bool shouldFetchOptionally<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
Query<TQueryFnData, TError, TQueryData> prevQuery,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
@@ -653,7 +681,11 @@ bool shouldFetchOptionally<TQueryFnData, TError, TData, TQueryData>(
isStale(query, options));
}
bool isStale<TQueryFnData, TError, TData, TQueryData>(
bool isStale<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
+2 -2
View File
@@ -36,9 +36,9 @@ bool isCancelledError(value) {
}
typedef OnError<TError> = void Function(TError error);
typedef OnData<TData> = void Function(TData data);
typedef OnData<TData extends Map<String, dynamic>> = void Function(TData data);
class Retryer<TData, TError> {
class Retryer<TData extends Map<String, dynamic>, TError> {
late void Function({bool? revert, bool? silent}) cancel;
late void Function() cancelRetry;
late void Function() continueRetry;
+51 -30
View File
@@ -1,6 +1,7 @@
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:collection/collection.dart';
/// Default query keys hash function.
/// Dummy function just to fill the gaps for original react-query like
@@ -95,41 +96,61 @@ bool shallowEqualMap(Map? a, Map? b) {
/// If not, it will replace any deeply equal children of `b` with those
/// of `a`\
/// This can be used for structural sharing between JSON values for example.
/// `a` & `b` can only be Type of [Iterable] or [Map]
T replaceEqualDeep<T>(T a, T b) {
/// `a` & `b` can only be Type of [List] or [Map]
replaceEqualDeep(a, b) {
if (a == b) {
return a;
}
bool isList = (a is Iterable && b is Iterable);
if (isList || (a is Map && b is Map)) {
int aSize = isList ? a.length : (a as Map).keys.length;
List bItems = (isList ? b : (b as Map).keys).toList();
int bSize = bItems.length;
var copy;
int equalItems = 0;
for (int i = 0; i < bSize; i++) {
var key = isList ? i : bItems[i];
if (isList) {
copy ??= [];
copy[key] = replaceEqualDeep((a as List)[key], (b as List)[key]);
if (copy[key] == a[key]) {
equalItems++;
}
} else {
copy ??= {};
copy[key] = replaceEqualDeep((a as Map)[key], (b as Map)[key]);
if (copy[key] == a[key]) {
equalItems++;
}
}
}
return aSize == bSize && equalItems == aSize ? a : copy as T;
int aSize;
List bItems;
int bSize;
int equalItems = 0;
onEqual() => equalItems++;
var copy;
if (a is List && b is List) {
aSize = a.length;
bItems = b;
bSize = bItems.length;
copy = replaceEqualDeepList(a, b, onEqual);
} else if (a is Map && b is Map) {
aSize = a.keys.length;
bItems = b.keys.toList();
bSize = bItems.length;
copy = replaceEqualDeepMap(a, b, onEqual);
} else {
return b;
}
return b;
return aSize == bSize && equalItems == aSize ? a : copy;
}
Map replaceEqualDeepMap(Map a, Map b, void Function() onEqual) {
final copy = Map.from(a);
copy.clear();
for (final bEntry in b.entries) {
final aItem = a[bEntry.key];
copy[bEntry.key] =
aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value;
if (copy[bEntry.key] == aItem) {
onEqual();
}
}
return copy;
}
List replaceEqualDeepList(List a, List b, void Function() onEqual) {
final copy = List.of(a, growable: true);
copy.clear();
for (final bEntry in b.asMap().entries) {
final aItem = a.firstWhereIndexedOrNull((i, _) => i == bEntry.key);
final result =
aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value;
copy.add(result);
if (copy.last == aItem) {
onEqual();
}
}
return copy;
}
Duration timeUntilStale(DateTime updatedAt, [Duration? staleTime]) =>
@@ -0,0 +1,320 @@
import 'package:fl_query/src/core/utils.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
group('core/utils', () {
group('replaceEqualDeep', () {
test(
'Should return the previous value When the next value is an equal primitive',
() {
expect(replaceEqualDeep(1, 1), equals(1));
expect(replaceEqualDeep('1', '1'), equals('1'));
expect(replaceEqualDeep(true, true), true);
expect(replaceEqualDeep(false, false), false);
expect(replaceEqualDeep(null, null), null);
});
test(
'Should return the next value When the previous value is a different value',
() {
expect(replaceEqualDeep(1, 0), equals(0));
expect(replaceEqualDeep(1, 2), equals(2));
expect(replaceEqualDeep('1', '2'), equals('2'));
expect(replaceEqualDeep(true, false), equals(false));
expect(replaceEqualDeep(false, true), equals(true));
});
test(
'Should return the next value When the previous value is a different type',
() {
final array = [1];
final object = {"a": "a"};
expect(replaceEqualDeep(0, null), equals(null));
expect(replaceEqualDeep(null, 0), equals(0));
expect(replaceEqualDeep(2, null), equals(null));
expect(replaceEqualDeep(null, 2), equals(2));
expect(replaceEqualDeep({}, null), equals(null));
expect(replaceEqualDeep([], null), equals(null));
expect(replaceEqualDeep(array, object), equals(object));
expect(replaceEqualDeep(object, array), equals(array));
});
test(
'Should return the previous value When the next value is an equal array',
() {
final prev = [1, 2];
final next = [1, 2];
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return a copy When the previous value is a different array subset',
() {
final prev = [1, 2];
final next = [1, 2, 3];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(equals(prev)));
});
test(
'Should return the previous value When the next value is an equal empty array',
() {
final prev = [];
final next = [];
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return the previous value When the next value is an equal empty object',
() {
final prev = {};
final next = {};
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return the previous value When the next value is an equal object',
() {
final prev = {"a": 'a'};
final next = {"a": 'a'};
expect(replaceEqualDeep(prev, next), equals(prev));
});
test('Should replace different values in objects', () {
final prev = {
"a": {"b": 'b'},
"c": 'c'
};
final next = {
"a": {"b": 'b'},
"c": 'd'
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result["a"], equals(prev["a"]));
expect(result["c"], equals(next["c"]));
});
test('Should replace different values in arrays', () {
final prev = [
1,
{"a": 'a'},
{
"b": {"b": 'b'}
},
[1]
];
final next = [
1,
{"a": 'a'},
{
"b": {"b": 'c'}
},
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result[0], prev[0]);
expect(result[1], prev[1]);
expect((result[2] as Map)["b"]["b"], (next[2] as Map)["b"]["b"]);
expect(result[3], prev[3]);
});
test(
'Should replace different values in arrays When the next value is a subset',
() {
final prev = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'}
];
final next = [
{"a": 'a'},
{"b": 'b'}
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result[0], prev[0]);
expect(result[1], prev[1]);
expect(() => result[2], throwsRangeError);
});
test(
'Should replace different values in arrays When the next value is a superset',
() {
final prev = [
{"a": 'a'},
{"b": 'b'}
];
final next = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'}
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result[0], equals(prev[0]));
expect(result[1], equals(prev[1]));
expect(result[2], equals(next[2]));
});
test('Should copy objects which are not arrays or objects', () {
final prev = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'},
1
];
final next = [
{"a": 'a'},
Map(),
{"c": 'c'},
2
];
final result = replaceEqualDeep(prev, next);
expect(result[0], equals(prev[0]));
expect(result[1], equals(next[1]));
expect(result[2], equals(prev[2]));
expect(result[3], equals(next[3]));
});
test('Should support equal objects which are not arrays or objects', () {
final map = new Map();
final prev = [
map,
[1]
];
final next = [
map,
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(prev));
});
test('Should support non equal objects which are not arrays or objects',
() {
final map1 = new Map();
final map2 = new Map();
final prev = [
map1,
[1]
];
final next = [
map2,
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result[0], equals(next[0]));
expect(result[1], equals(prev[1]));
});
test('Should replace all parent objects if some nested value changes',
() {
final prev = {
"todo": {
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": false},
},
"otherTodo": {
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true},
},
};
final next = {
"todo": {
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": true},
},
"otherTodo": {
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true},
},
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result["todo"] == prev["todo"], isFalse);
expect(result["todo"] == next["todo"], isFalse);
expect(result["todo"]["id"], equals((next["todo"] as Map)["id"]));
expect(result["todo"]["meta"], equals((prev["todo"] as Map)["meta"]));
expect(
result["todo"]["state"],
equals((next["todo"] as Map)["state"]),
);
expect(
result["todo"]["state"]["done"],
(next["todo"] as Map)["state"]["done"],
);
expect(result["otherTodo"], prev["otherTodo"]);
});
test('Should replace all parent arrays if some nested value changes', () {
final Map<String, List<Map>> prev = {
"todos": [
{
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": false}
},
{
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true}
},
],
};
final Map<String, List<Map>> next = {
"todos": [
{
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": true}
},
{
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true}
},
],
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result["todos"][0], isNot(equals(prev["todos"]?.first)));
expect(
result["todos"][0]?["id"],
equals(next["todos"]?.first["id"]),
);
expect(
result["todos"][0]?["meta"],
equals(prev["todos"]?.first["meta"]),
);
expect(result["todos"][0]?["state"]["done"],
next["todos"]?.first["state"]["done"]);
expect(result["todos"][1], equals(prev["todos"]?[1]));
});
});
group(
'matchMutation',
() => {
// test('should return false if mutationKey options is undefined', () => {
// const filters = { mutationKey: 'key1' };
// const queryClient = new QueryClient();
// const mutation = new Mutation({
// mutationId: 1,
// mutationCache: queryClient.getMutationCache(),
// options: {},
// })
// expect(matchMutation(filters, mutation)).toBeFalsy()
// })
});
});
}