qora 0.8.0
qora: ^0.8.0 copied to clipboard
A powerful async state management library for Dart, inspired by TanStack Query
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased #
0.8.0 - 2026-03-12 #
Added #
CacheEntry.setError(Object error)— transitions an entry toFailure<T>using the entry's own reifiedT; eliminates theFailure<dynamic>cast error that occurred whendebugSetQueryErrorused an untyped cache lookup.CacheEntry.markStale()— sets an internal_forcedStaleflag without pushing any state update to observers;isStale()now returnstruewhenever_forcedStaleis set, regardless ofstaleTime; the flag is cleared byupdateState().QoraClient.markStale(Object key)— silently flags a cache entry stale without transitioning toLoadingor triggering an immediate refetch; active observers see no change; the nextfetchQuery/watchQuerymount will seeisStale() == trueand trigger an SWR background revalidation.QoraTracker.onQueryRemoved(String key)— hook called whenremoveQueryevicts a cache entry;NoOpTrackerships an empty override.QoraTracker.onQueryMarkedStale(String key)— hook called whenmarkStalesilently flags an entry; differs fromonQueryInvalidatedin that no state transition or timeline fetch entry is implied;NoOpTrackerships an empty override.
Fixed #
QoraClient.debugSetQueryError()— previously called_cache.get<dynamic>()and pushedFailure<dynamic>into aStreamController<QoraState<T>>, causing aTypeErrorat runtime; now uses_cache.peek()and delegates toCacheEntry.setError()so theFailureis instantiated with the correct reifiedT.QoraClient.removeQuery()— did not notify the tracker; now calls_tracker.onQueryRemoved(sk)so DevTools overlays remove the corresponding row immediately.
0.7.0 - 2026-03-03 #
Added #
CancelToken— cooperative cancellation forfetchQuery,watchQuery, andprefetch; state restored to pre-fetch snapshot on cancellationQoraCancelException— thrown to the caller when a fetch is cancelled viaCancelTokenQoraTracker.onQueryCancelled(String key)— hook called when a fetch is cancelled;NoOpTrackerships an empty overrideQueryFiltertypedef —bool Function(String key, QoraState<dynamic> state, QoraOptions? lastOptions)— richer invalidation predicateQoraClient.invalidateQueries({required QueryFilter filter})— bulk invalidation usingQueryFilterCacheEntry.lastOptions— records the options from the last successful fetch; used byinvalidateQueriesQoraOptions.dependsOn— declares a query dependency;watchQueryfires reactively when the dependency resolves,fetchQuerythrowsStateErrorif unresolved,prefetchsilently skipsQoraClient.queueHydration(key, data, {updatedAt})— enqueue a pre-deserialized value for lazy typed injection; shared hydration mechanism forPersistQoraClientandSsrHydratorQoraClient.removeHydrationEntry(key)—@protected; removes a pending hydration entryQoraClient.clearHydrationQueue()—@protected; clears all pending hydration entriesSsrHydrator— Flutter Web SSR hydrator; readswindow.__QORA_STATE__, validates strictly, and callsqueueHydration(); XSS-safe viadartify()and per-deserializer try/catch; no-op stub on non-web platformsQoraTracker.onQueryFetching(String key)— hook called when a query transitions toLoading; pairs withonQueryFetchedfor fetch-duration tracking;NoOpTrackerships an empty overrideInfiniteData<TData, TPageParam>— immutable container for paginated pages;append(),prepend(),dropFirst(),dropLast(),flatten()InfiniteQueryState<TData, TPageParam>— sealed state machine:InfiniteInitial,InfiniteLoading,InfiniteSuccess,InfiniteFailureInfiniteQueryOptions<TData, TPageParam>— pagination config:initialPageParam,getNextPageParam,getPreviousPageParam,maxPagesInfiniteQueryObserver<TData, TPageParam>— pagination engine:fetch(),fetchNextPage(),fetchPreviousPage(),refetch()InfiniteQueryFunction<TData, TPageParam>typedef —Future<TData> Function(TPageParam pageParam)QoraClient.watchInfiniteState,getInfiniteQueryState,getInfiniteQueryData,setInfiniteQueryData,updateInfiniteQueryState,invalidateInfiniteQuery
Changed #
PersistQoraClienthydration delegated toQoraClient— hydration infrastructure lifted to the base class;PersistQoraClient.hydrate()now callsqueueHydration(); the six typed overrides removed
0.6.0 - 2026-03-02 #
Added #
NetworkMode— per-query enum (online/always/offlineFirst)FetchStatus— second-axis enum (fetching/paused/idle); observable viaQoraClient.watchFetchStatus(key)ReconnectStrategy— thundering-herd prevention on reconnect:maxConcurrent+jitter; named constructorsinstant()andconservative()OfflineMutationQueue— FIFO queue for offline writes; replays on reconnect;stopOnFirstErrorflag;OfflineReplayResultsurfacePendingMutation— type-erased queued-write containerQoraOfflineException— thrown byfetchQuerywhen offline with no cached dataQoraClient.attachConnectivityManager()— late-attach aConnectivityManager; called automatically byQoraScopeQoraClient.isOnline/networkStatus— real-time connectivity gettersQoraClient.watchFetchStatus(key)— stream ofFetchStatustransitionsQoraClient.offlineMutationQueue— sharedOfflineMutationQueueinstanceMutationSuccess.isOptimistic—truewhen the mutation was queued offline with anoptimisticResponseMutationOptions.offlineQueue— opt a mutation into theOfflineMutationQueueMutationOptions.optimisticResponse— syntheticTDatafor immediate UI feedbackQoraClientConfig.reconnectStrategy— global reconnect strategy; defaults to 5 concurrent / 100 ms jitterQoraOptions.networkMode— per-queryNetworkMode; defaults toNetworkMode.online
0.5.0 - 2026-03-01 #
Added #
PersistQoraClient—QoraClientsubclass that persists query results to aStorageAdapterand restores them on startupStorageAdapter— abstract key/value interface; ships withInMemoryStorageAdapterQoraSerializer<T>—toJson/fromJsonpair for a typePersistQoraClient.registerSerializer<T>— register a serializer; accepts optionalnamefor obfuscation safetyPersistQoraClient.hydrate()— reads storage, validates TTL, queues valid entries for lazy hydrationPersistQoraClient.persistQuery<T>— force-persist the current cached value with an optional TTL overridePersistQoraClient.evictFromStorage/clearStorage— storage-only evictionQoraClient.hydrateQuery<T>— inject a typedSuccess<T>with a customupdatedAtinto anInitialentryQoraClient.onFetchSuccess<T>—@protectedhook called after every successful fetch; used byPersistQoraClientto auto-persist
Fixed #
QoraStateSerialization.toJsonwrote'type': 'error'forFailurewhilefromJsonmatched on'failure';Failurestates were never restored correctly
0.4.0 - 2026-02-28 #
Added #
QoraTracker— abstract observability interface with lifecycle hooks for queries, mutations, and cache eventsNoOpTracker— defaultconstimplementation with zero overheadQoraClient(tracker:)— optional tracker injection; defaults toNoOpTracker
0.3.0 - 2026-02-25 #
Added #
MutationController<TData, TVariables, TContext>— manages the full mutation lifecycle:Idle → Pending → Success | FailureMutationState<TData, TVariables>— sealed class:MutationIdle,MutationPending,MutationSuccess,MutationFailure; each carries typedvariablesMutationOptions<TData, TVariables, TContext>— lifecycle callbacks:onMutate,onSuccess,onError,onSettled;retryCount/retryDelayMutatorFunction<TData, TVariables>typedefMutationTracker— abstract interface implemented byQoraClient; decouplesMutationControllerfrom the clientMutationEvent— type-erased event on every mutation state transition;mutatorId,status,data,error,variables,metadata,timestampQoraClientimplementsMutationTracker—mutationEventsstream,activeMutationssnapshot;debugInfo()now includesactive_mutationsMutationController.metadata—Map<String, Object?>?forwarded to everyMutationEventMutationController.id— uniquemutation_NidentifierMutationStateExtensions—fold<R>()andstatusgetterMutationStatusenum —idle | pending | success | errorMutationStateStreamExtensions—whereSuccess(),whereError(),dataOrNull()
Changed #
MutationFunctionrenamed toMutatorFunction
Fixed #
MutationController.streamrace condition — events emitted synchronously before the first microtask were lost; fixed with aStreamControllerwhoseonListenruns synchronously
0.2.0 - 2026-02-22 #
Added #
watchState<T>(key)— observe-only stream; no fetch triggeredprefetch<T>()— pre-warm the cache before navigation; no-op if already freshrestoreQueryData<T>(key, snapshot)— roll back an optimistic updateremoveQuery(key)— evict a single query and cancel any in-flight requestclear()— evict all queries and cancel all in-flight requestscachedKeys— all currently cached normalised query keysdebugInfo()— cache and pending-request count snapshot
Changed #
QoraState<T>rewritten as a sealed class —Initial | Loading | Success | Failure;LoadingandFailurecarrypreviousData- Polymorphic key system — all APIs now accept
Object(plainList<dynamic>orQoraKey); deep structural equality KeyCacheMap— custom map with deep recursive equality and order-independent map-key comparisoninvalidate(key)replacesinvalidateQuery(key)invalidateWhere(predicate)replacesinvalidateQueries(predicate)- Package structure reorganised —
cache/,config/,client/,key/,state/,utils/
Fixed #
- Normalised key lists wrapped in
List.unmodifiable()to prevent accidental mutation