diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0da109f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "editor.tokenColorCustomizations": { - "comments": "", - "textMateRules": [] - } -} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 90995f5..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# fl-query - -Flutter Query, the asynchronous data fetching & invalidation library for Flutter \ No newline at end of file diff --git a/packages/fl_query/.gitignore b/packages/fl_query/.gitignore deleted file mode 100644 index 9be145f..0000000 --- a/packages/fl_query/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -**/doc/api/ -.dart_tool/ -.packages -build/ diff --git a/packages/fl_query/.metadata b/packages/fl_query/.metadata deleted file mode 100644 index af84dae..0000000 --- a/packages/fl_query/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b - channel: stable - -project_type: package diff --git a/packages/fl_query/CHANGELOG.md b/packages/fl_query/CHANGELOG.md deleted file mode 100644 index 41cc7d8..0000000 --- a/packages/fl_query/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.0.1 - -* TODO: Describe initial release. diff --git a/packages/fl_query/LICENSE b/packages/fl_query/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/packages/fl_query/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/packages/fl_query/README.md b/packages/fl_query/README.md deleted file mode 100644 index 8b55e73..0000000 --- a/packages/fl_query/README.md +++ /dev/null @@ -1,39 +0,0 @@ - - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. - -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - -```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. diff --git a/packages/fl_query/analysis_options.yaml b/packages/fl_query/analysis_options.yaml deleted file mode 100644 index a5744c1..0000000 --- a/packages/fl_query/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/packages/fl_query/example/.gitignore b/packages/fl_query/example/.gitignore deleted file mode 100644 index 3c8a157..0000000 --- a/packages/fl_query/example/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Files and directories created by pub. -.dart_tool/ -.packages - -# Conventional directory for build output. -build/ diff --git a/packages/fl_query/example/CHANGELOG.md b/packages/fl_query/example/CHANGELOG.md deleted file mode 100644 index effe43c..0000000 --- a/packages/fl_query/example/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 1.0.0 - -- Initial version. diff --git a/packages/fl_query/example/README.md b/packages/fl_query/example/README.md deleted file mode 100644 index a307539..0000000 --- a/packages/fl_query/example/README.md +++ /dev/null @@ -1 +0,0 @@ -A simple command-line application. diff --git a/packages/fl_query/example/analysis_options.yaml b/packages/fl_query/example/analysis_options.yaml deleted file mode 100644 index dee8927..0000000 --- a/packages/fl_query/example/analysis_options.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -# Uncomment the following section to specify additional rules. - -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options diff --git a/packages/fl_query/example/bin/example.dart b/packages/fl_query/example/bin/example.dart deleted file mode 100644 index 8240894..0000000 --- a/packages/fl_query/example/bin/example.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:fl_query/fl_query.dart'; - -var todos = [ - {"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}, - { - "userId": 1, - "id": 2, - "title": "quis ut nam facilis et officia qui", - "completed": false - }, - {"userId": 1, "id": 3, "title": "fugiat veniam minus", "completed": false}, - {"userId": 1, "id": 4, "title": "et porro tempora", "completed": true}, - { - "userId": 1, - "id": 5, - "title": "laboriosam mollitia et enim quasi adipisci quia provident illum", - "completed": false - }, - { - "userId": 1, - "id": 6, - "title": "qui ullam ratione quibusdam voluptatem quia omnis", - "completed": false - }, -]; - -void main() async { - try { - var key = QueryKey("TEST"); - QueryClient queryClient = QueryClient(); - queryClient.mount(); - var data = await queryClient - .fetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (context) { - return Future.value(todos.first); - }, - ); - print("======FETCHED DATA======"); - print(data); - print("======CACHED DATA======"); - print(queryClient.getQueryData(key)); - queryClient.setQueryData>(key, (prevData) { - return { - ...(prevData) ?? {}, - "title": "Yehi aloh heh", - "completed": true, - }; - }); - print("======CACHED DATA======"); - print(queryClient.getQueryData(key)); - print("======STATE======"); - print(queryClient.getQueryState(key)?.toJson()); - } catch (e) { - print(e); - } -} diff --git a/packages/fl_query/example/pubspec.lock b/packages/fl_query/example/pubspec.lock deleted file mode 100644 index 97410f2..0000000 --- a/packages/fl_query/example/pubspec.lock +++ /dev/null @@ -1,124 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - fl_query: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" - hive: - dependency: transitive - description: - name: hive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.4" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - internet_connection_checker: - dependency: transitive - description: - name: internet_connection_checker - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1+3" - lints: - dependency: "direct dev" - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.5" -sdks: - dart: ">=2.15.1 <3.0.0" diff --git a/packages/fl_query/example/pubspec.yaml b/packages/fl_query/example/pubspec.yaml deleted file mode 100644 index 2fcdf31..0000000 --- a/packages/fl_query/example/pubspec.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: example -description: A simple command-line application. -version: 1.0.0 -# homepage: https://www.example.com -publish_to: none - -environment: - sdk: '>=2.15.1 <3.0.0' - - -dependencies: - fl_query: - path: ../ - -dev_dependencies: - lints: ^1.0.0 diff --git a/packages/fl_query/lib/fl_query.dart b/packages/fl_query/lib/fl_query.dart deleted file mode 100644 index a44b61b..0000000 --- a/packages/fl_query/lib/fl_query.dart +++ /dev/null @@ -1,4 +0,0 @@ -library fl_query; - -export 'package:fl_query/src/core/core.dart'; -// export 'package:fl_query/src/core/framework.dart'; diff --git a/packages/fl_query/lib/src/core/core.dart b/packages/fl_query/lib/src/core/core.dart deleted file mode 100644 index c00ff42..0000000 --- a/packages/fl_query/lib/src/core/core.dart +++ /dev/null @@ -1,21 +0,0 @@ -export 'package:fl_query/src/core/retryer.dart' show CancelledError; -export 'package:fl_query/src/core/query_cache.dart' show QueryCache; -export 'package:fl_query/src/core/query_client.dart' show QueryClient; -export 'package:fl_query/src/core/query_observer.dart' show QueryObserver; -export 'package:fl_query/src/core/query_key.dart'; -// export 'package:fl_query/src/core/queriesObserver.dart' show QueriesObserver; -// export 'package:fl_query/src/core/infiniteQueryObserver.dart' show InfiniteQueryObserver; -// export 'package:fl_query/src/core/mutationCache.dart' show MutationCache; -// export 'package:fl_query/src/core/mutationObserver.dart' show MutationObserver; -// export 'package:fl_query/src/core/logger.dart' show setLogger; -export 'package:fl_query/src/core/notify_manager.dart' show notifyManager; -// export 'package:fl_query/src/core/focusManager.dart' show focusManager; -export 'package:fl_query/src/core/online_manager.dart' show onlineManager; -export 'package:fl_query/src/core/utils.dart' show hashQueryKey; -export 'package:fl_query/src/core/retryer.dart' show isCancelledError; -// export 'package:fl_query/src/core/hydration.dart' show dehydrate, DehydrateOptions, DehydratedState, HydrateOptions, ShouldDehydrateMutationFunction, ShouldDehydrateQueryFunction; - -export 'package:fl_query/src/core/models.dart'; -export 'package:fl_query/src/core/query.dart' show Query; -// export type { Mutation } from './mutation' -// export type { Logger } from './logger' \ No newline at end of file diff --git a/packages/fl_query/lib/src/core/models.dart b/packages/fl_query/lib/src/core/models.dart deleted file mode 100644 index 2abc42c..0000000 --- a/packages/fl_query/lib/src/core/models.dart +++ /dev/null @@ -1,596 +0,0 @@ -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 = Map; -typedef QueryKeyHashFunction = String Function(QueryKey queryKey); -typedef QueryFunction = FutureOr Function( - QueryFunctionContext context, -); -typedef GetPreviousPageParamFunction> - = Function( - TQueryFnData firstPage, - List allPages, -); -typedef GetNextPageParamFunction> - = Function( - TQueryFnData firstPage, - List allPages, -); - -class QueryOptions, TError, - TData extends Map> { - ShouldRetryFunction? retry; - RetryDelayFunction? retryDelay; - Duration? cacheTime; - bool Function(TData? oldData, TData newData)? isDataEqual; - QueryFunction? queryFn; - QueryKey? queryKey; - - /// Basically [QueryKey.key] in short form - String? queryHash; - QueryKeyHashFunction? queryKeyHashFn; - TData? initialData; - DateTime? initialDataUpdatedAt; - QueryBehavior? behavior; - - /// Set this to `false` to disable structural sharing between query results\ - /// Defaults to `true`. - bool? structuralSharing; - - /// This function can be set to automatically get the previous cursor for infinite queries. - /// The result will also be used to determine the value of `hasPreviousPage`. - GetPreviousPageParamFunction? getPreviousPageParam; - - /// This function can be set to automatically get the next cursor for - /// infinite queries. - /// The result will also be used to determine the value of - /// `hasNextPage`. - GetNextPageParamFunction? getNextPageParam; - bool? defaulted; - - /// Additional payload to be stored on each query. - /// Use this property to pass information that can be used in other places. - QueryMeta? meta; - - QueryOptions({ - this.retry, - this.retryDelay, - this.queryKey, - this.queryKeyHashFn, - this.cacheTime, - this.isDataEqual, - this.queryFn, - this.defaulted, - this.initialData, - this.initialDataUpdatedAt, - this.meta, - this.queryHash, - this.structuralSharing, - this.getPreviousPageParam, - this.getNextPageParam, - this.behavior, - }); - - QueryOptions.fromJson(Map json) { - queryKey = json["queryKey"]; - queryKeyHashFn = json["queryKeyHashFn"]; - cacheTime = json["cacheTime"]; - isDataEqual = json["isDataEqual"]; - queryFn = json["queryFn"]; - queryHash = json["queryHash"]; - initialData = json["initialData"]; - initialDataUpdatedAt = json["initialDataUpdatedAt"]; - meta = json["meta"]; - structuralSharing = json["structuralSharing"]; - defaulted = json["defaulted"]; - retry = json["retry"]; - retryDelay = json["retryDelay"]; - behavior = json["behavior"]; - getPreviousPageParam = json["getPreviousPageParam"]; - getNextPageParam = json["getNextPageParam"]; - } - - Map toJson() { - return { - "queryKey": queryKey, - "queryKeyHashFn": queryKeyHashFn, - "cacheTime": cacheTime, - "isDataEqual": isDataEqual, - "queryFn": queryFn, - "queryHash": queryHash, - "initialData": initialData, - "initialDataUpdatedAt": initialDataUpdatedAt, - "meta": meta, - "structuralSharing": structuralSharing, - "defaulted": defaulted, - "retry": retry, - "retryDelay": retryDelay, - "behavior": behavior, - "getPreviousPageParam": getPreviousPageParam, - "getNextPageParam": getNextPageParam, - }; - } -} - -class QueryFilters { - bool? active; - bool? exact; - bool? inactive; - bool Function(Query query)? predicate; - QueryKey? queryKey; - bool? stale; - bool? fetching; - - QueryFilters({ - this.active, - this.exact, - this.inactive, - this.predicate, - this.queryKey, - this.stale, - this.fetching, - }); - - Map toJson() { - return { - "active": active, - "exact": exact, - "inactive": inactive, - "queryKey": queryKey, - "stale": stale, - "fetching": fetching, - "predicate": predicate, - }; - } -} - -class RefetchPageFilters { - bool Function(TPageData lastPage, int index, List allPages)? - refetchPage; -} - -class RefetchableQueryFilters extends QueryFilters - implements RefetchPageFilters { - @override - bool Function(TPageData lastPage, int index, List allPages)? - refetchPage; - RefetchableQueryFilters({ - bool? active, - bool? exact, - bool? inactive, - bool Function(Query query)? predicate, - QueryKey? 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 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 toJson() { - return { - "active": active, - "exact": exact, - "inactive": inactive, - "queryKey": queryKey, - "stale": stale, - "fetching": fetching, - "predicate": predicate, - "refetchPage": refetchPage, - }; - } -} - -class InvalidateQueryFilters - extends RefetchableQueryFilters { - bool? refetchActive; - bool? refetchInactive; - - InvalidateQueryFilters({ - bool? active, - bool? exact, - bool? inactive, - bool Function(Query query)? predicate, - QueryKey? queryKey, - bool? stale, - bool? fetching, - bool Function(TPageData lastPage, int index, List 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 json) - : super.fromJson(json) { - refetchActive = json["refetchActive"]; - refetchInactive = json["refetchInactive"]; - } - - @override - Map toJson() { - return { - ...super.toJson(), - "refetchActive": refetchActive, - "refetchInactive": refetchInactive, - }; - } -} - -class RefetchOptions { - bool? throwOnError; - bool? cancelRefetch; - RefetchOptions({ - this.cancelRefetch, - this.throwOnError, - }); -} - -enum QueryStatus { - idle, - loading, - error, - success, -} - -class QueryObserverResult, TError> { - TData? data; - DateTime? dataUpdatedAt; - TError? error; - DateTime? errorUpdatedAt; - int failureCount; - bool isError; - bool isFetched; - bool isFetchedAfterMount; - bool isFetching; - bool isIdle; - bool isLoading; - bool isLoadingError; - bool isPlaceholderData; - bool isPreviousData; - bool isRefetchError; - bool isRefetching; - bool isStale; - bool isSuccess; - Future?> Function({ - RefetchOptions options, - RefetchableQueryFilters filters, - }) refetch; - void Function() remove; - QueryStatus status; - - QueryObserverResult({ - required this.failureCount, - required this.isError, - required this.isFetched, - required this.isFetchedAfterMount, - required this.isFetching, - required this.isIdle, - required this.isLoading, - required this.isLoadingError, - required this.isPlaceholderData, - required this.isPreviousData, - required this.isRefetchError, - required this.isRefetching, - required this.isStale, - required this.isSuccess, - required this.refetch, - required this.remove, - required this.status, - this.data, - this.error, - this.dataUpdatedAt, - this.errorUpdatedAt, - }) { - String errorLabel = - "[QueryObserverResult.QueryObserverResult] status = `$status` but parent has wrong set of properties"; - if (status == QueryStatus.idle && - (data != null || - error != null || - isError || - !isIdle || - isLoading || - isLoadingError || - isRefetchError || - isSuccess)) throw Exception(errorLabel); - - if (status == QueryStatus.loading && - (data != null || - error != null || - isError || - isIdle || - !isLoading || - isLoadingError || - isRefetchError || - isSuccess != false)) throw Exception(errorLabel); - - if (status == QueryStatus.error && - ((!(error is TError)) || !isError || isIdle || isLoading || isSuccess)) - throw Exception(errorLabel); - - if (status == QueryStatus.success && - (!(data is TData) || - error != null || - isError || - isIdle || - isLoading || - isLoadingError || - isRefetchError || - !isSuccess)) throw Exception(errorLabel); - } - - Map toJson() { - final Map data = { - 'data': this.data, - 'dataUpdatedAt': dataUpdatedAt, - 'error': error, - 'errorUpdatedAt': errorUpdatedAt, - 'failureCount': failureCount, - 'isError': isError, - 'isFetched': isFetched, - 'isFetchedAfterMount': isFetchedAfterMount, - 'isFetching': isFetching, - 'isIdle': isIdle, - 'isLoading': isLoading, - 'isLoadingError': isLoadingError, - 'isPlaceholderData': isPlaceholderData, - 'isPreviousData': isPreviousData, - 'isRefetchError': isRefetchError, - 'isRefetching': isRefetching, - 'isStale': isStale, - 'isSuccess': isSuccess, - 'refetch': refetch, - 'remove': remove, - 'status': status, - }; - return data; - } -} - -typedef RefetchIntervalFunction< - TQueryFnData extends Map, - TError, - TQueryData extends Map, - TData extends Map> - = Duration? Function( - TData? data, - Query query, -); - -enum RefetchOnReconnect { - on, - off, - always, -} - -enum RefetchOnMount { - on, - off, - always, -} - -class QueryObserverOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map> - extends QueryOptions { - bool? enabled; - Duration? staleTime; - RefetchIntervalFunction? - refetchInterval; - bool? refetchIntervalInBackground; - RefetchOnReconnect? refetchOnReconnect; - RefetchOnMount? refetchOnMount; - bool? retryOnMount; - OnData? onSuccess; - OnError? onError; - void Function(TData? data, [TError? error])? onSettled; - bool Function(TError error)? useErrorBoundary; - TData Function(TQueryData? data)? select; - bool? suspense; - bool? keepPreviousData; - TQueryData? placeholderData; - bool? optimisticResults; - /*List|'tracked'?*/ dynamic notifyOnChangeProps; - List? notifyOnChangePropsExclusions; - - QueryObserverOptions({ - this.enabled, - this.staleTime, - this.refetchInterval, - this.refetchIntervalInBackground, - this.refetchOnReconnect, - this.refetchOnMount, - this.retryOnMount, - this.onSuccess, - this.onError, - this.onSettled, - this.useErrorBoundary, - this.select, - this.suspense, - this.keepPreviousData, - this.placeholderData, - this.optimisticResults, - QueryKey? queryKey, - QueryKeyHashFunction? queryKeyHashFn, - Duration? cacheTime, - bool Function(TQueryData? oldData, TQueryData newData)? isDataEqual, - QueryFunction? queryFn, - String? queryHash, - TQueryData? initialData, - DateTime? initialDataUpdatedAt, - QueryMeta? meta, - bool? structuralSharing, - bool? defaulted, - ShouldRetryFunction? retry, - RetryDelayFunction? retryDelay, - QueryBehavior? behavior, - GetPreviousPageParamFunction? getPreviousPageParam, - GetNextPageParamFunction? getNextPageParam, - }) : super( - queryKey: queryKey, - queryKeyHashFn: queryKeyHashFn, - cacheTime: cacheTime, - isDataEqual: isDataEqual, - queryFn: queryFn, - queryHash: queryHash, - initialData: initialData, - initialDataUpdatedAt: initialDataUpdatedAt, - meta: meta, - structuralSharing: structuralSharing, - defaulted: defaulted, - behavior: behavior, - getNextPageParam: getNextPageParam, - getPreviousPageParam: getPreviousPageParam, - retry: retry, - retryDelay: retryDelay, - ); - - QueryObserverOptions.fromJson(Map 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 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 { - QueryKey queryKey; - /* AbortSignal */ dynamic? signal; - TPageParam? pageParam; - QueryMeta? meta; - - QueryFunctionContext({ - required this.queryKey, - this.signal, - this.pageParam, - this.meta, - }); -} - -class DefaultOptions { - QueryObserverOptions? queries; - // MutationObserverOptions? mutations; - DefaultOptions({ - this.queries, - }); -} - -class FetchQueryOptions, TError, - TData extends Map> - extends QueryOptions { - /// The time after data is considered stale. - /// If the data is fresh it will be returned from the cache. - Duration? staleTime; - FetchQueryOptions({ - ShouldRetryFunction? retry, - RetryDelayFunction? retryDelay, - Duration? cacheTime, - bool Function(TData? oldData, TData newData)? isDataEqual, - QueryFunction? queryFn, - QueryKey? queryKey, - String? queryHash, - QueryKeyHashFunction? queryKeyHashFn, - TData? initialData, - DateTime? initialDataUpdatedAt, - QueryBehavior? behavior, - bool? structuralSharing, - GetPreviousPageParamFunction? getPreviousPageParam, - GetNextPageParamFunction? 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 json) - : staleTime = json["staleTime"], - super.fromJson(json); - - @override - Map toJson() { - return { - ...super.toJson(), - "staleTime": staleTime, - }; - } -} diff --git a/packages/fl_query/lib/src/core/notify_manager.dart b/packages/fl_query/lib/src/core/notify_manager.dart deleted file mode 100644 index 8acdc71..0000000 --- a/packages/fl_query/lib/src/core/notify_manager.dart +++ /dev/null @@ -1,95 +0,0 @@ -// TYPES - -import 'dart:async'; - -typedef NotifyCallback = void Function(); - -typedef NotifyFunction = void Function(void Function() callback); - -typedef BatchNotifyFunction = void Function(void Function() callback); - -class NotifyManager { - List _queue; - int _transactions; - late NotifyFunction _notifyFn; - late BatchNotifyFunction _batchNotifyFn; - - NotifyManager() - : _queue = [], - _transactions = 0 { - _notifyFn = (void Function() callback) { - callback(); - }; - - _batchNotifyFn = (void Function() callback) { - callback(); - }; - } - - T batch(T Function() callback) { - final T result; - _transactions++; - try { - result = callback(); - } finally { - _transactions--; - if (_transactions == 0) { - flush(); - } - } - return result; - } - - void schedule(NotifyCallback callback) { - if (_transactions > 0) { - _queue.add(callback); - } else { - scheduleMicrotask(() { - _notifyFn(callback); - }); - } - } - - /// All calls to the wrapped function will be batched. - T batchCalls(T callback) { - void fn(List? args) { - schedule(() { - callback(args); - }); - } - - ; - return fn as T; - } - - void flush() { - var queue = _queue; - _queue = []; - if (queue.isNotEmpty) { - scheduleMicrotask(() { - _batchNotifyFn(() { - queue.forEach((fn) { - _notifyFn(fn); - }); - }); - }); - } - } - - ///Use this method to set a custom notify function. - void setNotifyFunction(NotifyFunction fn) { - _notifyFn = fn; - } - - /// Use this method to set a custom function to batch notifications - /// together into a single tick. - /// By default React Query will use the batch function provided by - /// ReactDOM or React Native. - void setBatchNotifyFunction(BatchNotifyFunction fn) { - _batchNotifyFn = fn; - } -} - -// SINGLETON - -NotifyManager notifyManager = new NotifyManager(); diff --git a/packages/fl_query/lib/src/core/online_manager.dart b/packages/fl_query/lib/src/core/online_manager.dart deleted file mode 100644 index cfc15ea..0000000 --- a/packages/fl_query/lib/src/core/online_manager.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:internet_connection_checker/internet_connection_checker.dart'; - -typedef SetupFn = void Function()? Function( - void Function([bool? online]) setOnline); - -class OnlineManager extends Subscribable { - bool? _online; - void Function()? _cleanup; - SetupFn? _setup; - - OnlineManager([InternetConnectionChecker? connectionChecker]) { - connectionChecker ??= InternetConnectionChecker(); - _setup = (listener) { - var subscription = - connectionChecker!.onStatusChange.listen((status) => listener()); - return () { - subscription.cancel(); - }; - }; - } - - @override - void onSubscribe() { - if (_cleanup == null) { - setEventListener(_setup!); - } - } - - @override - void onUnsubscribe() { - if (!hasListeners()) { - _cleanup?.call(); - _cleanup = null; - } - } - - void setEventListener(SetupFn setup) { - _setup = setup; - _cleanup?.call(); - _cleanup = setup(([bool? online]) { - if (online != null) { - setOnline(online); - } else { - onOnline(); - } - }); - } - - void setOnline(bool? online) { - _online = online; - if (online != null && online) { - onOnline(); - } - } - - void onOnline() { - listeners.forEach((listener) { - listener(); - }); - } - - Future isOnline() { - if (_online != null) { - return Future.value(_online!); - } - - return InternetConnectionChecker().hasConnection; - } -} - -OnlineManager onlineManager = OnlineManager(); diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart deleted file mode 100644 index 8223f23..0000000 --- a/packages/fl_query/lib/src/core/query.dart +++ /dev/null @@ -1,666 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:convert'; -import 'dart:math'; - -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.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/retryer.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:meta/meta.dart'; -import 'package:collection/collection.dart'; - -class FetchOptions { - bool? cancelRefetch; - dynamic meta; - FetchOptions({this.cancelRefetch, this.meta}); -} - -class FetchContext, TError, - TData extends Map> { - FutureOr Function() fetchFn; - FetchOptions? fetchOptions; - QueryOptions options; - QueryKey queryKey; - QueryState state; - QueryMeta? meta; - - FetchContext({ - required this.fetchFn, - required this.options, - required this.queryKey, - required this.state, - this.meta, - this.fetchOptions, - }); -} - -class QueryBehavior, TError, - TData extends Map> { - void Function(FetchContext context) onFetch; - QueryBehavior({required this.onFetch}); -} - -class QueryState, TError> { - TData? data; - TError? error; - QueryStatus status; - DateTime? dataUpdatedAt; - int dataUpdateCount; - DateTime? errorUpdatedAt; - int errorUpdateCount; - int fetchFailureCount; - dynamic fetchMeta; - bool isFetching; - bool isInvalidated; - bool isPaused; - - QueryState({ - required this.status, - required this.dataUpdatedAt, - required this.dataUpdateCount, - required this.errorUpdatedAt, - required this.errorUpdateCount, - required this.fetchFailureCount, - required this.fetchMeta, - required this.isFetching, - required this.isInvalidated, - required this.isPaused, - this.data, - this.error, - }); - - QueryState.fromJson(Map json) - : data = json["data"], - error = json["error"], - status = json["status"], - dataUpdatedAt = json["dataUpdatedAt"], - dataUpdateCount = json["dataUpdateCount"], - errorUpdatedAt = json["errorUpdatedAt"], - errorUpdateCount = json["errorUpdateCount"], - fetchFailureCount = json["fetchFailureCount"], - fetchMeta = json["fetchMeta"], - isFetching = json["isFetching"], - isInvalidated = json["isInvalidated"], - isPaused = json["isPaused"]; - - Map toJson() { - return { - "data": data, - "error": error, - "status": status, - "dataUpdatedAt": dataUpdatedAt, - "dataUpdateCount": dataUpdateCount, - "errorUpdatedAt": errorUpdatedAt, - "errorUpdateCount": errorUpdateCount, - "fetchFailureCount": fetchFailureCount, - "fetchMeta": fetchMeta, - "isFetching": isFetching, - "isInvalidated": isInvalidated, - "isPaused": isPaused, - }; - } -} - -enum ActionType { - failed, - fetch, - success, - error, - invalidate, - pause, - resume, - setState, -} - -class SetStateOptions { - Object? meta; - SetStateOptions({this.meta}); - Map toJson() { - return {"meta": meta}; - } -} - -class Action, TError> { - ActionType type; - Object? meta; - TData? data; - DateTime? dataUpdatedAt; - TError? error; - QueryState? state; - SetStateOptions? setStateOptions; - - Action( - this.type, { - this.meta, - this.data, - this.dataUpdatedAt, - this.error, - this.state, - this.setStateOptions, - }) { - if (type == ActionType.error && error == null) - throw Exception( - "[Action.Action] property `error` can't be null when `type` = `$type`"); - - if (type == ActionType.setState && state == null) - throw Exception( - "[Action.Action] property `state` can't be null when `type` = `$type`"); - } - - Map toJson() { - return { - "type": type, - "meta": meta, - "data": data, - "dataUpdatedAt": dataUpdatedAt, - "error": error, - "state": state, - "setStateOptions": setStateOptions, - }; - } -} - -class Query, TError, - TData extends Map> { - QueryKey queryKey; - String queryHash; - late QueryOptions options; - late QueryState initialState; - QueryState? revertState; - late QueryState state; - Duration? cacheTime; - QueryMeta? meta; - - QueryCache _cache; - // Future? _future; - Completer? _completer; - Timer? _gcTimeout; - Retryer? _retryer; - List _observers; - QueryOptions? _defaultOptions; - bool _abortSignalConsumed; - bool _hadObservers; - - Query({ - required this.queryKey, - required this.queryHash, - required QueryCache cache, - QueryOptions? options, - QueryOptions? defaultOptions, - QueryState? state, - QueryMeta? meta, - }) : _abortSignalConsumed = false, - _hadObservers = false, - _defaultOptions = defaultOptions, - _observers = [], - _cache = cache { - _setOptions(options); - initialState = state ?? _getDefaultState(this.options); - this.state = initialState; - this.meta = meta; - _scheduleGc(); - } - - void _scheduleGc() { - this._clearGcTimeout(); - if (this.cacheTime != null) { - _gcTimeout = Timer(cacheTime!, () { - this._optionalRemove(); - }); - } - } - - void _clearGcTimeout() { - _gcTimeout?.cancel(); - _gcTimeout = null; - } - - void _optionalRemove() { - if (_observers.isEmpty) { - if (state.isFetching) { - if (_hadObservers) { - _scheduleGc(); - } - } else { - _cache.remove(this); - } - } - } - - void _setOptions(QueryOptions? options) { - this.options = QueryOptions.fromJson({ - ...(_defaultOptions?.toJson() ?? {}), - ...(options?.toJson() ?? {}), - }); - meta = options?.meta; - - /// Default to [5 minutes] if cache time isn't set - cacheTime = Duration( - milliseconds: max( - cacheTime?.inMilliseconds ?? 0, - this.options.cacheTime?.inMilliseconds ?? 5 * 60 * 1000, - )); - } - - QueryState _getDefaultState( - QueryOptions options) { - var data = options.initialData; - bool hasData = data != null; - - DateTime? initialDataUpdatedAt = - hasData ? options.initialDataUpdatedAt : null; - - return QueryState( - data: data, - dataUpdateCount: 0, - dataUpdatedAt: hasData ? initialDataUpdatedAt ?? DateTime.now() : null, - error: null, - errorUpdateCount: 0, - errorUpdatedAt: null, - fetchFailureCount: 0, - fetchMeta: null, - isFetching: false, - isInvalidated: false, - isPaused: false, - status: hasData ? QueryStatus.success : QueryStatus.idle, - ); - } - - TData setData( - DataUpdateFunction updater, { - DateTime? updatedAt, - }) { - try { - var prevData = this.state.data; - var data = updater(prevData); - // Use prev data if an isDataEqual function is defined and returns `true` - if (this.options.isDataEqual?.call(prevData, data) == true) { - data = prevData as TData; - } else if (this.options.structuralSharing != false) { - // Structurally share data between prev and new data if needed - final merged = - Map.from(replaceEqualDeep(prevData, data)); - data = merged as TData; - } - // Set data and mark it as cached - _dispatch(Action( - ActionType.success, - data: data, - dataUpdatedAt: updatedAt, - )); - return data; - } catch (e, stack) { - print("[Query.setData] $e"); - print(stack); - rethrow; - } - } - - void setState( - QueryState state, [ - SetStateOptions? setStateOptions, - ]) { - _dispatch(Action( - ActionType.setState, - state: state, - setStateOptions: setStateOptions, - )); - } - - Future cancel({bool? revert, bool? silent}) { - // var future = _future; - _retryer?.cancel(revert: revert, silent: silent); - if (_completer != null && !_completer!.isCompleted) { - _completer!.completeError("Cancelled Job", StackTrace.current); - return _completer!.future.then(noop).catchError(noop); - } - return Future.value(); - } - - void reset() { - destroy(); - setState(initialState); - } - - destroy() { - _clearGcTimeout(); - cancel(silent: true); - } - - bool isActive() { - return _observers.any((observer) => observer.options.enabled != false); - } - - bool isFetching() { - return this.state.isFetching; - } - - Future fetch([ - QueryOptions? options, - ObserverFetchOptions? fetchOptions, - ]) { - if (this.state.isFetching) { - if (this.state.dataUpdatedAt != null && - fetchOptions?.cancelRefetch == true) { - // Silently cancel current fetch if the user wants to cancel re-fetches - this.cancel(silent: true); - } else if (_completer != null) { - // make sure that retries that were potentially cancelled due to unmounts can continue - _retryer?.continueRetry(); - // Return current promise if we are already fetching - return _completer!.future; - } - } - - // Update config if passed, otherwise the config from the last execution is used - if (options != null) { - _setOptions(options); - } - - // Use the options from the first observer with a query function if no function is found. - // This can happen when the query is hydrated or created with setQueryData. - if (this.options.queryFn == null) { - final observer = - _observers.firstWhereOrNull((x) => x.options.queryFn != null); - if (observer != null) { - _setOptions(QueryOptions( - queryKey: observer.options.queryKey, - queryKeyHashFn: observer.options.queryKeyHashFn, - cacheTime: observer.options.cacheTime, - isDataEqual: observer.options.isDataEqual, - queryFn: - observer.options.queryFn as QueryFunction, - queryHash: observer.options.queryHash, - initialData: observer.options.initialData as TData?, - initialDataUpdatedAt: observer.options.initialDataUpdatedAt, - meta: observer.options.meta, - structuralSharing: observer.options.structuralSharing, - defaulted: observer.options.defaulted, - )); - } - } - - QueryFunctionContext queryFnContext = QueryFunctionContext( - queryKey: queryKey, - meta: meta, - ); - - /// !!LANGUAGE LIMITATION!! There's no equivalent of [AbortController] - /// the [get] can be implemented using Dart's getter but it'd be - /// useless since there's no equivalent of AbortController. - /// Have to find a better way to control ABORTION - - // Object.defineProperty(queryFnContext, 'signal', { - // enumerable: true, - // get: () { - // if (abortController) { - // this.abortSignalConsumed = true - // return abortController.signal - // } - // return undefined - // }, - // }) - - // Create fetch function - FutureOr fetchFn() { - if (this.options.queryFn == null) { - return Future.error('Missing queryFn'); - } - _abortSignalConsumed = false; - return options!.queryFn!.call(queryFnContext); - } - - // Trigger behavior hook - FetchContext context = - FetchContext( - fetchOptions: fetchOptions, - options: this.options, - queryKey: queryKey, - state: this.state, - fetchFn: fetchFn, - meta: this.meta, - ); - - this.options.behavior?.onFetch(context); - // Store state in case the current fetch needs to be reverted - this.revertState = this.state; - - // Set to fetching state if not already in it - if (!this.state.isFetching || - this.state.fetchMeta != context.fetchOptions?.meta) { - _dispatch(Action(ActionType.fetch, meta: context.fetchOptions?.meta)); - } - - _retryer = Retryer( - fn: context.fetchFn as FutureOr Function(), - // abort: abortController?.abort?.bind(abortController), - onSuccess: (data) { - this.setData((_) => data); - - // Notify cache callback - _cache.onData?.call(data, this); - if (_completer?.isCompleted == false) _completer?.complete(data); - // Remove query after fetching if cache time is 0 - if (this.cacheTime == null || this.cacheTime == Duration.zero) { - _optionalRemove(); - } - }, - onError: (TError error) { - // Optimistically update state if needed - if (!(isCancelledError(error) && (error as dynamic)?.silent == true)) { - _dispatch(Action(ActionType.error, error: error)); - } - - if (!isCancelledError(error)) { - // Notify cache callback - _cache.onError?.call(error, this); - - // Log error - // getLogger().error(error); - } - - // Remove query after fetching if cache time is 0 - if (this.cacheTime == null || this.cacheTime == Duration.zero) { - _optionalRemove(); - } - if (_completer?.isCompleted == false) - _completer?.completeError( - error ?? "Retry Failed", StackTrace.current); - }, - onFail: (failureCount, error) { - _dispatch(Action(ActionType.failed)); - }, - onPause: () { - _dispatch(Action(ActionType.pause)); - }, - onContinue: () { - _dispatch(Action(ActionType.resume)); - }, - retry: context.options.retry, - retryDelay: context.options.retryDelay, - ); - - this._completer = _retryer!.completer; - return this._completer!.future; - } - - void _dispatch(Action action) { - this.state = this.reducer(this.state, action); - - notifyManager.batch(() { - _observers.forEach((observer) { - observer.onQueryUpdate(action); - }); - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryUpdated, - this, - action: action, - )); - }); - } - - void addObserver(QueryObserver observer) { - if (_observers.indexOf(observer) == -1) { - _observers.add(observer); - _hadObservers = true; - - // Stop the query from being garbage collected - _clearGcTimeout(); - - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerAdded, - this, - observer: observer, - )); - } - } - - void removeObserver(QueryObserver observer) { - if (_observers.indexOf(observer) != -1) { - _observers = _observers.where((x) => x != observer).toList(); - - if (_observers.isEmpty) { - // If the transport layer does not support cancellation - // we'll let the query continue so the result can be cached - if (_retryer != null) { - if (_retryer?.isTransportCancelable == true || _abortSignalConsumed) { - _retryer?.cancel(revert: true); - } else { - _retryer?.cancelRetry(); - } - } - - if (cacheTime != null) { - _scheduleGc(); - } else { - _cache.remove(this); - } - } - - _cache.notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerRemoved, - this, - observer: observer, - )); - } - } - - int getObserversCount() { - return _observers.length; - } - - void invalidate() { - if (!this.state.isInvalidated) { - _dispatch(Action(ActionType.invalidate)); - } - } - - bool isStale() { - return (this.state.isInvalidated || - this.state.dataUpdatedAt == null || - _observers - .any((observer) => observer.getCurrentResult()?.isStale == true)); - } - - bool isStaleByTime(Duration? staleTime) { - return (this.state.isInvalidated || - this.state.dataUpdatedAt == null || - timeUntilStale(this.state.dataUpdatedAt!, staleTime) == Duration.zero); - } - - void onOnline() { - var observer = _observers - .firstWhereOrNull((x) => x.shouldFetchCurrentQueryOnReconnect()); - - if (observer != null) { - observer.refetch(); - } - - // Continue fetch if currently paused - _retryer?.continueFn(); - } - - @protected - QueryState reducer( - QueryState state, - Action action, - ) { - switch (action.type) { - case ActionType.failed: - return QueryState.fromJson({ - ...state.toJson(), - "fetchFailureCount": state.fetchFailureCount + 1, - }); - case ActionType.fetch: - return QueryState.fromJson({ - ...state.toJson(), - "fetchFailureCount": 0, - "fetchMeta": action.meta, - "isFetching": true, - "isPaused": false, - if (state.dataUpdatedAt == null) - ...({ - "error": null, - "status": QueryStatus.loading, - }) - }); - case ActionType.success: - return QueryState.fromJson({ - ...state.toJson(), - "data": action.data, - "dataUpdateCount": state.dataUpdateCount + 1, - "dataUpdatedAt": action.dataUpdatedAt ?? DateTime.now(), - "error": null, - "fetchFailureCount": 0, - "isFetching": false, - "isInvalidated": false, - "isPaused": false, - "status": QueryStatus.success, - }); - case ActionType.error: - var error = action.error as dynamic; - if (isCancelledError(error) && - error?.revert == true && - revertState != null) { - return QueryState.fromJson(revertState!.toJson()); - } - - return QueryState.fromJson({ - ...state.toJson(), - "error": error as TError, - "errorUpdateCount": state.errorUpdateCount + 1, - "errorUpdatedAt": DateTime.now(), - "fetchFailureCount": state.fetchFailureCount + 1, - "isFetching": false, - "isPaused": false, - "status": QueryStatus.error, - }); - case ActionType.invalidate: - return QueryState.fromJson({ - ...state.toJson(), - "isInvalidated": true, - }); - case ActionType.pause: - return QueryState.fromJson({ - ...state.toJson(), - "isPaused": true, - }); - case ActionType.resume: - return QueryState.fromJson({ - ...state.toJson(), - "isPaused": false, - }); - case ActionType.setState: - return QueryState.fromJson({ - ...state.toJson(), - ...(action.state?.toJson() ?? {}), - }); - default: - return state; - } - } -} diff --git a/packages/fl_query/lib/src/core/query_cache.dart b/packages/fl_query/lib/src/core/query_cache.dart deleted file mode 100644 index 0775ce0..0000000 --- a/packages/fl_query/lib/src/core/query_cache.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:fl_query/src/core/query.dart'; -import 'package:fl_query/src/core/query_client.dart'; -import 'package:fl_query/src/core/query_key.dart'; -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:collection/collection.dart'; - -enum QueryCacheNotifyEventType { - observerResultsUpdated, - observerRemoved, - observerAdded, - queryUpdated, - queryRemoved, - queryAdded -} - -class QueryCacheNotifyEvent { - Query query; - Object? observer; - Object? action; - QueryCacheNotifyEventType type; - QueryCacheNotifyEvent( - this.type, - this.query, { - this.observer, - this.action, - }) { - if ([ - QueryCacheNotifyEventType.observerAdded, - QueryCacheNotifyEventType.observerRemoved - ].contains(type) && - observer == null) - throw Exception( - "[QueryCacheNotifyEvent.constructor] property `observer` can't be `null` for `QueryCacheNotifyEventType.observerAdded` & `QueryCacheNotifyEventType.observerRemoved`"); - if (type == QueryCacheNotifyEventType.queryUpdated && action == null) - throw Exception( - "[QueryCacheNotifyEvent.constructor] property `action` can't be `null` for `QueryCacheNotifyEventType.queryUpdated`"); - } -} - -typedef QueryCacheListener = void Function(QueryCacheNotifyEvent? event); -typedef QueryCacheOnError = void Function(dynamic error, Query query); -typedef QueryCacheOnData = void Function(dynamic data, Query query); -typedef QueryHashMap = Map; - -class QueryCache extends Subscribable { - List _queries; - QueryHashMap _queriesMap; - - QueryCacheOnError? onError; - QueryCacheOnData? onData; - - QueryCache({ - this.onData, - this.onError, - }) : _queries = [], - _queriesMap = {}, - super(); - - Query build< - TQueryFnData extends Map, - TError, - TData extends Map>( - QueryClient client, - QueryOptions options, [ - QueryState? state, - ]) { - QueryKey queryKey = options.queryKey!; - String queryHash = - options.queryHash ?? hashQueryKeyByOptions(queryKey, options); - Query? query = - get(queryHash); - - if (query == null) { - query = Query( - cache: this, - queryKey: queryKey, - queryHash: queryHash, - options: client.defaultQueryOptions( - QueryObserverOptions.fromJson(options.toJson()), - ), - state: state, - defaultOptions: QueryOptions.fromJson( - client.getQueryDefaults(queryKey)?.toJson() ?? {}, - ), - meta: options.meta, - ); - add(query); - } - return query; - } - - QueryHashMap get queriesMap => _queriesMap; - List get queries => _queries; - - void add(Query query) { - if (!_queriesMap.containsKey(query.queryHash)) { - _queriesMap[query.queryHash] = query; - _queries.add(query); - notify( - QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryAdded, - query, - ), - ); - } - } - - void remove(Query query) { - Query? queryInMap = _queriesMap[query.queryHash]; - if (queryInMap == null) return; - query.destroy(); - _queries = _queries.where((x) => x != query).toList(); - if (queryInMap == query) { - _queriesMap.remove(query.queryHash); - } - notify(QueryCacheNotifyEvent( - QueryCacheNotifyEventType.queryRemoved, - query, - )); - } - - void clear() { - notifyManager.batch(() { - for (var query in _queries) { - remove(query); - } - }); - } - - Query? get< - TQueryFnData extends Map, - TError, - TData extends Map>(String queryHash) { - return _queriesMap[queryHash] as Query?; - } - - List getAll() { - return _queries; - } - - Query? find< - TQueryFnData extends Map, - TError, - TData extends Map>(QueryKey queryKey, - [QueryFilters? queryFilters]) { - queryFilters ??= QueryFilters(); - queryFilters.exact ??= true; - return _queries.firstWhereOrNull((query) => matchQuery( - queryFilters!, - query, - queryKey, - )) as Query?; - } - - List findAll([QueryKey? queryKeys, QueryFilters? filters]) { - return filters == null && queryKeys == null - ? _queries - : _queries - .where( - (query) => matchQuery( - filters ?? QueryFilters(), - query, - queryKeys, - ), - ) - .toList(); - } - - void notify(QueryCacheNotifyEvent event) { - notifyManager.batch(() { - for (final listener in listeners) { - listener(event); - } - }); - } - - /// Dummy function just to keep the API similar to react-query - void onFocus() {} - - void onOnline() { - notifyManager.batch(() { - _queries.forEach((query) { - query.onOnline(); - }); - }); - } -} diff --git a/packages/fl_query/lib/src/core/query_client.dart b/packages/fl_query/lib/src/core/query_client.dart deleted file mode 100644 index 3f8e47b..0000000 --- a/packages/fl_query/lib/src/core/query_client.dart +++ /dev/null @@ -1,390 +0,0 @@ -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> { - QueryKey queryKey; - TData data; - QueryData({ - required this.queryKey, - required this.data, - }); -} - -class QueryClient { - QueryCache _queryCache; - // QueryCache _mutationCache; - DefaultOptions _defaultOptions; - List _queryDefaults; - // List _mutationDefaults; - void Function()? _unsubscribeFocus; - void Function()? _unsubscribeOnline; - // MutationKey _mutationKey; - // MutationOptions _mutationDefaultOptions; - - QueryClient({ - QueryCache? queryCache, - QueryCache? mutationCache, - DefaultOptions? defaultOptions, - }) : _queryCache = queryCache ?? 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>( - QueryKey queryKey, [ - QueryFilters? filters, - ]) { - return _queryCache - .find>( - queryKey, filters ?? QueryFilters()) - ?.state - .data as TData?; - } - - List> getQueriesData>({ - QueryKey? queryKeys, - QueryFilters? filters, - }) { - return getQueryCache().findAll(queryKeys, filters).map((query) { - return QueryData( - data: query.state.data as TData, - queryKey: query.queryKey, - ); - }).toList(); - } - - TData setQueryData>( - QueryKey queryKey, - DataUpdateFunction updater, [ - DateTime? updatedAt, - ]) { - final QueryOptions, dynamic, TData> defaultedOptions = - QueryOptions, dynamic, TData>.fromJson( - defaultQueryOptions, dynamic, TData, - Map>( - QueryObserverOptions, dynamic, TData, - Map>(queryKey: queryKey)) - .toJson()); - return _queryCache - .build, dynamic, TData>(this, defaultedOptions) - .setData( - updater, - updatedAt: updatedAt, - ); - } - - List setQueriesData>({ - required DataUpdateFunction updater, - QueryKey? queryKeys, - QueryFilters? filters, - DateTime? updatedAt, - }) { - if (queryKeys == null && filters == null) - throw Exception( - "[QueryClient.setQueriesData] both `queryKey` & `filters` can't be null at the same time"); - return notifyManager - .batch(() => getQueryCache().findAll(queryKeys, filters).map( - (query) => QueryData( - queryKey: query.queryKey, - data: setQueryData( - query.queryKey, - updater, - updatedAt, - ), - ), - )) - .toList(); - } - - QueryState? - getQueryState, TError>( - QueryKey queryKey, [ - QueryFilters? filters, - ]) { - return _queryCache - .find>( - queryKey, - filters ?? QueryFilters(), - ) - ?.state as QueryState?; - } - - void removeQueries({QueryKey? queryKeys, QueryFilters? filters}) { - notifyManager.batch( - () => { - _queryCache.findAll(queryKeys, filters).forEach((query) { - _queryCache.remove(query); - }) - }, - ); - } - - Future resetQueries({ - QueryKey? queryKeys, - RefetchableQueryFilters? filters, - bool? throwOnError, - }) { - filters?.active = true; - var refetchFilters = RefetchableQueryFilters.fromJson({ - ...(filters?.toJson() ?? {}), - "active": true, - }); - - return notifyManager.batch(() { - _queryCache.findAll(queryKeys, filters).forEach((query) { - query.reset(); - }); - return refetchQueries( - filters: refetchFilters, - options: RefetchOptions(throwOnError: throwOnError), - ); - }); - } - - Future cancelQueries({ - QueryKey? queryKeys, - QueryFilters? filters, - bool? revert = true, - bool? silent, - }) { - var futures = notifyManager.batch(() => - _queryCache.findAll(queryKeys, filters).map((query) => query.cancel( - revert: revert, - silent: silent, - ))); - return Future.wait(futures).then(noop).catchError(noop); - } - - Future invalidateQueries({ - QueryKey? queryKeys, - InvalidateQueryFilters? filters, - RefetchOptions? options, - }) { - var refetchFilters = RefetchableQueryFilters.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(queryKeys, filters).forEach((query) { - query.invalidate(); - }); - return this.refetchQueries( - filters: refetchFilters, - options: options, - ); - }); - } - - Future refetchQueries({ - QueryKey? queryKeys, - RefetchableQueryFilters? filters, - RefetchOptions? options, - }) { - var futures = notifyManager.batch( - () => _queryCache.findAll(queryKeys, 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 fetchQuery, TError, - TData extends Map>({ - QueryKey? queryKey, - QueryFunction? queryFn, - FetchQueryOptions? options, - }) { - final defaultedOptions = this.defaultQueryOptions( - QueryObserverOptions, dynamic, Map, - TData>( - 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; - final query = _queryCache.build, dynamic, TData>( - this, defaultedOptions); - return query.isStaleByTime(defaultedOptions.staleTime) - ? query.fetch(defaultedOptions) - : Future.value(query.state.data as TData); - } - - Future prefetchQuery, TError, - TData extends Map>({ - QueryKey? queryKey, - QueryFunction? queryFn, - FetchQueryOptions? options, - }) { - return fetchQuery( - queryKey: queryKey, - queryFn: queryFn, - options: options, - ).then(noop).catchError(noop); - } - - QueryObserverOptions defaultQueryOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - QueryObserverOptions? options) { - if (options?.defaulted == true) return options!; - final defaultedOptions = - QueryObserverOptions.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 - defaultQueryObserverOptions< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>([ - QueryObserverOptions? options, - ]) { - return this.defaultQueryOptions(options); - } - - 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() {} - // getMutationCache() {} - - QueryCache getQueryCache() { - return _queryCache; - } - - void clear() { - _queryCache.clear(); - // _mutationCache.clear(); - } -} diff --git a/packages/fl_query/lib/src/core/query_key.dart b/packages/fl_query/lib/src/core/query_key.dart deleted file mode 100644 index 4e70647..0000000 --- a/packages/fl_query/lib/src/core/query_key.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// Used for defining a unique identifier for a specific query -/// that can be used to read/modify/delete the query from the -/// store -class QueryKey { - List _key; - QueryKey(String key) : _key = [key]; - - QueryKey.fromList(List key) : _key = key; - QueryKey.parse(String keyStr) : _key = keyStr.split("."); - - String get key => _key.map((k) => k.replaceAll(".", "")).join("."); - List get keyAsList => _key; - - @override - String toString() { - return 'QueryKey("$key")'; - } -} diff --git a/packages/fl_query/lib/src/core/query_observer.dart b/packages/fl_query/lib/src/core/query_observer.dart deleted file mode 100644 index ac96106..0000000 --- a/packages/fl_query/lib/src/core/query_observer.dart +++ /dev/null @@ -1,740 +0,0 @@ -/// `TQueryData`, `TQueryFnData`, `TData` should be [Map]s for shallow/deep equality checks -/// Or these can be data classes that have `toJson` method & `fromJson` -/// constructor. This also requires the data-class to be passed to the -/// [Query] constructor parameters e.g ([dataType]) - -import 'dart:async'; - -import 'package:fl_query/src/core/models.dart'; -import 'package:fl_query/src/core/notify_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_client.dart'; -import 'package:fl_query/src/core/retryer.dart'; -import 'package:fl_query/src/core/subscribable.dart'; -import 'package:fl_query/src/core/utils.dart'; -import 'package:meta/meta.dart'; - -typedef QueryObserverListener, TError> = void - Function(QueryObserverResult result); - -class NotifyOptions { - bool? cache; - bool? listeners; - bool? onError; - bool? onSuccess; - - NotifyOptions({this.cache, this.listeners, this.onError, this.onSuccess}); - - /// [safe] default `true`- if it's true then there'll be no key - /// containing null value - Map toJson([bool safe = true]) { - final Map data = new Map(); - if (safe) { - if (this.cache != null) data['cache'] = this.cache; - if (this.listeners != null) data['listeners'] = this.listeners; - if (this.onError != null) data['onError'] = this.onError; - if (this.onSuccess != null) data['onSuccess'] = this.onSuccess; - } else { - data['cache'] = this.cache; - data['listeners'] = this.listeners; - data['onError'] = this.onError; - data['onSuccess'] = this.onSuccess; - } - return data; - } - - NotifyOptions.fromJson(Map json) { - cache = json['cache']; - listeners = json['listeners']; - onError = json['onError']; - onSuccess = json['onSuccess']; - } -} - -class ObserverFetchOptions extends FetchOptions { - bool? throwOnError; - ObserverFetchOptions({ - this.throwOnError, - bool? cancelRefetch, - dynamic meta, - }) : super(cancelRefetch: cancelRefetch, meta: meta); -} - -class SelectQuery, - TData extends Map> { - TData Function(TQueryData data) fn; - TData result; - SelectQuery(this.fn, this.result); -} - -class QueryObserver< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map> - extends Subscribable { - QueryObserverOptions options; - QueryClient _client; - Query? _currentQuery; - - late QueryState _currentQueryInitialState; - QueryObserverResult? _currentResult; - - /// List of tracked keys/properties of [QueryObserverResult] - late List _trackedProps; - - QueryState? _currentResultState; - QueryObserverOptions? - _currentResultOptions; - QueryObserverResult? _previousQueryResult; - Exception? _previousSelectError; - SelectQuery? _previousSelect; - Timer? _staleTimeout; - Timer? _refetchInterval; - Duration? _currentRefetchInterval; - - @protected - Timer? get refetchInterval => _refetchInterval; - - QueryObserver( - this._client, - QueryObserverOptions? _options, - ) : _trackedProps = [], - _previousSelectError = null, - options = _options ?? QueryObserverOptions(), - super() { - this.setOptions(options); - } - - bool shouldFetchCurrentQueryOnReconnect() { - return shouldFetchOnReconnect(_currentQuery!, this.options); - } - - @override - void onSubscribe() { - if (listeners.length == 1) { - _currentQuery?.addObserver(this); - - if (_currentQuery != null && - shouldFetchOnMount(_currentQuery!, options)) { - _executeFetch(); - } - - _updateTimers(); - } - } - - @override - void onUnsubscribe() { - if (listeners.isEmpty) { - this.destroy(); - } - } - - void destroy() { - listeners = []; - _clearTimers(); - _currentQuery?.removeObserver(this); - } - - void setOptions( - QueryObserverOptions? options, [ - NotifyOptions? notifyOptions, - ]) { - final prevOptions = this.options; - final prevQuery = _currentQuery; - - this.options = this._client.defaultQueryObserverOptions(options); - - this.options.queryKey ??= prevOptions.queryKey; - - _updateQuery(); - - bool mounted = hasListeners(); - - if (mounted && - _currentQuery != null && - prevQuery != null && - shouldFetchOptionally( - _currentQuery!, prevQuery, this.options, prevOptions)) { - _executeFetch(); - } - ; - - this.updateResult(notifyOptions); - if (mounted && - (_currentQuery != prevQuery || - this.options.enabled != prevOptions.enabled || - this.options.staleTime != prevOptions.staleTime)) { - _updateStaleTimeout(); - } - - final nextRefetchInterval = _computeRefetchInterval(); - - // Update refetch interval if needed - if (mounted && - (_currentQuery != prevQuery || - this.options.enabled != prevOptions.enabled || - nextRefetchInterval != _currentRefetchInterval)) { - _updateRefetchInterval(nextRefetchInterval); - } - } - - QueryObserverResult getOptimisticResult( - QueryObserverOptions options, - ) { - final defaultedOptions = _client.defaultQueryObserverOptions(options); - - final query = _client.getQueryCache().build(_client, defaultedOptions); - - return createResult(query, defaultedOptions); - } - - QueryObserverResult? getCurrentResult() { - return _currentResult; - } - - /// There's nothing similar to JS [defineProperty] in dart native - /// objects thus modifying the underlying property `get` method is - /// impossible so [trackProp] can't be implemented at the moment - /// At least not following this procedure - QueryObserverResult trackResult( - QueryObserverResult result, - QueryObserverOptions - defaultedOptions, - ) { - // final Map trackedResult = {}; - // const trackProp = (key: keyof QueryObserverResult) => { - // if (!this.trackedProps.includes(key)) { - // this.trackedProps.push(key) - // } - // } - // Object.keys(result).forEach(key => { - // Object.defineProperty(trackedResult, key, { - // configurable: false, - // enumerable: true, - // get: () => { - // trackProp(key as keyof QueryObserverResult) - // return result[key as keyof QueryObserverResult] - // }, - // }) - // }) - // if (defaultedOptions.useErrorBoundary || defaultedOptions.suspense) { - // trackProp('error') - // } - // return trackedResult - - throw UnimplementedError("COULD NOT IMPLEMENT DUE TO LANGUAGE LIMITATIONS"); - } - - Future> getNextResult([ - bool? throwOnError, - ]) { - final completer = Completer>(); - var unsubscribe; - unsubscribe = subscribe((result) { - if (!result.isFetching) { - unsubscribe?.call(); - if (result.isError && throwOnError == true) { - if (!completer.isCompleted) completer.completeError(result.error); - } else { - if (!completer.isCompleted) - completer.complete( - result as QueryObserverResult, - ); - } - } - }); - return completer.future; - } - - Query getCurrentQuery() { - return _currentQuery!; - } - - Future> fetchOptimistic( - QueryObserverOptions options) { - final defaultedOptions = _client.defaultQueryObserverOptions(options); - final query = _client.getQueryCache().build(_client, defaultedOptions); - - return query.fetch().then((val) { - return createResult(query, defaultedOptions); - }); - } - - @protected - Future?> fetch( - ObserverFetchOptions fetchOptions, - ) { - return _executeFetch(fetchOptions).then((val) { - updateResult(); - return _currentResult; - }); - } - - Future _executeFetch([ObserverFetchOptions? fetchOptions]) { - // Make sure we reference the latest query as the current one might have been removed - _updateQuery(); - // Fetch - Future future = _currentQuery!.fetch( - this.options, - fetchOptions, - ); - - if (fetchOptions?.throwOnError != null) { - future = future.catchError((e) => e); - } - - return future; - } - - bool _shouldNotifyListeners(QueryObserverResult result, - [QueryObserverResult? prevResult]) { - if (prevResult == null) return true; - if (options.notifyOnChangeProps == false && - options.notifyOnChangePropsExclusions == null) { - return true; - } - - if (options.notifyOnChangeProps == 'tracked' && _trackedProps.isEmpty) { - return true; - } - - List? includedProps = options.notifyOnChangeProps == 'tracked' - ? _trackedProps - : options.notifyOnChangeProps; - - Map resultMap = result.toJson(); - Map prevResultMap = prevResult.toJson(); - - return resultMap.keys.any((key) { - final changed = resultMap[key] != prevResultMap[key]; - bool? isIncluded = includedProps?.any((x) => x == key); - bool isExcluded = - options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false; - return changed && - !isExcluded && - (includedProps == null || isIncluded == true); - }); - } - - void updateResult([NotifyOptions? notifyOptions]) { - final QueryObserverResult? prevResult = _currentResult; - - if (_currentQuery != null) - _currentResult = this.createResult(_currentQuery!, this.options); - _currentResultState = _currentQuery?.state; - _currentResultOptions = this.options; - - final isSameMap = - shallowEqualMap(_currentResult?.toJson(), prevResult?.toJson()); - // Only notify if something has changed - if (isSameMap) { - return; - } - NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true); - if (notifyOptions?.listeners != false && - _currentResult != null && - _shouldNotifyListeners(_currentResult!, prevResult)) { - defaultNotifyOptions.listeners = true; - } - - final mergedNotifyOptions = { - ...defaultNotifyOptions.toJson(), - ...(notifyOptions?.toJson() ?? {}), - }; - - _notify(NotifyOptions.fromJson(mergedNotifyOptions)); - } - - void _updateQuery() { - final query = - this._client.getQueryCache().build(this._client, this.options); - - if (query == _currentQuery) return; - - final prevQuery = _currentQuery; - _currentQuery = query; - _currentQueryInitialState = query.state; - _previousQueryResult = _currentResult; - - if (hasListeners()) { - prevQuery?.removeObserver(this); - query.addObserver(this); - } - } - - void onQueryUpdate(Action action) { - final NotifyOptions notifyOptions = NotifyOptions(); - - if (action.type == 'success') { - notifyOptions.onSuccess = true; - } else if (action.type == 'error' && !isCancelledError(action.error)) { - notifyOptions.onError = true; - } - - updateResult(notifyOptions); - - if (this.hasListeners()) { - _updateTimers(); - } - } - - QueryObserverResult createResult( - Query query, - QueryObserverOptions options, - ) { - 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; - final prevQueryResult = queryChange ? _currentResult : _previousQueryResult; - - 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; - TData? data; - - // Optimistically set result in fetching state if needed - if (options.optimisticResults == true) { - final bool mounted = hasListeners(); - - final bool fetchOnMount = !mounted && shouldFetchOnMount(query, options); - - bool fetchOptionally = mounted && - prevQuery != null && - shouldFetchOptionally(query, prevQuery, options, prevOptions); - - if (fetchOnMount || fetchOptionally) { - isFetching = true; - if (dataUpdatedAt == null) { - status = QueryStatus.loading; - } - } - } - - // Keep previous data if needed - if (prevQueryResult != null && - options.keepPreviousData == true && - state.dataUpdateCount == 0 && - prevQueryResult.isSuccess == true && - status != QueryStatus.error) { - data = prevQueryResult.data; - dataUpdatedAt = prevQueryResult.dataUpdatedAt; - status = prevQueryResult.status; - isPreviousData = true; - } - - // Select data if needed - else if (options.select != null && state.data != null) { - print("prevResult != null ${prevResult != null}"); - print( - "state.data == prevResultState?.data | ${state.data} == ${prevResultState?.data} | ${shallowEqualMap(state.data, prevResultState?.data)}"); - print( - "options.select == _previousSelect?.fn ${options.select == _previousSelect?.fn}"); - print("_previousSelectError == null ${_previousSelectError == null}"); - if (prevResult != null && - shallowEqualMap(state.data, prevResultState?.data) && - options.select == _previousSelect?.fn && - _previousSelectError == null) { - data = _previousSelect?.result; - } else { - try { - data = options.select?.call(state.data); - if (options.structuralSharing != false) { - data = Map.from( - replaceEqualDeep(prevResult?.data, data)) as TData; - } - if (options.select != null && data != null) { - _previousSelect = SelectQuery( - options.select!, - data, - ); - } - _previousSelectError = null; - } catch (selectError) { - // getLogger().error(selectError); - error = selectError as TError; - _previousSelectError = selectError as Exception; - errorUpdatedAt = DateTime.now(); - status = QueryStatus.error; - } - } - } - // Use query data - else { - data = state.data as TData?; - } - - if (options.placeholderData != null && - data == null && - (status == QueryStatus.loading || status == QueryStatus.idle)) { - var placeholderData; - - if (prevResult?.isPlaceholderData == true && - options.placeholderData == prevResultOptions?.placeholderData) { - placeholderData = prevResult?.data; - } else { - placeholderData = options.placeholderData; - if (options.select != null && placeholderData != null) { - try { - placeholderData = options.select?.call(placeholderData); - if (options.structuralSharing != false) { - placeholderData = - replaceEqualDeep(prevResult?.data, placeholderData); - } - _previousSelectError = null; - } catch (selectError) { - // getLogger().error(selectError); - error = selectError as TError; - _previousSelectError = selectError as Exception; - errorUpdatedAt = DateTime.now(); - status = QueryStatus.error; - } - } - } - - if (placeholderData != null) { - status = QueryStatus.success; - data = placeholderData as TData; - isPlaceholderData = true; - } - } - - final QueryObserverResult result = - QueryObserverResult( - status: status, - dataUpdatedAt: dataUpdatedAt, - isLoading: status == QueryStatus.loading, - isSuccess: status == QueryStatus.success, - isError: status == QueryStatus.error, - isIdle: status == QueryStatus.idle, - data: data, - error: error, - failureCount: state.fetchFailureCount, - isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0, - isFetchedAfterMount: - state.dataUpdateCount > queryInitialState.dataUpdateCount || - state.errorUpdateCount > queryInitialState.errorUpdateCount, - isFetching: isFetching, - isRefetching: isFetching && status != QueryStatus.loading, - isLoadingError: - status == QueryStatus.error && state.dataUpdatedAt == null, - isPlaceholderData: isPlaceholderData, - isPreviousData: isPreviousData, - isRefetchError: status == 'error' && state.dataUpdatedAt != 0, - isStale: isStale(query, options), - refetch: this.refetch, - remove: this.remove, - ); - return result; - } - - void _notify(NotifyOptions notifyOptions) { - notifyManager.batch(() { - // First trigger the configuration callbacks - if (notifyOptions.onSuccess == true && _currentResult != null) { - this.options.onSuccess?.call(_currentResult!.data!); - this.options.onSettled?.call(_currentResult!.data!); - } else if (notifyOptions.onError == true && _currentResult != null) { - this.options.onError?.call(_currentResult!.error!); - this.options.onSettled?.call(null, _currentResult!.error!); - } - - // Then trigger the listeners - if (notifyOptions.listeners == true && _currentResult != null) { - this.listeners.forEach((listener) { - listener(_currentResult!); - }); - } - - // Then the cache listeners - if (notifyOptions.cache == true && _currentQuery != null) { - _client.getQueryCache().notify( - QueryCacheNotifyEvent( - QueryCacheNotifyEventType.observerResultsUpdated, - _currentQuery as Query, - ), - ); - } - }); - } - - Duration? _computeRefetchInterval() { - return this.options.refetchInterval != null && _currentQuery != null - ? this.options.refetchInterval!(_currentResult?.data, _currentQuery!) - : null; - } - - void _updateTimers() { - _updateStaleTimeout(); - _updateRefetchInterval(_computeRefetchInterval()); - } - - void _updateStaleTimeout() { - _clearStaleTimeout(); - if (_currentResult?.isStale == true || - options.staleTime == null || - _currentResult?.dataUpdatedAt == null) return; - - // The timeout is sometimes triggered 1 ms before the stale time - // expiration. To mitigate this issue we always add 1 ms to the - // timeout. - Duration time = Duration( - milliseconds: - timeUntilStale(_currentResult!.dataUpdatedAt!, this.options.staleTime) - .inMilliseconds + - 1, - ); - - _staleTimeout = Timer(time, () { - if (!_currentResult!.isStale) { - this.updateResult(); - } - }); - } - - _updateRefetchInterval(Duration? nextInterval) { - _clearRefetchInterval(); - - _currentRefetchInterval = nextInterval; - - if (this.options.enabled == false || - _currentRefetchInterval == null || - _currentRefetchInterval == Duration.zero) return; - - _refetchInterval = Timer.periodic(_currentRefetchInterval!, (t) { - if (this.options.refetchIntervalInBackground == true) { - _executeFetch(); - } - }); - } - - void _clearTimers() { - _clearStaleTimeout(); - _clearRefetchInterval(); - } - - void _clearStaleTimeout() { - _staleTimeout?.cancel(); - _staleTimeout = null; - } - - void _clearRefetchInterval() { - _refetchInterval?.cancel(); - _refetchInterval = null; - } - - void remove() { - _client.getQueryCache().remove(_currentQuery as Query); - _clearTimers(); - _currentQuery?.removeObserver(this); - } - - Future?> refetch({ - RefetchableQueryFilters? filters, - RefetchOptions? options, - }) { - return fetch( - ObserverFetchOptions( - cancelRefetch: options?.cancelRefetch, - meta: filters?.toJson(), - throwOnError: options?.throwOnError, - ), - ); - } -} - -bool shouldLoadOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - query.state.dataUpdatedAt == null && - !(query.state.status == QueryStatus.error && - options.retryOnMount == false)); -} - -bool shouldRefetchOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - query.state.dataUpdatedAt != null && - (options.refetchOnMount == RefetchOnMount.always || - (options.refetchOnMount != RefetchOnMount.off && - isStale(query, options)))); -} - -bool shouldFetchOnMount< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (shouldLoadOnMount(query, options) || - shouldRefetchOnMount(query, options)); -} - -bool shouldFetchOnReconnect< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return (options.enabled != false && - (options.refetchOnReconnect == RefetchOnReconnect.always || - (options.refetchOnReconnect != RefetchOnReconnect.off && - isStale( - query, options)))); -} - -bool shouldFetchOptionally< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - Query prevQuery, - QueryObserverOptions options, - QueryObserverOptions prevOptions, -) { - return (options.enabled != false && - (query != prevQuery || prevOptions.enabled == false) && - (options.suspense != true || query.state.status != QueryStatus.error) && - isStale(query, options)); -} - -bool isStale< - TQueryFnData extends Map, - TError, - TData extends Map, - TQueryData extends Map>( - Query query, - QueryObserverOptions options, -) { - return query.isStaleByTime(options.staleTime); -} diff --git a/packages/fl_query/lib/src/core/retryer.dart b/packages/fl_query/lib/src/core/retryer.dart deleted file mode 100644 index e6d1b33..0000000 --- a/packages/fl_query/lib/src/core/retryer.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'dart:async'; - -import 'dart:math' show pow, min; - -import 'package:fl_query/src/core/online_manager.dart'; - -typedef ShouldRetryFunction = int Function( - int failureCount, - TError error, -); -typedef RetryDelayFunction = double Function( - int failureCount, - TError error, -); - -double defaultRetryDelay(int failureCount) { - return min(pow(1000 * 2, failureCount), 30000).toDouble(); -} - -abstract class Cancelable { - void cancel(); -} - -bool isCancelable(value) { - return value is Cancelable; -} - -class CancelledError { - bool? revert; - bool? silent; - CancelledError({this.revert, this.silent}); - - @override - String toString() { - return "CancelledError(revert: $revert, silent: $silent)"; - } -} - -bool isCancelledError(value) { - return value is CancelledError; -} - -typedef OnError = void Function(TError error); -typedef OnData> = void Function(TData data); - -class Retryer, TError> { - late void Function({bool? revert, bool? silent}) cancel; - late void Function() cancelRetry; - late void Function() continueRetry; - late void Function() continueFn; - // late Future future; - late Completer completer; - int failureCount; - bool isPaused; - bool isResolved; - bool isTransportCancelable; - - // config options for the retryer - FutureOr Function() fn; - void Function()? _abort; - OnError? onError; - OnData? onSuccess; - void Function(int failureCount, TError error)? onFail; - void Function()? onPause; - void Function()? onContinue; - ShouldRetryFunction? retry; - RetryDelayFunction? retryDelay; - - Retryer({ - required this.fn, - void Function()? abort, - this.onError, - this.onSuccess, - this.onFail, - this.onPause, - this.onContinue, - this.retry, - this.retryDelay, - }) : _abort = abort, - failureCount = 0, - isPaused = false, - isResolved = false, - isTransportCancelable = false { - bool cancelRetry = false; - void Function({bool? revert, bool? silent})? cancelFn; - void Function([dynamic value])? continueFn; - cancel = ({bool? revert, bool? silent}) { - cancelFn?.call(); - }; - - this.cancelRetry = () { - cancelRetry = true; - }; - - this.continueRetry = () { - cancelRetry = false; - }; - - this.continueFn = () => continueFn?.call(); - - completer = Completer(); - - // this.future = completer.future; - - resolve(value) { - if (!this.isResolved) { - this.isResolved = true; - onSuccess?.call(value); - continueFn?.call(); - if (!completer.isCompleted) completer.complete(value); - } - } - - reject(value) { - if (!this.isResolved) { - this.isResolved = true; - onError?.call(value); - continueFn?.call(); - if (!completer.isCompleted) completer.completeError(value); - } - } - - pause() { - Completer pauseCompleter = Completer(); - if (!pauseCompleter.isCompleted) continueFn = pauseCompleter.complete; - this.isPaused = true; - onPause?.call(); - return pauseCompleter.future.then((val) { - continueFn = null; - this.isPaused = false; - onContinue?.call(); - }); - } - - run() { - // Do nothing if already resolved - if (this.isResolved) { - return; - } - var promiseOrValue; - - // Execute query - try { - promiseOrValue = fn(); - } catch (error) { - promiseOrValue = Future.error(error); - } - - // Create callback to cancel this fetch - cancelFn = ({bool? revert, bool? silent}) { - if (!this.isResolved) { - reject(new CancelledError(revert: revert, silent: silent)); - - abort?.call(); - - // Cancel transport if supported - if (isCancelable(promiseOrValue)) { - try { - promiseOrValue.cancel(); - } catch (error) {} - } - } - }; - - // Check if the transport layer support cancellation - this.isTransportCancelable = isCancelable(promiseOrValue); - Future.value(promiseOrValue).then(resolve).catchError((error) { - // Stop if the fetch is already resolved - if (this.isResolved) return; - // Do we need to retry the request? - int _retry = retry?.call(failureCount, error) ?? 3; - double _retryDelay = retryDelay?.call(failureCount, error) ?? - defaultRetryDelay(failureCount); - bool shouldRetry = _retry > 0 && _retry > failureCount; - if (cancelRetry || !shouldRetry) { - // We are done if the query does not need to be retried - reject(error); - return; - } - this.failureCount++; - - // Notify on fail - onFail?.call(this.failureCount, error); - Future.delayed(Duration(milliseconds: _retryDelay.toInt())) - .then((val) async { - if (!await onlineManager.isOnline()) { - return pause(); - } - }).then((val) { - if (cancelRetry) { - reject(error); - } else { - run(); - } - }); - }); - } - - // Start loop - run(); - } -} diff --git a/packages/fl_query/lib/src/core/subscribable.dart b/packages/fl_query/lib/src/core/subscribable.dart deleted file mode 100644 index 118a6b1..0000000 --- a/packages/fl_query/lib/src/core/subscribable.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:meta/meta.dart'; - -//? using a single argument due to TypeCast Error cause queryObserver -//? listeners -void placeholder(a1) {} - -abstract class Subscribable { - @protected - List listeners; - Subscribable() : listeners = []; - - void Function() subscribe([TListener? listener]) { - listener ??= placeholder as TListener; - - listeners.add(listener); - - onSubscribe(); - - return () { - listeners = listeners.where((x) => x != listener).toList(); - onUnsubscribe(); - }; - } - - bool hasListeners() { - return listeners.isNotEmpty; - } - - @protected - void onSubscribe() {} - - @protected - void onUnsubscribe() {} -} diff --git a/packages/fl_query/lib/src/core/utils.dart b/packages/fl_query/lib/src/core/utils.dart deleted file mode 100644 index 071a550..0000000 --- a/packages/fl_query/lib/src/core/utils.dart +++ /dev/null @@ -1,175 +0,0 @@ -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'; -import 'dart:math'; - -/// Default query keys hash function. -/// Dummy function just to fill the gaps for original react-query like -/// function body signatures -/// It is not required as a Standardized [QueryKey] data-class is used to -/// create the queryKey -String hashQueryKeyByOptions( - QueryKey queryKey, - QueryOptions? options, -) { - return options?.queryKeyHashFn?.call(queryKey) ?? queryKey.key; -} - -enum QueryStatusFilter { - all, - active, - inactive, - none, -} - -QueryStatusFilter mapQueryStatusFilter( - bool? active, - bool? inactive, -) { - if ((active == true && inactive == true) || - (active == null && inactive == null)) { - return QueryStatusFilter.all; - } else if (active == false && inactive == false) { - return QueryStatusFilter.none; - } else { - // At this point, active|inactive can only be true|false or false|true - // so, when only one value is provided, the missing one has to be the negated value - bool isActive = active ?? !(inactive ?? false); - return isActive ? QueryStatusFilter.active : QueryStatusFilter.inactive; - } -} - -bool matchQuery( - QueryFilters filters, - Query query, [ - - /// multiple queryKeys to find the query - QueryKey? queryKeys, -]) { - if (queryKeys != null) { - if (filters.exact == true && - query.queryHash != hashQueryKeyByOptions(queryKeys, query.options)) - return false; - else if (query.queryKey.key != queryKeys.key && - !queryKeys.keyAsList.contains(query.queryKey.key) && - !query.queryKey.keyAsList.contains(queryKeys.key)) return false; - } - QueryStatusFilter queryStatusFilter = - mapQueryStatusFilter(filters.active, filters.inactive); - - if (queryStatusFilter == QueryStatusFilter.none) { - return false; - } else if (queryStatusFilter != QueryStatusFilter.all) { - bool isActive = query.isActive(); - if (queryStatusFilter == QueryStatusFilter.active && !isActive) { - return false; - } - if (queryStatusFilter == QueryStatusFilter.inactive && isActive) { - return false; - } - } - - if (filters.stale != null && query.isStale() != filters.stale) { - return false; - } - - if (filters.fetching != null && query.isFetching() != filters.fetching) { - return false; - } - - if (filters.predicate != null && !filters.predicate!(query)) { - return false; - } - - return true; -} - -void noop([e]) => null; - -bool shallowEqualMap(Map? a, Map? b) { - if ((a != null && b == null) || (b != null && a == null)) { - return false; - } - - for (final item in a!.entries) { - if (a[item.key] != b?[item.key]) return false; - } - - return true; -} - -/// This function returns `a` if `b` is deeply equal\ -/// 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 [List] or [Map] -replaceEqualDeep(a, b) { - if (a == b) { - return a; - } - - 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 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]) { - return Duration( - milliseconds: max( - updatedAt - .add(staleTime ?? Duration.zero) - .difference(DateTime.now()) - .inMilliseconds, - 0, - ), - ); -} - -typedef DataUpdateFunction = TOutput Function(TInput input); diff --git a/packages/fl_query/pubspec.yaml b/packages/fl_query/pubspec.yaml deleted file mode 100644 index 842a822..0000000 --- a/packages/fl_query/pubspec.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: fl_query -description: A new Flutter package project. -version: 0.0.1 -homepage: https://github.com/KRTirtho/fl-query - -environment: - sdk: ">=2.15.1 <3.0.0" - # flutter: ">=1.17.0" - -dependencies: - # flutter: - # sdk: flutter - meta: ^1.3.0 - path: ^1.8.0 - hive: ^2.0.0 - http: ^0.13.0 - collection: ^1.15.0 - uuid: ^3.0.1 - internet_connection_checker: ^0.0.1+3 - -dev_dependencies: - async: ^2.5.0 - mockito: ^5.0.0 - test: ^1.18.2 - coverage: ^1.0.3 - http_parser: ^4.0.0 - lints: ^1.0.1 - build_runner: ^2.1.7 - -# The following section is specific to Flutter. -# flutter: diff --git a/packages/fl_query/test/helpers/utils.dart b/packages/fl_query/test/helpers/utils.dart deleted file mode 100644 index 9a73ba2..0000000 --- a/packages/fl_query/test/helpers/utils.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:fl_query/src/core/core.dart'; -import 'package:uuid/uuid.dart'; - -Uuid uuid = Uuid(); - -QueryKey queryKey() { - return QueryKey("query_${uuid.v4()}"); -} - -class SpyFn { - int _calls = 0; - late T _customFn; - int get calls => _calls; - T get customFn => _customFn; - - SpyFn(); - - SpyFn.withFn(this._customFn); - - fn([Function()? cb]) { - _calls++; - return () => cb?.call(); - } - - fn1([Function()? cb]) { - _calls++; - return (p0) => cb?.call(); - } - - fn2([Function()? cb]) { - _calls++; - return (p0, p1) => cb?.call(); - } - - fn3([Function()? cb]) { - _calls++; - return (p0, p1, p2) => cb?.call(); - } - - fn4([Function()? cb]) { - _calls++; - return (p0, p1, p2, p3) => cb?.call(); - } - - fn5([Function()? cb]) { - _calls++; - return (p0, p1, p2, p3, p4) => cb?.call(); - } -} - -Future sleep(int ms) => Future.delayed(Duration(milliseconds: ms)); diff --git a/packages/fl_query/test/src/core/notify_manager_test.dart b/packages/fl_query/test/src/core/notify_manager_test.dart deleted file mode 100644 index c2e22c5..0000000 --- a/packages/fl_query/test/src/core/notify_manager_test.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:fl_query/src/core/notify_manager.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -class SpyNotifyManager extends NotifyManager { - SpyNotifyManager() : super(); - - int flushCall = 0; - - @override - void flush() { - super.flush(); - flushCall++; - } -} - -void main() { - group("NotifyManager", () { - test( - "Should call _notifyFn in schedule When no callback is batched", - () async { - final NotifyManager notifyManager = NotifyManager(); - int called = 0; - notifyManager.schedule(() => called++); - await Future.delayed(Duration(milliseconds: 1)); - expect(called, equals(1)); - }, - ); - - test( - "Should call default _batchNotifyFn even When multiple level deep callbacks are registered", - () async { - final NotifyManager notifyManager = NotifyManager(); - int level1 = 0; - int level2 = 0; - int level3 = 0; - callback() async { - await Future.delayed(Duration(milliseconds: 20)); - level3++; - } - - notifyManager.batch(() { - notifyManager.batch(() { - notifyManager.schedule(callback); - level2++; - }); - level1++; - }); - await Future.delayed(Duration(milliseconds: 30)); - expect(level1, equals(1)); - expect(level2, equals(1)); - expect(level3, equals(1)); - }, - timeout: Timeout(Duration(minutes: 2)), - ); - - test("Should flush When Exception is thrown in a batched callback", () { - final SpyNotifyManager notifyManager = SpyNotifyManager(); - try { - notifyManager.batch(() { - throw Exception("Damn an exception"); - }); - } catch (e) {} - - expect(notifyManager.flushCall, equals(1)); - }); - }); -} diff --git a/packages/fl_query/test/src/core/online_manager_test.dart b/packages/fl_query/test/src/core/online_manager_test.dart deleted file mode 100644 index 0c6db8b..0000000 --- a/packages/fl_query/test/src/core/online_manager_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'dart:async'; - -import 'package:fl_query/src/core/online_manager.dart'; -import 'package:internet_connection_checker/internet_connection_checker.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; -import './online_manager_test.mocks.dart'; - -@GenerateMocks([InternetConnectionChecker]) -void main() { - group('OnlineManager', () { - late OnlineManager onlineManagerTest; - late StreamController statusController; - late MockInternetConnectionChecker connectionChecker; - setUp(() { - statusController = StreamController.broadcast(); - statusController.add(InternetConnectionStatus.connected); - connectionChecker = MockInternetConnectionChecker(); - when(connectionChecker.hasConnection) - .thenAnswer((_) => Future.value(true)); - when(connectionChecker.hasListeners) - .thenReturn(statusController.hasListener); - when(connectionChecker.onStatusChange) - .thenAnswer((_) => statusController.stream); - onlineManagerTest = OnlineManager(connectionChecker); - }); - - tearDown(() { - statusController.close(); - }); - - test( - 'isOnline Should return true When InternetConnectionChecker.hasConnection is true', - () async { - bool online = await onlineManagerTest.isOnline(); - expect(online, isTrue); - }, - ); - - test( - "setEventListener Should use _online property When setOnline sets _online = false", - () async { - int count = 0; - - setup(void Function(bool?) setOnline) { - Timer(Duration(milliseconds: 20), () { - count++; - setOnline(false); - }); - return () {}; - } - - onlineManagerTest.setEventListener(setup); - await Future.delayed(Duration(milliseconds: 30)); - expect(count, equals(1)); - onlineManagerTest.isOnline().then((online) { - expect(online, isFalse); - }); - }, - ); - - test( - 'setEventListener Should call previous remove handler When replacing an event listener', - () { - int cb1calls = 0; - int cb2calls = 0; - onlineManagerTest.setEventListener((_) => () => cb1calls++); - onlineManagerTest.setEventListener((_) => () => cb2calls++); - expect(cb1calls, equals(1)); - expect(cb2calls, equals(0)); - }, - ); - test( - 'Should replace default window listener When a new event listener is set', - () { - // Should set the default event listener with window event listeners - final unsubscribe = onlineManagerTest.subscribe(); - verify(connectionChecker.onStatusChange.listen).called(1); - // Should replace the window default event listener by a new one - // and it should call window.removeEventListener twice - onlineManagerTest.setEventListener((online) { - return () => null; - }); - expect(connectionChecker.hasListeners, isFalse); - unsubscribe(); - }, - ); - - test('Should cancel StreamSubscription When last listener unsubscribes', - () { - final unsubscribe1 = onlineManager.subscribe(() => null); - final unsubscribe2 = onlineManager.subscribe(() => null); - - verify(connectionChecker.onStatusChange.listen).called(1); - unsubscribe1(); - expect(connectionChecker.hasListeners, isTrue); - unsubscribe2(); - expect(connectionChecker.hasListeners, isFalse); - }, skip: true); - - test('should keep setup function even if last listener unsubscribes', () { - int count = 0; - onlineManager.setEventListener((_) => () => count++); - - final unsubscribe1 = onlineManagerTest.subscribe(() => null); - expect(count, equals(1)); - unsubscribe1(); - - final unsubscribe2 = onlineManager.subscribe(() => null); - expect(count, equals(2)); - unsubscribe2(); - }, skip: true); - }); -} diff --git a/packages/fl_query/test/src/core/online_manager_test.mocks.dart b/packages/fl_query/test/src/core/online_manager_test.mocks.dart deleted file mode 100644 index fc3a6c1..0000000 --- a/packages/fl_query/test/src/core/online_manager_test.mocks.dart +++ /dev/null @@ -1,82 +0,0 @@ -// Mocks generated by Mockito 5.1.0 from annotations -// in fl_query/test/src/core/online_manager_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i3; - -import 'package:internet_connection_checker/internet_connection_checker.dart' - as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeDuration_0 extends _i1.Fake implements Duration {} - -class _FakeAddressCheckResult_1 extends _i1.Fake - implements _i2.AddressCheckResult {} - -/// A class which mocks [InternetConnectionChecker]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockInternetConnectionChecker extends _i1.Mock - implements _i2.InternetConnectionChecker { - MockInternetConnectionChecker() { - _i1.throwOnMissingStub(this); - } - - @override - List<_i2.AddressCheckOptions> get addresses => - (super.noSuchMethod(Invocation.getter(#addresses), - returnValue: <_i2.AddressCheckOptions>[]) - as List<_i2.AddressCheckOptions>); - @override - set addresses(List<_i2.AddressCheckOptions>? _addresses) => - super.noSuchMethod(Invocation.setter(#addresses, _addresses), - returnValueForMissingStub: null); - @override - Duration get checkInterval => - (super.noSuchMethod(Invocation.getter(#checkInterval), - returnValue: _FakeDuration_0()) as Duration); - @override - set checkInterval(Duration? _checkInterval) => - super.noSuchMethod(Invocation.setter(#checkInterval, _checkInterval), - returnValueForMissingStub: null); - @override - _i3.Future get hasConnection => - (super.noSuchMethod(Invocation.getter(#hasConnection), - returnValue: Future.value(false)) as _i3.Future); - @override - _i3.Future<_i2.InternetConnectionStatus> get connectionStatus => - (super.noSuchMethod(Invocation.getter(#connectionStatus), - returnValue: Future<_i2.InternetConnectionStatus>.value( - _i2.InternetConnectionStatus.connected)) - as _i3.Future<_i2.InternetConnectionStatus>); - @override - _i3.Stream<_i2.InternetConnectionStatus> get onStatusChange => - (super.noSuchMethod(Invocation.getter(#onStatusChange), - returnValue: Stream<_i2.InternetConnectionStatus>.empty()) - as _i3.Stream<_i2.InternetConnectionStatus>); - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - @override - bool get isActivelyChecking => - (super.noSuchMethod(Invocation.getter(#isActivelyChecking), - returnValue: false) as bool); - @override - _i3.Future<_i2.AddressCheckResult> isHostReachable( - _i2.AddressCheckOptions? options) => - (super.noSuchMethod(Invocation.method(#isHostReachable, [options]), - returnValue: Future<_i2.AddressCheckResult>.value( - _FakeAddressCheckResult_1())) - as _i3.Future<_i2.AddressCheckResult>); -} diff --git a/packages/fl_query/test/src/core/query_cache_test.dart b/packages/fl_query/test/src/core/query_cache_test.dart deleted file mode 100644 index 88de98d..0000000 --- a/packages/fl_query/test/src/core/query_cache_test.dart +++ /dev/null @@ -1,300 +0,0 @@ -import 'package:fl_query/src/core/core.dart'; -import 'package:fl_query/src/core/query_cache.dart'; -import 'package:fl_query/src/core/retryer.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -import '../../helpers/utils.dart'; - -void main() { - group('QueryCache', () { - late QueryClient queryClient; - late QueryCache queryCache; - - setUp(() { - queryClient = new QueryClient(); - queryCache = queryClient.getQueryCache(); - }); - - tearDown(() { - queryClient.clear(); - }); - group('subscribe', () { - test('Should pass the correct query', () async { - final QueryKey key = queryKey(); - var _event; - subscriber(event) { - _event ??= event; - } - - final unsubscribe = queryCache.subscribe(subscriber); - final Map data = {'foo': "foo"}; - queryClient.setQueryData(key, (_) => data); - final query = queryCache.find(key); - await Future.delayed(Duration(milliseconds: 1)); - expect(_event, isA()); - expect( - (_event as QueryCacheNotifyEvent).type, - QueryCacheNotifyEventType.queryAdded, - ); - expect( - (_event as QueryCacheNotifyEvent).query, - same(query), - ); - unsubscribe(); - }); - - test('Should notify listeners When new query is added', () async { - final key = queryKey(); - late bool called; - callback(_) { - called = true; - } - - queryCache.subscribe(callback); - queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) async => {'data': "data"}, - ); - await Future.delayed(Duration(milliseconds: 100)); - - expect(called, isTrue); - }); - - test('Should include the queryCache and query When notifying listeners', - () async { - final key = queryKey(); - QueryCacheNotifyEvent? _event; - callback(event) => _event ??= event; - queryCache.subscribe(callback); - queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {'data': "data"}, - ); - final query = queryCache.find(key); - await Future.delayed(Duration(milliseconds: 100)); - expect(_event, isA()); - expect(_event?.type, QueryCacheNotifyEventType.queryAdded); - expect(_event?.query, same(query)); - }); - - test('Should notify subscribers When new query with initialData is added', - () async { - final key = queryKey(); - late bool called; - callback(_) => called = true; - queryCache.subscribe(callback); - queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) => {'data': "Data"}, - options: FetchQueryOptions(initialData: {"data": "initial-data"}), - ); - await Future.delayed(Duration(milliseconds: 100)); - expect(called, isTrue); - }); - }); - - group('find', () { - test('Should filter correctly', () async { - final key = queryKey(); - await queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {"data": 'data1'}, - ); - final query = queryCache.find(key); - expect(query, isNotNull); - }); - - test( - 'Should filter correctly When called with exact set to false', - () async { - final key = queryKey(); - await queryClient.prefetchQuery( - queryKey: key, - queryFn: (_) => {"data": 'data1'}, - ); - final query = queryCache.find(key, QueryFilters(exact: false)); - expect(query, isNotNull); - }, - ); - }); - - group('findAll', () { - test('Should filter correctly', () async { - final key1 = queryKey(); - final key2 = queryKey(); - final key3 = QueryKey.fromList(['posts', "1"]); - await queryClient.prefetchQuery( - queryKey: key1, - queryFn: (_) => {"data": 'data1'}, - ); - await queryClient.prefetchQuery( - queryKey: key2, - queryFn: (_) => {"data": 'data2'}, - ); - await queryClient.prefetchQuery( - queryKey: key3, - queryFn: (_) => {"data": 'data4'}, - ); - await queryClient.invalidateQueries(queryKeys: key2); - final query1 = queryCache.find(key1); - final query2 = queryCache.find(key2); - final query4 = queryCache.find(key3); - - expect(queryCache.findAll(key1), equals([query1])); - expect(queryCache.findAll(), equals([query1, query2, query4])); - expect( - queryCache.findAll(key1, QueryFilters(active: false)), - equals([query1]), - ); - expect( - queryCache.findAll(key1, QueryFilters(active: true)), equals([])); - expect(queryCache.findAll(key1, QueryFilters(stale: true)), equals([])); - expect( - queryCache.findAll(key1, QueryFilters(stale: false)), - equals([query1]), - ); - expect( - queryCache.findAll(key1, QueryFilters(stale: false, active: true)), - equals([]), - ); - expect( - queryCache.findAll(key1, QueryFilters(active: false, stale: false)), - equals([query1]), - ); - expect( - queryCache.findAll( - key1, - QueryFilters(active: false, stale: false, exact: true), - ), - equals([query1]), - ); - - expect(queryCache.findAll(key2), equals([query2])); - expect( - queryCache.findAll(key2, QueryFilters(stale: null)), - equals([query2]), - ); - expect( - queryCache.findAll(key2, QueryFilters(stale: true)), - equals([query2]), - ); - expect( - queryCache.findAll(key2, QueryFilters(stale: false)), - equals([]), - ); - - expect( - queryCache.findAll( - null, - QueryFilters(predicate: (query) => query == query4), - ), - equals([query4]), - ); - expect(queryCache.findAll(QueryKey('posts')), equals([query4])); - }); - - test('Should return all the queries When no filters are defined', - () async { - final key1 = queryKey(); - final key2 = queryKey(); - await queryClient.prefetchQuery( - queryKey: key1, - queryFn: (_) => {"data": 'data1'}, - ); - await queryClient.prefetchQuery( - queryKey: key2, - queryFn: (_) { - return {"data": 'data2'}; - }, - ); - expect(queryCache.findAll().length, 2); - }); - }); - - group('QueryCacheConfig.onError', () { - test('should be called when a query errors', () async { - final key = queryKey(); - var errorArg; - var queryArg; - onError(error, query) { - errorArg = error; - queryArg = query; - } - - final testCache = new QueryCache(onError: onError); - final testClient = new QueryClient(queryCache: testCache); - await testClient - .prefetchQuery, dynamic, Map>( - queryKey: key, queryFn: (_) => Future.error('error')); - final query = testCache.find(key); - expect(errorArg, equals("error")); - expect(queryArg, equals(query)); - }); - }); - - group('QueryCacheConfig.onSuccess', () { - test('should be called when a query is successful', () async { - final key = queryKey(); - var dataArg; - var queryArg; - onData(data, query) { - dataArg = data; - queryArg = query; - } - - final testCache = new QueryCache(onData: onData); - final testClient = new QueryClient(queryCache: testCache); - await testClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) => Future.value({"data": 5}), - ); - final query = testCache.find(key); - expect(dataArg, equals({"data": 5})); - expect(queryArg, equals(query)); - }); - }); - group('QueryCache.add', () { - test('should not try to add a query already added to the cache', - () async { - final key = queryKey(); - final hash = key.key; - - await queryClient.prefetchQuery( - queryKey: key, queryFn: (_) => {"data": 'data1'}); - - // Directly add the query from the cache - // to simulate a race condition - final query = queryCache.queriesMap[hash] as Query; - - // No error should be thrown when trying to add the query - queryCache.add(query); - expect(queryCache.queries.length, 1); - - // Clean-up to avoid an error when queryClient.clear() - queryCache.remove(query); - }); - }); - - group('QueryCache.remove', () { - test('should not try to remove a query already removed from the cache', - () async { - final key = queryKey(); - final hash = key.key; - - await queryClient.prefetchQuery( - queryKey: key, queryFn: (_) => {"data": 'data1'}); - - // Directly remove the query from the cache - // to simulate a race condition - final query = queryCache.queriesMap[hash] as Query; - queryCache.queriesMap.remove(hash); - - // No error should be thrown when trying to remove the query - expect(() => queryCache.remove(query), isNot(throwsException)); - }); - }); - }); -} diff --git a/packages/fl_query/test/src/core/query_observer_test.dart b/packages/fl_query/test/src/core/query_observer_test.dart deleted file mode 100644 index df1d921..0000000 --- a/packages/fl_query/test/src/core/query_observer_test.dart +++ /dev/null @@ -1,912 +0,0 @@ -import 'dart:async'; - -import 'package:fl_query/src/core/core.dart'; -import 'package:fl_query/src/core/query_observer.dart'; -import 'package:test/test.dart'; - -import '../../helpers/utils.dart'; - -typedef QueryFn = FutureOr> Function( - QueryFunctionContext); - -void main() { - group('QueryObserver', () { - late QueryClient queryClient; - - setUp(() { - queryClient = QueryClient(); - queryClient.mount(); - }); - - tearDown(() { - queryClient.clear(); - }); - - test('should trigger a fetch when subscribed', () async { - final key = queryKey(); - ; - int calls = 0; - queryFn(context) { - calls++; - return {"data": "data1"}; - } - - final observer = QueryObserver( - queryClient, - QueryObserverOptions(queryKey: key, queryFn: queryFn), - ); - final unsubscribe = observer.subscribe(); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect(calls, 1); - }); - - test('should notify when switching query', () async { - final key1 = queryKey(); - final key2 = queryKey(); - final List results = []; - final observer = QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key1, - queryFn: (_) => {"data": 1}, - ), - ); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions( - QueryObserverOptions(queryKey: key2, queryFn: (_) => {"data": 2}), - ); - await Future.delayed(Duration(milliseconds: 2)); - unsubscribe(); - expect(results.length, 4); - expect(results[0].data, isNull); - expect(results[0].status, QueryStatus.loading); - expect(results[1].data, {"data": 1}); - expect(results[1].status, QueryStatus.success); - expect(results[2].data, isNull); - expect(results[2].status, QueryStatus.loading); - expect(results[3].data, {"data": 2}); - expect(results[3].status, QueryStatus.success); - }); - - test('should be able to fetch with a selector', () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) => ({"myCount": data?["count"]}), - )); - QueryObserverResult? observerResult; - final unsubscribe = observer.subscribe((result) { - observerResult = result; - }); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect( - observerResult?.data, - equals({"myCount": 1}), - ); - }); - - test('should be able to fetch with a selector using the fetch method', - () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) => ({"myCount": data?["count"]}), - )); - final observerResult = await observer.refetch(); - expect(observerResult?.data, equals({"myCount": 1})); - }); - - test('should run the selector again if the data changed', () async { - final key = queryKey(); - ; - int count = 0; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => Map.from({"count": count}), - select: (data) { - count++; - return Map.from({"myCount": data?["count"]}); - }, - )); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 2); - expect(observerResult1?.data, equals({"myCount": 0})); - expect(observerResult2?.data, equals({"myCount": 1})); - }); - - test('should run the selector again if the selector changed', () async { - final key = queryKey(); - ; - int count = 0; - final List results = []; - final queryFn = (_) => ({"count": 1}); - select1(data) { - count++; - return {"myCount": data?["count"]}; - } - - select2(_data) { - count++; - return {"myCount": 99}; - } - - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select1, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select2, - )); - await Future.delayed(Duration(milliseconds: 1)); - //! Currently causing an extra call for refetch - //! select shouldn't be called when refetch is called - await observer.refetch(); - unsubscribe(); - expect(count, 2); - expect(results.length, 5); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.success); - expect(results[1].isFetching, false); - expect(results[1].data, {"myCount": 1}); - expect(results[2].status, QueryStatus.success); - expect(results[2].isFetching, false); - expect(results[2].data, {"myCount": 99}); - expect(results[3].status, QueryStatus.success); - expect(results[3].isFetching, true); - expect(results[3].data, {"myCount": 99}); - expect(results.last.status, QueryStatus.success); - expect(results.last.isFetching, false); - expect(results.last.data, {"myCount": 99}); - }); - test( - 'should not run the selector again if the data and selector did not change', - () async { - final key = queryKey(); - ; - int count = 0; - final List results = []; - final queryFn = (_) => {"count": 1}; - select(data) { - count++; - return {"myCount": data["count"]}; - } - - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: queryFn, - select: select, - )); - await Future.delayed(Duration(milliseconds: 1)); - await observer.refetch(); - unsubscribe(); - expect(count, 1); - expect(results.length, 4); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.success); - expect(results[1].isFetching, false); - expect(results[1].data, {"myCount": 1}); - expect(results[2].status, QueryStatus.success); - expect(results[2].isFetching, true); - expect(results[2].data, {"myCount": 1}); - expect(results.last.status, QueryStatus.success); - expect(results.last.isFetching, false); - expect(results.last.data, {"myCount": 1}); - }); - - test('should not run the selector again if the data did not change', - () async { - final key = queryKey(); - ; - int count = 0; - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": 1}, - select: (data) { - count++; - return {"myCount": data?["count"]}; - }, - ), - ); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 1); - expect(observerResult1?.data, equals({"myCount": 1})); - expect(observerResult2?.data, equals({"myCount": 1})); - }); - - test('should always run the selector again if selector throws an error', - () async { - final key = queryKey(); - ; - final List results = []; - select(data) { - throw new Exception('selector error'); - } - - queryFn(_) => ({"count": 1}); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - select: select, - )); - final unsubscribe = observer.subscribe((result) { - results.add(result); - }); - await Future.delayed(Duration(milliseconds: 1)); - await observer.refetch(); - unsubscribe(); - expect(results.length, 5); - expect(results.first.status, QueryStatus.loading); - expect(results.first.isFetching, isTrue); - expect(results.first.data, isNull); - expect(results[1].status, QueryStatus.error); - expect(results[1].isFetching, false); - expect(results[1].data, isNull); - expect(results[2].status, QueryStatus.error); - expect(results[2].isFetching, true); - expect(results[2].data, isNull); - expect(results[3].status, QueryStatus.error); - expect(results[3].isFetching, false); - expect(results[3].data, isNull); - expect(results.last.status, QueryStatus.error); - expect(results.last.isFetching, false); - expect(results.last.data, isNull); - }); - - test('should structurally share the selector', () async { - final key = queryKey(); - ; - int count = 0; - final observer = new QueryObserver( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"count": ++count}, - select: (data) { - return {"myCount": 1}; - }, - )); - final observerResult1 = await observer.refetch(); - final observerResult2 = await observer.refetch(); - expect(count, 2); - expect(observerResult1?.data, isNotNull); - expect(observerResult1?.data, equals(observerResult2?.data)); - }); - - test('should not trigger a fetch when subscribed and disabled', () async { - int count = 0; - final key = queryKey(); - ; - final observer = new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: (_) { - count++; - return {"data": null}; - }, - enabled: false, - )); - final unsubscribe = observer.subscribe(); - await Future.delayed(Duration(milliseconds: 1)); - unsubscribe(); - expect(count, 0); - }); - - test('should not trigger a fetch when not subscribed', () async { - int count = 0; - final key = queryKey(); - ; - new QueryObserver( - queryClient, - QueryObserverOptions( - queryKey: key, - queryFn: (_) { - count++; - return {"data": null}; - }, - )); - await Future.delayed(Duration(milliseconds: 1)); - expect(count, 0); - }); - - test('should be able to watch a query without defining a query function', - () async { - int count = 0; - int subscribeCount = 0; - final key = queryKey(); - ; - queryFn(_) { - count++; - return {"data": null}; - } - - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - enabled: false, - )); - - final unsubscribe = observer.subscribe((_) { - subscribeCount++; - }); - await queryClient.fetchQuery(queryKey: key, queryFn: queryFn); - unsubscribe(); - expect(count, 1); - expect(subscribeCount, 2); - }); - - test('should accept unresolved query config in update function', () async { - final key = queryKey(); - ; - final observer = QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - - final List results = []; - final unsubscribe = observer.subscribe((x) { - results.add(x); - }); - observer.setOptions( - QueryObserverOptions( - enabled: false, - staleTime: Duration(milliseconds: 10), - ), - ); - int count = 0; - - queryFn(_) { - count++; - return {"data": null}; - } - - await queryClient.fetchQuery(queryKey: key, queryFn: queryFn); - await sleep(100); - unsubscribe(); - expect(count, 1); - expect(results.length, 3); - expect(results[0].isStale, true); - expect(results[1].isStale, false); - expect(results[2].isStale, true); - }); - - test('should be able to handle multiple subscribers', () async { - final key = queryKey(); - - int count = 0; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - final List results1 = []; - final List results2 = []; - final unsubscribe1 = observer.subscribe((x) { - results1.add(x); - }); - final unsubscribe2 = observer.subscribe((x) { - results2.add(x); - }); - await queryClient - .fetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) { - count++; - return {"data": false}; - }, - ); - await sleep(50); - unsubscribe1(); - unsubscribe2(); - expect(count, 1); - expect(results1.length, 2); - expect(results2.length, 2); - expect(results1[0].data?["data"], isNull); - expect(results1[1].data?["data"], isFalse); - expect(results2[0].data?["data"], isNull); - expect(results2[1].data?["data"], isFalse); - }); - - test('should be able to resolve a promise', () async { - final key = queryKey(); - int count = 0; - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - QueryObserverResult, dynamic>? value; - observer.getNextResult().then((x) { - value = x; - }); - queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, - queryFn: (_) { - count++; - return {"data": "a data"}; - }); - await sleep(50); - expect(count, 1); - expect(value?.data?["data"], "a data"); - }); - - test('should be able to resolve a promise with an error', () async { - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - enabled: false, - )); - var error; - await observer.getNextResult(true).catchError((e) async { - error = e; - return e; - }); - await queryClient - .prefetchQuery, dynamic, Map>( - queryKey: key, queryFn: (_) => Future.error('reject')); - await sleep(50); - expect(error, 'reject'); - }, skip: true); - - test('should stop retry when unsubscribing', () async { - int count = 0; - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) { - count++; - return Future.error({"data": 'reject'}); - }, - retry: (_, __) => 10, - retryDelay: (_, __) => 50, - ), - ); - final unsubscribe = observer.subscribe(); - await sleep(70); - unsubscribe(); - await sleep(200); - expect(count, 2); - }, skip: true); - - test('should clear interval when unsubscribing to a refetchInterval query', - () async { - final key = queryKey(); - - final fetchData = - (_) => Future>.error({'data': null}); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: fetchData, - cacheTime: Duration.zero, - refetchInterval: (_, __) => Duration(milliseconds: 1), - )); - final unsubscribe = observer.subscribe(); - // @ts-expect-error - expect(observer.refetchInterval, isNotNull); - unsubscribe(); - // @ts-expect-error - expect(observer.refetchInterval, isNull); - await sleep(10); - expect(queryClient.getQueryCache().find(key), isNull); - }); - - test( - 'uses placeholderData as non-cache data when loading a query with no data', - () async { - final key = queryKey(); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {'data': 'data'}, - placeholderData: {"data": 'placeholder'}, - )); - final result = observer.getCurrentResult(); - expect(result?.status, QueryStatus.success); - - final List results = []; - - final unsubscribe = observer.subscribe((x) { - results.add(x); - }); - - await sleep(10); - unsubscribe(); - - expect(results.length, 2); - expect(results.first.status, QueryStatus.success); - expect(results.first.data, equals({"data": 'placeholder'})); - expect(results.last.status, QueryStatus.success); - expect(results.last.data, equals({'data': 'data'})); - }); - - test( - 'the retryer should not throw an error when reject if the retrier is already resolved', - () async { - final key = queryKey(); - int count = 0; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) { - count++; - return Future.error({'reject': count}); - }, - retry: (_, __) => 1, - retryDelay: (_, __) => 20, - )); - - final unsubscribe = observer.subscribe(); - - // Simulate a race condition when an unsubscribe and a retry occur. - await sleep(20); - unsubscribe(); - - // A second reject is triggered for the retry - // but the retryer has already set isResolved to true - // so it does nothing and no error is thrown - - // Should not log an error - queryClient.clear(); - await sleep(40); - expect(true, true); - // expect(consoleMock).not.toHaveBeenNthCalledWith(1, 'reject 1') - - // consoleMock.mockRestore() - }, skip: true); - - test('getCurrentQuery should return the current query', () async { - final key = queryKey(); - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, queryFn: (_) => {'data': 'data'})); - - expect(observer.getCurrentQuery().queryKey, key); - }); - - test('should throw an error if throwOnError option is true', () async { - final key = queryKey(); - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => Future.error({'error': 'error'}), - retry: (_, __) => 0, - )); - - var error = null; - try { - await observer.refetch(options: RefetchOptions(throwOnError: true)); - } catch (err) { - error = err; - } - - expect(error, equals({'error': 'error'})); - }); - - test( - 'should not refetch in background if refetchIntervalInBackground is false', - () async { - final key = queryKey(); - final spy = SpyFn(); - - // focusManager.setFocused(false) - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: spy.fn1(), - refetchIntervalInBackground: false, - refetchInterval: (_, __) => Duration(milliseconds: 10), - )); - - final unsubscribe = observer.subscribe(); - await sleep(30); - - expect(spy.calls, 1); - - // Clean-up - unsubscribe(); - // focusManager.setFocused(true) - }); - - test( - 'should not use replaceEqualDeep for select value when structuralSharing option is true', - () async { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - final unsubscribe = observer.subscribe(); - - await sleep(10); - expect(observer.getCurrentResult()?.data, data); - - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - structuralSharing: false, - select: (_) => selectedData, - )); - - await observer.refetch(filters: RefetchableQueryFilters(queryKey: key)); - expect(observer.getCurrentResult()?.data, selectedData); - unsubscribe(); - }); - - test('select function error using placeholderdata should log an error', () { - final key = queryKey(); - - QueryObserver, dynamic, Map, - Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => {"data": 'data'}, - placeholderData: {"data": 'placeholderdata'}, - select: (_) { - throw new Exception('error'); - }, - )); - - expect(true, true); - // expect(consoleMock).toHaveBeenNthCalledWith(1, new Error('error')) - - // consoleMock.mockRestore() - }); - - test( - 'should not use replaceEqualDeep for select value when structuralSharing option is true and placeholderdata is defined', - () { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData1 = {"value": 'data'}; - final selectedData2 = {"value": 'data'}; - final placeholderData1 = {"value": 'data'}; - final placeholderData2 = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData1; - }, - placeholderData: placeholderData1, - )); - observer.setOptions(QueryObserverOptions( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData2; - }, - placeholderData: placeholderData2, - structuralSharing: false, - )); - - expect(observer.getCurrentResult()?.data, equals(selectedData2)); - }); - - test( - 'should not use an undefined value returned by select as placeholderdata', - () { - final key = queryKey(); - - final data = {"value": 'data'}; - final selectedData = {"value": 'data'}; - final placeholderData1 = {"value": 'data'}; - final placeholderData2 = {"value": 'data'}; - - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) => data, - )); - - observer.setOptions(QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return selectedData; - }, - placeholderData: placeholderData1, - )); - - expect(observer.getCurrentResult()?.isPlaceholderData, isTrue); - - observer.setOptions(QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: (_) => data, - select: (_) { - return {}; - }, - placeholderData: placeholderData2, - )); - - expect(observer.getCurrentResult()?.isPlaceholderData, isFalse); - }); - - test( - 'updateResult should not notify cache listeners if cache option is false', - () async { - final key = queryKey(); - - final data1 = {"value": 'data 1'}; - final data2 = {"value": 'data 2'}; - - await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data1); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>(queryKey: key)); - await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data2); - - final spy = SpyFn(); - final unsubscribe = queryClient.getQueryCache().subscribe(spy.fn1()); - observer.updateResult(NotifyOptions(cache: false)); - - expect(spy.calls, 0); - - unsubscribe(); - }); - - test( - 'should not notify observer when the stale timeout expires and the current result is stale', - () async { - final key = queryKey(); - final queryFn = (_) => {'data': "data"}; - - await queryClient.prefetchQuery(queryKey: key, queryFn: queryFn); - final observer = new QueryObserver, dynamic, - Map, Map>( - queryClient, - QueryObserverOptions, dynamic, - Map, Map>( - queryKey: key, - queryFn: queryFn, - staleTime: Duration(milliseconds: 20), - )); - - final spy = SpyFn(); - final unsubscribe = observer.subscribe(spy.fn1()); - await queryClient.refetchQueries(queryKeys: key); - await sleep(10); - - // Force isStale to true - // because no use case has been found to reproduce this condition - // @ts-ignore - // observer.getCurrentResult().isStale = true; - await sleep(30); - expect(spy.calls, 0); - unsubscribe(); - }); - }); -} diff --git a/packages/fl_query/test/src/core/utils_test.dart b/packages/fl_query/test/src/core/utils_test.dart deleted file mode 100644 index 77aa934..0000000 --- a/packages/fl_query/test/src/core/utils_test.dart +++ /dev/null @@ -1,320 +0,0 @@ -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> prev = { - "todos": [ - { - "id": '1', - "meta": {"createdAt": 0}, - "state": {"done": false} - }, - { - "id": '2', - "meta": {"createdAt": 0}, - "state": {"done": true} - }, - ], - }; - final 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() - // }) - }); - }); -}