qora 0.6.0
qora: ^0.6.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.6.0 - 2026-03-02 #
Added #
NetworkMode— per-query enum (online/always/offlineFirst) controlling fetch behaviour while the device is offlineFetchStatus— second-axis enum (fetching/paused/idle) emitted independently ofQoraState; observable viaQoraClient.watchFetchStatus(key)ReconnectStrategy— global config for thundering-herd prevention on reconnect:maxConcurrentbatching + randomjitterbetween batches; named constructorsinstant()andconservative()OfflineMutationQueue— FIFO in-memory queue for write operations that could not be sent offline; replays in order on reconnect;stopOnFirstErrorflag for ordered dependencies;OfflineReplayResultsurface (succeeded / failed / skipped counts)PendingMutation— type-erased container (mutatorId,variables,enqueuedAt,replay) representing a queued writeQoraOfflineException— thrown byfetchQuerywhen offline and no cached data existsQoraClient.attachConnectivityManager()— late-attach aConnectivityManagerafter construction; called automatically byQoraScopeQoraClient.isOnline/networkStatus— real-time connectivity state gettersQoraClient.watchFetchStatus(key)— stream ofFetchStatustransitions for a given query keyQoraClient.offlineMutationQueue— exposes the sharedOfflineMutationQueueinstanceMutationSuccess.isOptimistic—boolflag;truewhen the mutation was queued offline and anoptimisticResponsewas provided; surfaced on the sealed class getter,when()/maybeWhen()callbacks, andMutationEventMutationOptions.offlineQueue— opt a mutation into theOfflineMutationQueuewhen offlineMutationOptions.optimisticResponse— function returning a syntheticTDatafor immediate UI feedback while the mutation is queuedQoraClientConfig.reconnectStrategy— inject aReconnectStrategy; defaults toReconnectStrategy()(5 concurrent, 100 ms jitter)QoraOptions.networkMode— per-queryNetworkMode; defaults toNetworkMode.online
0.5.0 - 2026-03-01 #
Added #
PersistQoraClient—QoraClientsubclass that persists successful query results to aStorageAdapterand restores them on startup viahydrate(); fully backwards-compatible drop-in replacementStorageAdapter— abstract key/value interface for pluggable storage backends; ships withInMemoryStorageAdapter(suitable for tests)QoraSerializer<T>— pair oftoJson/fromJsonconverters (dynamicin/out); supports objects, collections, and primitives without extra wrappersPersistQoraClient.registerSerializer<T>— registers a serializer for typeT; accepts an optional explicitnameto remain stable under Flutter Web /--obfuscatetree-shakingPersistQoraClient.hydrate()— reads all valid entries from storage, validates TTL, and queues them in a lazy typed hydration map; corrupt/expired entries are deleted and skippedPersistQoraClient.persistQuery<T>— force-persist the current cached value with an optional per-entry TTL overridePersistQoraClient.evictFromStorage/clearStorage— targeted and bulk storage eviction without touching the in-memory cacheQoraClient.hydrateQuery<T>— inject a typedSuccess<T>with a customupdatedAtinto anInitialcache entry; used internally by persistence and available for advanced use casesQoraClient.onFetchSuccess<T>— protected override hook called after every successful fetch (direct and SWR background revalidation); enables subclasses to react without duplicating fetch logic
Fixed #
QoraStateSerialization.toJsonwrote'type': 'error'forFailurestates whilefromJsonmatched on'failure';Failurestates were never correctly restored from JSON
0.4.0 - 2026-02-28 #
Added #
QoraTracker— abstract observability interface with lifecycle hooks for queries, mutations, and cache eventsNoOpTracker— defaultconstimplementation with zero overhead (production safe)QoraClient(tracker:)— optional tracker injection; defaults toNoOpTracker
0.3.0 - 2026-02-25 #
Added #
MutationController<TData, TVariables, TContext>— standalone controller managing the full mutation lifecycle:MutationIdle → MutationPending → MutationSuccess | MutationFailureMutationState<TData, TVariables>— sealed class hierarchy with four variants:MutationIdle,MutationPending,MutationSuccess,MutationFailure; each carries typedvariablesfor full traceabilityMutationOptions<TData, TVariables, TContext>— per-mutation configuration with lifecycle callbacks:onMutate(variables)— called before the mutator; return value becomesTContext(snapshot for rollback)onSuccess(data, variables, context)— called on successonError(error, variables, context)— called on failure; usecontextto roll back optimistic updates viarestoreQueryDataonSettled(data, error, variables, context)— called after either outcomeretryCount/retryDelay— optional retry with exponential backoff (default: 0 retries)
MutatorFunction<TData, TVariables>typedef — mirrorsQueryFunction<T>for consistency (mutatorparameter naming mirrorsfetcher)MutationTracker— abstract interface implemented byQoraClient; decouplesMutationControllerfrom the client to prevent circular importsMutationEvent— type-erased event emitted on every mutation state transition:mutatorId— stablemutation_Nidentifier correlating events to a specific controllerstatus/isIdle/isPending/isSuccess/isError/isFinished— coarse-grained status helpersdata,error,variables— type-erased payloadmetadata— optionalMap<String, Object?>forwarded fromMutationController.metadata; attach domain context (e.g.{'category': 'auth'}) without modifying the core schematimestamp— emission time
QoraClientimplementsMutationTracker— global DevTools observability for all tracked mutations:mutationEvents—Stream<MutationEvent>of every state transition from all tracked controllersactiveMutations— snapshotMap<String, MutationEvent>of currently running (pending) mutations only; finished entries are auto-purged onSuccess/Failure, preventing memory accumulation of "ghost" entriesdebugInfo()now includesactive_mutationscount
MutationController.metadata— optionalMap<String, Object?>?forwarded verbatim to everyMutationEvent; enables DevTools labelling without schema changesMutationController.id— uniquemutation_Nidentifier (monotonically increasing counter)MutationStateExtensions—fold<R>()exhaustive mapper andstatusgetter returningMutationStatusenumMutationStatusenum — coarse-grainedidle | pending | success | errorvalues with boolean gettersMutationStateStreamExtensions—whereSuccess(),whereError(),dataOrNull()stream operators
Changed #
MutationFunctionrenamed toMutatorFunction— aligns naming with thefetcher/mutatorparameter convention used throughout the API
Fixed #
MutationController.streamrace condition — the previousasync*implementation started the generator in the next microtask, causing events emitted before the first microtask (e.g. callingmutatesynchronously afterlisten) to be lost on the broadcast stream. The stream getter now uses aStreamControllerwhoseonListencallback runs synchronously, capturing the current state and subscribing to the broadcast stream with no timing gap
0.2.0 - 2026-02-22 #
Added #
watchState<T>(Object key)— observe-only stream that subscribes to a query's state without triggering any fetch; ideal for derived UI components (e.g. badges, avatar widgets)prefetch<T>()— pre-warm the cache before navigation without blocking the UI; no-op if data is already freshrestoreQueryData<T>(key, snapshot)— roll back an optimistic update; removes the entry from cache if snapshot isnullremoveQuery(key)— evict a single query from cache and cancel any pending request for itclear()— evict all cached queries and cancel all in-flight requests (e.g. on user logout)cachedKeysgetter — returns all currently cached normalised query keys for debugging or bulk operationsdebugInfo()— returns a map snapshot of cache and pending-request counts
Changed #
QoraState<T>rewritten as a sealed class — four exhaustive variants:Initial | Loading | Success | Failure.LoadingandFailurenow carrypreviousDatafor graceful degradation (stale data shown during refetch or on error)- Polymorphic key system —
fetchQuery,watchQuery,watchState,prefetch,setQueryData,restoreQueryData,invalidate,getQueryData, andgetQueryStatenow acceptObject(plainList<dynamic>orQoraKey); keys are normalised and compared with deep structural equality KeyCacheMap— custom map implementation with deep recursive equality and order-independent map-key comparison; eliminates reference-equality bugsinvalidate(key)replaces the oldinvalidateQuery(key)invalidateWhere(predicate)replaces the oldinvalidateQueries(predicate); predicate receives the normalisedList<dynamic>key- Package structure reorganised — source files split into
cache/,config/,client/,key/,state/, andutils/subdirectories for clarity
Fixed #
- Defensive immutability: normalised key lists are wrapped in
List.unmodifiable()to prevent accidental mutation by callers