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"); var key = QueryKey("TEST");
QueryClient queryClient = QueryClient(); QueryClient queryClient = QueryClient();
queryClient.mount(); queryClient.mount();
var data = await queryClient.fetchQuery<Map, dynamic, Map>( var data = await queryClient
.fetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key, queryKey: key,
queryFn: (context) { queryFn: (context) {
return Future.value(todos.first); return Future.value(todos.first);
@@ -39,7 +40,7 @@ void main() async {
print(data); print(data);
print("======CACHED DATA======"); print("======CACHED DATA======");
print(queryClient.getQueryData(key)); print(queryClient.getQueryData(key));
queryClient.setQueryData<Map>(key, (prevData) { queryClient.setQueryData<Map<String, dynamic>>(key, (prevData) {
return { return {
...(prevData) ?? {}, ...(prevData) ?? {},
"title": "Yehi aloh heh", "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( typedef QueryFunction<T, TPageParam> = FutureOr<T> Function(
QueryFunctionContext<TPageParam> context, QueryFunctionContext<TPageParam> context,
); );
typedef GetPreviousPageParamFunction<TQueryFnData> = Function( typedef GetPreviousPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage, TQueryFnData firstPage,
List<TQueryFnData> allPages, List<TQueryFnData> allPages,
); );
typedef GetNextPageParamFunction<TQueryFnData> = Function( typedef GetNextPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage, TQueryFnData firstPage,
List<TQueryFnData> allPages, List<TQueryFnData> allPages,
); );
class QueryOptions<TQueryFnData, TError, TData> { class QueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
ShouldRetryFunction<TError>? retry; ShouldRetryFunction<TError>? retry;
RetryDelayFunction<TError>? retryDelay; RetryDelayFunction<TError>? retryDelay;
Duration? cacheTime; Duration? cacheTime;
@@ -259,7 +262,7 @@ enum QueryStatus {
success, success,
} }
class QueryObserverResult<TData, TError> { class QueryObserverResult<TData extends Map<String, dynamic>, TError> {
TData? data; TData? data;
DateTime? dataUpdatedAt; DateTime? dataUpdatedAt;
TError? error; 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( = Duration? Function(
TData? data, TData? data,
Query<TQueryFnData, TError, TQueryData> query, Query<TQueryFnData, TError, TQueryData> query,
@@ -391,7 +398,11 @@ enum RefetchOnMount {
always, 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> { extends QueryOptions<TQueryFnData, TError, TQueryData> {
bool? enabled; bool? enabled;
Duration? staleTime; 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> { extends QueryOptions<TQueryFnData, TError, TData> {
/// The time after data is considered stale. /// The time after data is considered stale.
/// If the data is fresh it will be returned from the cache. /// 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}); 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; FutureOr<TQueryFnData> Function() fetchFn;
FetchOptions? fetchOptions; FetchOptions? fetchOptions;
QueryOptions<TQueryFnData, TError, TData> options; 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; void Function(FetchContext<TQueryFnData, TError, TData> context) onFetch;
QueryBehavior({required this.onFetch}); QueryBehavior({required this.onFetch});
} }
class QueryState<TData, TError> { class QueryState<TData extends Map<String, dynamic>, TError> {
TData? data; TData? data;
TError? error; TError? error;
QueryStatus status; QueryStatus status;
@@ -120,7 +122,7 @@ class SetStateOptions {
} }
} }
class Action<TData, TError> { class Action<TData extends Map<String, dynamic>, TError> {
ActionType type; ActionType type;
Object? meta; Object? meta;
TData? data; 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; QueryKey queryKey;
String queryHash; String queryHash;
late QueryOptions<TQueryFnData, TError, TData> options; late QueryOptions<TQueryFnData, TError, TData> options;
@@ -277,7 +280,7 @@ class Query<TQueryFnData, TError, TData> {
data = prevData as TData; data = prevData as TData;
} else if (this.options.structuralSharing != false) { } else if (this.options.structuralSharing != false) {
// Structurally share data between prev and new data if needed // 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 // Set data and mark it as cached
_dispatch(Action( _dispatch(Action(
@@ -360,7 +363,7 @@ class Query<TQueryFnData, TError, TData> {
var observer = var observer =
_observers.firstWhereOrNull((x) => x.options.queryFn != null); _observers.firstWhereOrNull((x) => x.options.queryFn != null);
if (observer != null) { if (observer != null) {
_setOptions(QueryOptions( _setOptions(QueryOptions<TQueryFnData, TError, TData>(
queryKey: observer.options.queryKey, queryKey: observer.options.queryKey,
queryKeyHashFn: observer.options.queryKeyHashFn, queryKeyHashFn: observer.options.queryKeyHashFn,
cacheTime: observer.options.cacheTime, cacheTime: observer.options.cacheTime,
@@ -368,7 +371,7 @@ class Query<TQueryFnData, TError, TData> {
queryFn: queryFn:
observer.options.queryFn as QueryFunction<TQueryFnData, dynamic>, observer.options.queryFn as QueryFunction<TQueryFnData, dynamic>,
queryHash: observer.options.queryHash, queryHash: observer.options.queryHash,
initialData: observer.options.initialData, initialData: observer.options.initialData as TData?,
initialDataUpdatedAt: observer.options.initialDataUpdatedAt, initialDataUpdatedAt: observer.options.initialDataUpdatedAt,
meta: observer.options.meta, meta: observer.options.meta,
structuralSharing: observer.options.structuralSharing, structuralSharing: observer.options.structuralSharing,
@@ -59,7 +59,10 @@ class QueryCache extends Subscribable<QueryCacheListener> {
_queriesMap = {}, _queriesMap = {},
super(); 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, QueryClient client,
QueryOptions<TQueryFnData, TError, TData> options, [ QueryOptions<TQueryFnData, TError, TData> options, [
QueryState<TData, TError>? state, QueryState<TData, TError>? state,
@@ -124,8 +127,10 @@ class QueryCache extends Subscribable<QueryCacheListener> {
}); });
} }
Query<TQueryFnData, TError, TData>? get<TQueryFnData, TError, TData>( Query<TQueryFnData, TError, TData>? get<
String queryHash) { TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(String queryHash) {
return _queriesMap[queryHash] as Query<TQueryFnData, TError, TData>?; return _queriesMap[queryHash] as Query<TQueryFnData, TError, TData>?;
} }
@@ -133,13 +138,16 @@ class QueryCache extends Subscribable<QueryCacheListener> {
return _queries; 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, QueryKey queryKey,
QueryFilters queryFilters, QueryFilters queryFilters,
) { ) {
queryFilters.exact ??= true; queryFilters.exact ??= true;
return _queries return _queries.firstWhereOrNull((query) => matchQuery(queryFilters, query))
.firstWhereOrNull((query) => matchQuery(queryFilters, query)); as Query<TQueryFnData, TError, TData>?;
} }
List<Query> findAll(QueryKey? queryKey, [QueryFilters? filters]) { 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; QueryKey queryKey;
TData data; TData data;
QueryData({ QueryData({
@@ -85,17 +85,18 @@ class QueryClient {
// return _mutationCache.findAll({ ...filters, fetching: true }).length // return _mutationCache.findAll({ ...filters, fetching: true }).length
// } // }
TData? getQueryData<TData>( TData? getQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey, [ QueryKey queryKey, [
QueryFilters? filters, QueryFilters? filters,
]) { ]) {
return _queryCache return _queryCache
.find<TData, dynamic, dynamic>(queryKey, filters ?? QueryFilters()) .find<TData, dynamic, Map<String, dynamic>>(
queryKey, filters ?? QueryFilters())
?.state ?.state
.data; .data as TData?;
} }
List<QueryData<TData>> getQueriesData<TData>({ List<QueryData<TData>> getQueriesData<TData extends Map<String, dynamic>>({
QueryKey? queryKey, QueryKey? queryKey,
QueryFilters? filters, QueryFilters? filters,
}) { }) {
@@ -107,26 +108,27 @@ class QueryClient {
}).toList(); }).toList();
} }
TData setQueryData<TData>( TData setQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey, QueryKey queryKey,
DataUpdateFunction<TData?, TData> updater, [ DataUpdateFunction<TData?, TData> updater, [
DateTime? updatedAt, DateTime? updatedAt,
]) { ]) {
final QueryOptions<dynamic, dynamic, TData> defaultedOptions = final QueryOptions<Map<String, dynamic>, dynamic, TData> defaultedOptions =
QueryOptions<dynamic, dynamic, TData>.fromJson( QueryOptions<Map<String, dynamic>, dynamic, TData>.fromJson(
defaultQueryOptions<dynamic, dynamic, TData, dynamic>( defaultQueryOptions<Map<String, dynamic>, dynamic, TData,
QueryObserverOptions<dynamic, dynamic, TData, dynamic>( Map<String, dynamic>>(
queryKey: queryKey)) QueryObserverOptions<Map<String, dynamic>, dynamic, TData,
Map<String, dynamic>>(queryKey: queryKey))
.toJson()); .toJson());
return _queryCache return _queryCache
.build<dynamic, dynamic, TData>(this, defaultedOptions) .build<Map<String, dynamic>, dynamic, TData>(this, defaultedOptions)
.setData( .setData(
updater, updater,
updatedAt: updatedAt, updatedAt: updatedAt,
); );
} }
List<QueryData> setQueriesData<TData>({ List<QueryData> setQueriesData<TData extends Map<String, dynamic>>({
required DataUpdateFunction<TData?, TData> updater, required DataUpdateFunction<TData?, TData> updater,
QueryKey? queryKey, QueryKey? queryKey,
QueryFilters? filters, QueryFilters? filters,
@@ -149,12 +151,13 @@ class QueryClient {
.toList(); .toList();
} }
QueryState<TData, TError>? getQueryState<TData, TError>( QueryState<TData, TError>?
getQueryState<TData extends Map<String, dynamic>, TError>(
QueryKey queryKey, [ QueryKey queryKey, [
QueryFilters? filters, QueryFilters? filters,
]) { ]) {
return _queryCache return _queryCache
.find<TData, TError, dynamic>( .find<TData, TError, Map<String, dynamic>>(
queryKey, queryKey,
filters ?? QueryFilters(), filters ?? QueryFilters(),
) )
@@ -257,13 +260,15 @@ class QueryClient {
return future; return future;
} }
Future<TData> fetchQuery<TQueryFnData, TError, TData>({ Future<TData> fetchQuery<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>({
QueryKey? queryKey, QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn, QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options, FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) { }) {
var defaultedOptions = this.defaultQueryOptions( var defaultedOptions = this.defaultQueryOptions(
QueryObserverOptions( QueryObserverOptions<Map<String, dynamic>, dynamic, Map<String, dynamic>,
TData>(
queryFn: queryFn, queryFn: queryFn,
queryKey: queryKey, queryKey: queryKey,
staleTime: options?.staleTime, staleTime: options?.staleTime,
@@ -280,28 +285,33 @@ class QueryClient {
); );
// returning 0 indicates turing off retry // returning 0 indicates turing off retry
defaultedOptions.retry ??= (_, __) => 0; 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) return query.isStaleByTime(defaultedOptions.staleTime)
? query.fetch(defaultedOptions) ? query.fetch(defaultedOptions)
: Future.value(query.state.data as TData); : 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, QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn, QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options, FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) { }) {
return fetchQuery( return fetchQuery<TQueryFnData, dynamic, TData>(
queryKey: queryKey, queryKey: queryKey,
queryFn: queryFn, queryFn: queryFn,
options: options, options: options,
).then(noop).catchError(noop); ).then(noop).catchError(noop);
} }
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> QueryObserverOptions<TQueryFnData, TError, TData,
defaultQueryOptions<TQueryFnData, TError, TData, TQueryData>( TQueryData> defaultQueryOptions<
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? TQueryFnData extends Map<String, dynamic>,
options) { TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options) {
if (options?.defaulted == true) return options!; if (options?.defaulted == true) return options!;
var defaultedOptions = var defaultedOptions =
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({ QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
@@ -322,7 +332,11 @@ class QueryClient {
} }
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> 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, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options,
]) { ]) {
return this.defaultQueryOptions(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:fl_query/src/core/utils.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
typedef QueryObserverListener<TData, TError> = void Function( typedef QueryObserverListener<TData extends Map<String, dynamic>, TError> = void
QueryObserverResult<TData, TError> result); Function(QueryObserverResult<TData, TError> result);
class NotifyOptions { class NotifyOptions {
bool? cache; bool? cache;
@@ -52,13 +52,18 @@ class ObserverFetchOptions extends FetchOptions {
}) : super(cancelRefetch: cancelRefetch, meta: meta); }) : 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 Function(TQueryData data) fn;
TData result; TData result;
SelectQuery(this.fn, this.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> { extends Subscribable<QueryObserverListener> {
late QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options; late QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options;
QueryClient _client; QueryClient _client;
@@ -70,7 +75,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
_currentResultOptions; _currentResultOptions;
QueryObserverResult<TData, TError>? _previousQueryResult; QueryObserverResult<TData, TError>? _previousQueryResult;
Exception? _previousSelectError; Exception? _previousSelectError;
SelectQuery? _previousSelect; SelectQuery<TQueryData, TData>? _previousSelect;
Timer? _staleTimeout; Timer? _staleTimeout;
Timer? _refetchInterval; Timer? _refetchInterval;
Duration? _currentRefetchInterval; Duration? _currentRefetchInterval;
@@ -119,8 +124,8 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options, [ QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options, [
NotifyOptions? notifyOptions, NotifyOptions? notifyOptions,
]) { ]) {
var prevOptions = this.options; final prevOptions = this.options;
var prevQuery = _currentQuery; final prevQuery = _currentQuery;
this.options = this._client.defaultQueryObserverOptions(options); this.options = this._client.defaultQueryObserverOptions(options);
@@ -190,7 +195,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Future<QueryObserverResult<TData, TError>> getNextResult( Future<QueryObserverResult<TData, TError>> getNextResult(
bool? throwOnError, bool? throwOnError,
) { ) {
var completer = Completer<QueryObserverResult<TData, TError>>(); final completer = Completer<QueryObserverResult<TData, TError>>();
var unsubscribe; var unsubscribe;
unsubscribe = subscribe((result) { unsubscribe = subscribe((result) {
if (!result.isFetching) { if (!result.isFetching) {
@@ -215,8 +220,8 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Future<QueryObserverResult<TData, TError>> fetchOptimistic( Future<QueryObserverResult<TData, TError>> fetchOptimistic(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options) { QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options) {
var defaultedOptions = _client.defaultQueryObserverOptions(options); final defaultedOptions = _client.defaultQueryObserverOptions(options);
var query = _client.getQueryCache().build(_client, defaultedOptions); final query = _client.getQueryCache().build(_client, defaultedOptions);
return query.fetch().then((val) { return query.fetch().then((val) {
return createResult(query, defaultedOptions); return createResult(query, defaultedOptions);
@@ -269,7 +274,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Map<String, dynamic> prevResultMap = prevResult.toJson(); Map<String, dynamic> prevResultMap = prevResult.toJson();
return resultMap.keys.any((key) { 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? isIncluded = includedProps?.any((x) => x == key);
bool isExcluded = bool isExcluded =
options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false; options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false;
@@ -280,7 +285,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
} }
void updateResult([NotifyOptions? notifyOptions]) { void updateResult([NotifyOptions? notifyOptions]) {
QueryObserverResult<TData, TError>? prevResult = _currentResult; final QueryObserverResult<TData, TError>? prevResult = _currentResult;
if (_currentQuery != null) if (_currentQuery != null)
_currentResult = this.createResult(_currentQuery!, this.options); _currentResult = this.createResult(_currentQuery!, this.options);
@@ -288,7 +293,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
_currentResultOptions = this.options; _currentResultOptions = this.options;
// Only notify if something has changed // Only notify if something has changed
if (shallowEqualMap(_currentResult.toJson(), prevResult.toJson())) { if (shallowEqualMap(_currentResult.toJson(), prevResult?.toJson())) {
return; return;
} }
NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true); NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true);
@@ -304,11 +309,12 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
} }
void _updateQuery() { 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; if (query == _currentQuery) return;
var prevQuery = _currentQuery; final prevQuery = _currentQuery;
_currentQuery = query; _currentQuery = query;
_currentQueryInitialState = query.state; _currentQueryInitialState = query.state;
_previousQueryResult = _currentResult; _previousQueryResult = _currentResult;
@@ -339,22 +345,22 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
Query<TQueryFnData, TError, TQueryData> query, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) { ) {
var prevQuery = _currentQuery; final prevQuery = _currentQuery;
var prevOptions = this.options; final prevOptions = this.options;
var prevResult = _currentResult; final prevResult = _currentResult;
var prevResultState = _currentResultState; final prevResultState = _currentResultState;
var prevResultOptions = _currentResultOptions; final prevResultOptions = _currentResultOptions;
bool queryChange = query != prevQuery; final bool queryChange = query != prevQuery;
var queryInitialState = final queryInitialState =
queryChange ? query.state : _currentQueryInitialState; queryChange ? query.state : _currentQueryInitialState;
var prevQueryResult = queryChange ? _currentResult : _previousQueryResult; final prevQueryResult = queryChange ? _currentResult : _previousQueryResult;
var state = query.state; final state = query.state;
var dataUpdatedAt = state.dataUpdatedAt; DateTime? dataUpdatedAt = state.dataUpdatedAt;
var error = state.error; TError? error = state.error;
var errorUpdatedAt = state.errorUpdatedAt; DateTime? errorUpdatedAt = state.errorUpdatedAt;
var isFetching = state.isFetching; bool isFetching = state.isFetching;
var status = state.status; QueryStatus status = state.status;
bool isPreviousData = false; bool isPreviousData = false;
bool isPlaceholderData = false; bool isPlaceholderData = false;
@@ -362,9 +368,9 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
// Optimistically set result in fetching state if needed // Optimistically set result in fetching state if needed
if (options.optimisticResults == true) { 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 && bool fetchOptionally = mounted &&
prevQuery != null && prevQuery != null &&
@@ -401,7 +407,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
try { try {
data = options.select?.call(state.data); data = options.select?.call(state.data);
if (options.structuralSharing != false) { if (options.structuralSharing != false) {
data = replaceEqualDeep(prevResult?.data, data); data = replaceEqualDeep(prevResult.data, data);
} }
if (options.select != null && data != null) { if (options.select != null && data != null) {
_previousSelect = SelectQuery<TQueryData, TData>( _previousSelect = SelectQuery<TQueryData, TData>(
@@ -429,7 +435,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
(status == QueryStatus.loading || status == QueryStatus.idle)) { (status == QueryStatus.loading || status == QueryStatus.idle)) {
var placeholderData; var placeholderData;
if (prevResult?.isPlaceholderData == true && if (prevResult.isPlaceholderData == true &&
options.placeholderData == prevResultOptions?.placeholderData) { options.placeholderData == prevResultOptions?.placeholderData) {
placeholderData = prevResult.data; placeholderData = prevResult.data;
} else { } else {
@@ -439,7 +445,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
placeholderData = options.select?.call(placeholderData); placeholderData = options.select?.call(placeholderData);
if (options.structuralSharing != false) { if (options.structuralSharing != false) {
placeholderData = placeholderData =
replaceEqualDeep(prevResult?.data, placeholderData); replaceEqualDeep(prevResult.data, placeholderData);
} }
_previousSelectError = null; _previousSelectError = null;
} catch (selectError) { } 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, status: status,
dataUpdatedAt: dataUpdatedAt, dataUpdatedAt: dataUpdatedAt,
isLoading: status == QueryStatus.loading, 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, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) { ) {
@@ -612,7 +623,11 @@ bool shouldLoadOnMount<TQueryFnData, TError, TData, TQueryData>(
options.retryOnMount == false)); 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, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) { ) {
@@ -623,7 +638,11 @@ bool shouldRefetchOnMount<TQueryFnData, TError, TData, TQueryData>(
isStale(query, options)))); 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, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) { ) {
@@ -631,17 +650,26 @@ bool shouldFetchOnMount<TQueryFnData, TError, TData, TQueryData>(
shouldRefetchOnMount(query, options)); 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, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) { ) {
return (options.enabled != false && return (options.enabled != false &&
(options.refetchOnReconnect == RefetchOnReconnect.always || (options.refetchOnReconnect == RefetchOnReconnect.always ||
(options.refetchOnReconnect != RefetchOnReconnect.off && (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> query,
Query<TQueryFnData, TError, TQueryData> prevQuery, Query<TQueryFnData, TError, TQueryData> prevQuery,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
@@ -653,7 +681,11 @@ bool shouldFetchOptionally<TQueryFnData, TError, TData, TQueryData>(
isStale(query, options)); 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, Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options, 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 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({bool? revert, bool? silent}) cancel;
late void Function() cancelRetry; late void Function() cancelRetry;
late void Function() continueRetry; 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/models.dart';
import 'package:fl_query/src/core/query.dart'; import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_key.dart'; import 'package:fl_query/src/core/query_key.dart';
import 'package:collection/collection.dart';
/// Default query keys hash function. /// Default query keys hash function.
/// Dummy function just to fill the gaps for original react-query like /// 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 /// If not, it will replace any deeply equal children of `b` with those
/// of `a`\ /// of `a`\
/// This can be used for structural sharing between JSON values for example. /// This can be used for structural sharing between JSON values for example.
/// `a` & `b` can only be Type of [Iterable] or [Map] /// `a` & `b` can only be Type of [List] or [Map]
T replaceEqualDeep<T>(T a, T b) { replaceEqualDeep(a, b) {
if (a == b) { if (a == b) {
return a; return a;
} }
bool isList = (a is Iterable && b is Iterable); int aSize;
if (isList || (a is Map && b is Map)) { List bItems;
int aSize = isList ? a.length : (a as Map).keys.length; int bSize;
List bItems = (isList ? b : (b as Map).keys).toList(); int equalItems = 0;
int bSize = bItems.length; onEqual() => equalItems++;
var copy; var copy;
if (a is List && b is List) {
int equalItems = 0; aSize = a.length;
bItems = b;
for (int i = 0; i < bSize; i++) { bSize = bItems.length;
var key = isList ? i : bItems[i]; copy = replaceEqualDeepList(a, b, onEqual);
if (isList) { } else if (a is Map && b is Map) {
copy ??= []; aSize = a.keys.length;
copy[key] = replaceEqualDeep((a as List)[key], (b as List)[key]); bItems = b.keys.toList();
if (copy[key] == a[key]) { bSize = bItems.length;
equalItems++; copy = replaceEqualDeepMap(a, b, onEqual);
} } else {
} else { return b;
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;
} }
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]) => 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()
// })
});
});
}