Single Query support completed

This commit is contained in:
Kingkor Roy Tirtho
2022-02-12 10:57:01 +06:00
parent 599453c82e
commit 70b84fe85a
4 changed files with 548 additions and 63 deletions
+190 -33
View File
@@ -1,10 +1,14 @@
import 'dart:async';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/retryer.dart';
typedef QueryMeta<T> = Map<String, T>;
typedef QueryKeyHashFunction = String Function(QueryKey queryKey);
typedef QueryFunction<T, TPageParam> = Function(QueryFunctionContext context);
typedef QueryFunction<T, TPageParam> = FutureOr<T> Function(
QueryFunctionContext<TPageParam> context,
);
typedef GetPreviousPageParamFunction<TQueryFnData> = Function(
TQueryFnData firstPage,
List<TQueryFnData> allPages,
@@ -64,6 +68,7 @@ class QueryOptions<TQueryFnData, TError, TData> {
this.structuralSharing,
this.getPreviousPageParam,
this.getNextPageParam,
this.behavior,
});
QueryOptions.fromJson(Map<String, dynamic> json) {
@@ -144,36 +149,40 @@ class RefetchPageFilters<TPageData> {
refetchPage;
}
class RefetchQueryFilters<TPageData>
implements QueryFilters, RefetchPageFilters<TPageData> {
@override
bool? active;
@override
bool? exact;
@override
bool? fetching;
@override
bool? inactive;
@override
bool Function(Query query)? predicate;
@override
bool? queryKey;
class RefetchableQueryFilters<TPageData> extends QueryFilters
implements RefetchPageFilters<TPageData> {
@override
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
refetchPage;
@override
bool? stale;
RefetchQueryFilters({
this.active,
this.exact,
this.inactive,
this.predicate,
this.queryKey,
this.stale,
this.fetching,
RefetchableQueryFilters({
bool? active,
bool? exact,
bool? inactive,
bool Function(Query query)? predicate,
bool? queryKey,
bool? stale,
bool? fetching,
this.refetchPage,
});
}) : super(
active: active,
exact: exact,
fetching: fetching,
inactive: inactive,
predicate: predicate,
queryKey: queryKey,
stale: stale,
);
RefetchableQueryFilters.fromJson(Map<String, dynamic> json) {
active = json["active"];
exact = json["exact"];
inactive = json["inactive"];
queryKey = json["queryKey"];
stale = json["stale"];
fetching = json["fetching"];
predicate = json["predicate"];
refetchPage = json["refetchPage"];
}
@override
Map<String, dynamic> toJson() {
@@ -185,19 +194,62 @@ class RefetchQueryFilters<TPageData>
"stale": stale,
"fetching": fetching,
"predicate": predicate,
"refetchPage": refetchPage,
};
}
}
class ResultOptions {
bool? throwOnError;
}
class InvalidateQueryFilters<TPageData>
extends RefetchableQueryFilters<TPageData> {
bool? refetchActive;
bool? refetchInactive;
class RefetchOptions implements ResultOptions {
bool? cancelRefetch;
InvalidateQueryFilters({
bool? active,
bool? exact,
bool? inactive,
bool Function(Query query)? predicate,
bool? queryKey,
bool? stale,
bool? fetching,
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
refetchPage,
this.refetchActive,
this.refetchInactive,
}) : super(
active: active,
exact: exact,
fetching: fetching,
inactive: inactive,
predicate: predicate,
queryKey: queryKey,
stale: stale,
refetchPage: refetchPage,
);
InvalidateQueryFilters.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
refetchActive = json["refetchActive"];
refetchInactive = json["refetchInactive"];
}
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"refetchActive": refetchActive,
"refetchInactive": refetchInactive,
};
}
}
class RefetchOptions {
bool? throwOnError;
bool? cancelRefetch;
RefetchOptions({
this.cancelRefetch,
this.throwOnError,
});
}
enum QueryStatus {
@@ -228,7 +280,7 @@ class QueryObserverResult<TData, TError> {
bool isSuccess;
Future<QueryObserverResult<TData, TError>> Function<TPageData>({
RefetchOptions options,
RefetchQueryFilters<TPageData> filters,
RefetchableQueryFilters<TPageData> filters,
}) refetch;
void Function() remove;
QueryStatus status;
@@ -402,6 +454,48 @@ class QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
structuralSharing: structuralSharing,
defaulted: defaulted,
);
QueryObserverOptions.fromJson(Map<String, dynamic> json)
: enabled = json["enabled"],
staleTime = json["staleTime"],
refetchInterval = json["refetchInterval"],
refetchIntervalInBackground = json["refetchIntervalInBackground"],
refetchOnReconnect = json["refetchOnReconnect"],
refetchOnMount = json["refetchOnMount"],
retryOnMount = json["retryOnMount"],
onSuccess = json["onSuccess"],
onError = json["onError"],
onSettled = json["onSettled"],
useErrorBoundary = json["useErrorBoundary"],
select = json["select"],
suspense = json["suspense"],
keepPreviousData = json["keepPreviousData"],
placeholderData = json["placeholderData"],
optimisticResults = json["optimisticResults"],
super.fromJson(json);
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"enabled": enabled,
"staleTime": staleTime,
"refetchInterval": refetchInterval,
"refetchIntervalInBackground": refetchIntervalInBackground,
"refetchOnReconnect": refetchOnReconnect,
"refetchOnMount": refetchOnMount,
"retryOnMount": retryOnMount,
"onSuccess": onSuccess,
"onError": onError,
"onSettled": onSettled,
"useErrorBoundary": useErrorBoundary,
"select": select,
"suspense": suspense,
"keepPreviousData": keepPreviousData,
"placeholderData": placeholderData,
"optimisticResults": optimisticResults,
};
}
}
class QueryFunctionContext<TPageParam> {
@@ -417,3 +511,66 @@ class QueryFunctionContext<TPageParam> {
this.meta,
});
}
class DefaultOptions<TError> {
QueryObserverOptions<dynamic, TError, dynamic, dynamic>? queries;
// MutationObserverOptions<dynamic, TError, dynamic>? mutations;
DefaultOptions({
this.queries,
});
}
class FetchQueryOptions<TQueryFnData, TError, TData>
extends QueryOptions<TQueryFnData, TError, TData> {
/**
* The time in milliseconds after data is considered stale.
* If the data is fresh it will be returned from the cache.
*/
Duration? staleTime;
FetchQueryOptions(
ShouldRetryFunction<TError>? retry,
RetryDelayFunction<TError>? retryDelay,
Duration? cacheTime,
bool Function(TData? oldData, TData newData)? isDataEqual,
QueryFunction? queryFn,
QueryKey? queryKey,
String? queryHash,
QueryKeyHashFunction? queryKeyHashFn,
TData? initialData,
DateTime? initialDataUpdatedAt,
QueryBehavior<TQueryFnData, TError, TData>? behavior,
bool? structuralSharing,
GetPreviousPageParamFunction<TQueryFnData>? getPreviousPageParam,
GetNextPageParamFunction<TQueryFnData>? getNextPageParam,
bool? defaulted,
this.staleTime,
) : super(
retry: retry,
retryDelay: retryDelay,
cacheTime: cacheTime,
isDataEqual: isDataEqual,
queryFn: queryFn,
queryKey: queryKey,
queryHash: queryHash,
queryKeyHashFn: queryKeyHashFn,
initialData: initialData,
initialDataUpdatedAt: initialDataUpdatedAt,
behavior: behavior,
structuralSharing: structuralSharing,
getPreviousPageParam: getPreviousPageParam,
getNextPageParam: getNextPageParam,
defaulted: defaulted,
);
FetchQueryOptions.fromJson(Map<String, dynamic> json)
: staleTime = json["staleTime"],
super.fromJson(json);
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"staleTime": staleTime,
};
}
}
@@ -73,9 +73,13 @@ class QueryCache extends Subscribable<QueryCacheListener> {
cache: this,
queryKey: queryKey,
queryHash: queryHash,
options: client.defaultQueryOptions(options),
options: client.defaultQueryOptions(
QueryObserverOptions.fromJson(options.toJson()),
),
state: state,
defaultOptions: client.getQueryDefaults(queryKey),
defaultOptions: QueryOptions.fromJson(
client.getQueryDefaults(queryKey)?.toJson() ?? {},
),
meta: options.meta,
);
add(query);
+348 -25
View File
@@ -1,48 +1,371 @@
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:fl_query/src/core/online_manager.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/query_observer.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:collection/collection.dart';
class QueryDefaults {
QueryKey queryKey;
QueryOptions defaultOptions;
QueryDefaults({
required this.queryKey,
required this.defaultOptions,
});
}
class MutationDefaults {
// QueryKey queryKey;
// QueryOptions defaultOptions;
// MutationDefaults({
// required this.queryKey,
// required this.defaultOptions,
// });
}
class QueryData<TData> {
QueryKey queryKey;
TData data;
QueryData({
required this.queryKey,
required this.data,
});
}
class QueryClient {
Object? options;
QueryCache _queryCache;
QueryCache _mutationCache;
// QueryCache _mutationCache;
DefaultOptions _defaultOptions;
List<QueryDefaults> _queryDefaults;
// List<MutationDefaults> _mutationDefaults;
void Function()? _unsubscribeFocus;
void Function()? _unsubscribeOnline;
// MutationKey _mutationKey;
// MutationOptions<any, any, any, any> _mutationDefaultOptions;
QueryClient({
QueryCache? queryCache,
QueryCache? mutationCache,
this.options,
DefaultOptions? defaultOptions,
}) : _queryCache = queryCache ?? QueryCache(),
_mutationCache = mutationCache ?? QueryCache() {}
_defaultOptions = defaultOptions ?? DefaultOptions(),
_queryDefaults = [];
/* _mutationDefaults = [], */
/* _mutationCache = mutationCache ?? QueryCache() */
void mount() {
// this.unsubscribeFocus = focusManager.subscribe(() => {
// if (focusManager.isFocused() && onlineManager.isOnline()) {
// this.mutationCache.onFocus()
// this.queryCache.onFocus()
// }
// })
_unsubscribeOnline = onlineManager.subscribe(() async {
if (/* focusManager.isFocused() && */ await onlineManager.isOnline()) {
// _mutationCache.onOnline();
_queryCache.onOnline();
}
});
}
void unmount() {
_unsubscribeFocus?.call();
_unsubscribeOnline?.call();
}
int isFetching({QueryKey? queryKey, QueryFilters? filters}) {
filters?.fetching = true;
return _queryCache.findAll(null, filters).length;
}
// int isMutating([MutationFilters? filters]) {
// return _mutationCache.findAll({ ...filters, fetching: true }).length
// }
TData? getQueryData<TData>(
QueryKey,
queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, dynamic, dynamic>(queryKey, filters ?? QueryFilters())
?.state
.data;
}
List<QueryData<TData>> getQueriesData<TData>({
QueryKey? queryKey,
QueryFilters? filters,
}) {
return getQueryCache().findAll(queryKey, filters).map((query) {
return QueryData<TData>(
data: query.state.data as TData,
queryKey: query.queryKey,
);
}).toList();
}
TData setQueryData<TData>(
QueryKey queryKey,
DataUpdateFunction<TData?, TData> updater, [
DateTime? updatedAt,
]) {
var defaultedOptions =
defaultQueryOptions(QueryObserverOptions(queryKey: queryKey));
return _queryCache.build(this, defaultedOptions).setData(
updater as Function(dynamic),
updatedAt: updatedAt,
);
}
List<QueryData> setQueriesData<TData>({
required DataUpdateFunction<TData?, TData> updater,
QueryKey? queryKey,
QueryFilters? filters,
DateTime? updatedAt,
}) {
if (queryKey == null && filters == null)
throw Exception(
"[QueryClient.setQueriesData] both `queryKey` & `filters` can't be null at the same time");
return notifyManager
.batch(() => getQueryCache().findAll(queryKey, filters).map(
(query) => QueryData(
queryKey: query.queryKey,
data: setQueryData<TData>(
query.queryKey,
updater,
updatedAt,
),
),
))
.toList();
}
QueryState<TData, TError>? getQueryState<TData, TError>(
QueryKey,
queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, TError, dynamic>(
queryKey,
filters ?? QueryFilters(),
)
?.state as QueryState<TData, TError>?;
}
void removeQueries({QueryKey? queryKey, QueryFilters? filters}) {
notifyManager.batch(
() => {
_queryCache.findAll(queryKey, filters).forEach((query) {
_queryCache.remove(query);
})
},
);
}
Future<void> resetQueries<TPageData>({
QueryKey? queryKey,
RefetchableQueryFilters<TPageData>? filters,
bool? throwOnError,
}) {
filters?.active = true;
var refetchFilters = RefetchableQueryFilters<TPageData>.fromJson({
...(filters?.toJson() ?? {}),
"active": true,
});
return notifyManager.batch(() {
_queryCache.findAll(queryKey, filters).forEach((query) {
query.reset();
});
return refetchQueries(
filters: refetchFilters,
options: RefetchOptions(throwOnError: throwOnError),
);
});
}
Future<void> cancelQueries({
QueryKey? queryKey,
QueryFilters? filters,
bool? revert = true,
bool? silent,
}) {
var futures = notifyManager.batch(() =>
_queryCache.findAll(queryKey, filters).map((query) => query.cancel(
revert: revert,
silent: silent,
)));
return Future.wait(futures).then(noop).catchError(noop);
}
Future<void> invalidateQueries<TPageData>({
QueryKey? queryKey,
InvalidateQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
var refetchFilters = RefetchableQueryFilters<TPageData>.fromJson({
...(filters?.toJson() ?? {}),
// if filters.refetchActive is not provided and filters.active is explicitly false,
// e.g. invalidateQueries({ active: false }), we don't want to refetch active queries
"active": filters?.refetchActive ?? filters?.active ?? true,
"inactive": filters?.refetchInactive ?? false,
});
return notifyManager.batch(() {
_queryCache.findAll(queryKey, filters).forEach((query) {
query.invalidate();
});
return this.refetchQueries(
filters: refetchFilters,
options: options,
);
});
}
Future<void> refetchQueries<TPageData>({
QueryKey? queryKey,
RefetchableQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
var futures = notifyManager.batch(
() => _queryCache.findAll(queryKey, filters).map(
(query) => query.fetch(
null,
ObserverFetchOptions(
cancelRefetch: options?.cancelRefetch,
throwOnError: options?.throwOnError,
meta: {"refetchPage": filters?.refetchPage},
),
),
),
);
var future = Future.wait(futures).then(noop);
if (options?.throwOnError == false) {
future = future.catchError(noop);
}
return future;
}
Future<TData> fetchQuery<TQueryFnData, TError, TData>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
var defaultedOptions = this.defaultQueryOptions(
QueryObserverOptions(
queryFn: queryFn,
queryKey: queryKey,
staleTime: options?.staleTime,
cacheTime: options?.cacheTime,
defaulted: options?.defaulted,
initialData: options?.initialData,
initialDataUpdatedAt: options?.initialDataUpdatedAt,
isDataEqual: options?.isDataEqual,
meta: options?.meta,
queryHash: options?.queryHash,
queryKeyHashFn: options?.queryKeyHashFn,
structuralSharing: options?.structuralSharing,
),
);
// returning 0 indicates turing off retry
defaultedOptions.retry ??= (_, __) => 0;
var query = _queryCache.build(this, defaultedOptions);
return query.isStaleByTime(defaultedOptions.staleTime)
? query.fetch(defaultedOptions)
: Future.value(query.state.data as TData);
}
Future<void> prefetchQuery<TQueryFnData, TError, TData>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
return fetchQuery(
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) {}
options) {
if (options?.defaulted == true) return options!;
var defaultedOptions =
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
...(_defaultOptions.queries?.toJson() ?? {}),
...(getQueryDefaults(options?.queryKey)?.toJson() ?? {}),
...(options?.toJson() ?? {}),
"defaulted": true,
});
if (defaultedOptions.queryHash == null &&
defaultedOptions.queryKey != null) {
defaultedOptions.queryHash = hashQueryKeyByOptions(
defaultedOptions.queryKey!,
defaultedOptions,
);
}
return defaultedOptions;
}
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
defaultQueryObserverOptions<TQueryFnData, TError, TData, TQueryData>(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>?
options) {
defaultQueryObserverOptions<TQueryFnData, TError, TData, TQueryData>([
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options,
]) {
return this.defaultQueryOptions(options);
}
fetchQuery() {}
getQueryData() {}
setQueryData() {}
getQueryState() {}
invalidateQueries() {}
refetchQueries() {}
cancelQueries() {}
removeQueries() {}
resetQueries() {}
bool get isFetching => false;
bool get isMutating => false;
getDefaultOptions() {}
setDefaultOptions() {}
getQueryDefaults() {}
setQueryDefaults() {}
DefaultOptions getDefaultOptions() {
return _defaultOptions;
}
void setDefaultOptions(DefaultOptions options) {
_defaultOptions = options;
}
QueryObserverOptions? getQueryDefaults([QueryKey? queryKey]) {
return queryKey != null
? QueryObserverOptions.fromJson((_queryDefaults
.firstWhereOrNull(
(x) => queryKey.key == x.queryKey.key,
)
?.defaultOptions)
?.toJson() ??
{})
: null;
}
void setQueryDefaults(QueryKey queryKey, QueryObserverOptions options) {
var result = _queryDefaults.firstWhereOrNull(
(x) => queryKey.key == x.queryKey.key,
);
if (result != null) {
result.defaultOptions = options;
} else {
_queryDefaults
.add(QueryDefaults(queryKey: queryKey, defaultOptions: options));
}
}
getMutationDefaults() {}
setMutationDefaults() {}
QueryCache getQueryCache() {}
QueryCache getQueryCache() {
return _queryCache;
}
getMutationCache() {}
clear() {}
void clear() {
_queryCache.clear();
// _mutationCache.clear();
}
}
@@ -188,13 +188,14 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
Future<QueryObserverResult<TData, TError>> getNextResult(
ResultOptions? options) {
bool? throwOnError,
) {
var completer = Completer<QueryObserverResult<TData, TError>>();
var unsubscribe;
unsubscribe = subscribe((result) {
if (!result.isFetching) {
unsubscribe?.call();
if (result.isError && options?.throwOnError == true) {
if (result.isError && throwOnError == true) {
if (!completer.isCompleted)
completer.completeError(result.error as Object);
} else {
@@ -588,7 +589,7 @@ class QueryObserver<TQueryFnData, TError, TData, TQueryData>
}
Future<QueryObserverResult<TData, TError>> refetch<TPageData>({
RefetchQueryFilters<TPageData>? filters,
RefetchableQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
return fetch(