PersistQoraClient
PersistQoraClient extends QoraClient. Every method from the base client works unchanged. This page documents the additional persistence API.
See the Persistence guide for a walkthrough with full examples.
PersistQoraClient
Constructor
PersistQoraClient({
required StorageAdapter storage,
Duration persistDuration = const Duration(days: 7),
QoraClientConfig? config,
QoraTracker? tracker,
})
| Parameter | Description |
|---|---|
storage | The storage backend. Must be initialised via StorageAdapter.init() before calling hydrate(). |
persistDuration | Default TTL for persisted entries. If null, entries are persisted indefinitely. Pass Duration.zero to disable persistence (entries expire immediately). |
config | Forwarded to QoraClient. |
tracker | Forwarded to QoraClient. |
final client = PersistQoraClient(
storage: InMemoryStorageAdapter(),
persistDuration: const Duration(days: 7),
config: const QoraClientConfig(
defaultOptions: QoraOptions(staleTime: Duration(minutes: 5)),
debugMode: true,
),
);
registerSerializer
void registerSerializer<T>(
QoraSerializer<T> serializer, {
String? name,
})
Register a QoraSerializer for type T.
| Parameter | Description |
|---|---|
serializer | Provides toJson / fromJson converters for T. |
name | The stable string identifier stored as a type discriminator on disk. Defaults to T.toString(). Pass an explicit value on Flutter Web or when --obfuscate is active. |
- Calling
registerSerializertwice for the sameTor the samenamereplaces the previous entry. - Must be called before
hydrate().
client.registerSerializer<User>(
QoraSerializer(toJson: (u) => u.toJson(), fromJson: User.fromJson),
name: 'User',
);
client.registerSerializer<List<Post>>(
QoraSerializer(
toJson: (posts) => posts.map((p) => p.toJson()).toList(),
fromJson: (json) => (json as List).map((e) => Post.fromJson(e)).toList(),
),
name: 'List<Post>',
);
hydrate
Future<void> hydrate()
Load all valid persisted entries from StorageAdapter into a pending hydration queue.
Entries are injected into the typed cache lazily: on the first call to fetchQuery<T>, watchQuery<T>, getQueryData<T>, etc., for that key. The restored Success state carries the original persistedAt as updatedAt, so staleTime evaluates freshness correctly.
Per-entry behaviour:
| Condition | Action |
|---|---|
| JSON decode failure | Delete from storage, skip |
| TTL expired | Delete from storage, skip |
| No serializer registered for the type | Log (if debugMode), skip |
fromJson throws | Delete from storage, log, skip |
| Valid | Queue in pending hydration map |
Call once at startup, after all serializers are registered and before runApp:
await storage.init();
client.registerSerializer<User>(...);
await client.hydrate(); // ← before runApp
runApp(...);
persistQuery
Future<void> persistQuery<T>(Object key, {Duration? ttl})
Force-persist the current cached value for key. No-op if the query is not in Success state or T has no registered serializer.
ttl overrides persistDuration for this specific entry.
// Persist with a short TTL (e.g. for sensitive data)
await client.persistQuery<User>(['users', userId], ttl: const Duration(hours: 1));
evictFromStorage
Future<void> evictFromStorage(Object key)
Delete the persisted entry for key from StorageAdapter. The in-memory cache is not affected.
Use removeQuery to remove from both cache and storage simultaneously.
await client.evictFromStorage(['users', userId]);
clearStorage
Future<void> clearStorage()
Delete every entry Qora wrote to the StorageAdapter. Keys Qora did not write (app settings in a shared Hive box, for example) are left untouched; call clear() on the adapter itself to wipe everything. The in-memory cache is not affected.
Use clear() to wipe both in-memory cache and storage simultaneously.
removeQuery (override)
Calls super.removeQuery(key) and deletes the entry from StorageAdapter.
// On delete: removes from cache and storage
await api.deleteUser(userId);
client.removeQuery(['users', userId]);
clear (override)
Calls super.clear() and calls clearStorage().
// On logout: wipes everything
client.clear();
hydrateQuery (from QoraClient)
void hydrateQuery<T>(Object key, T data, {DateTime? updatedAt})
Pre-populate the cache with a value. Inserts a Success<T> state with updatedAt (defaults to DateTime.now()). No-op if the entry is already in a non-Initial state.
This is used internally by PersistQoraClient but is also available for advanced use cases (e.g. pre-loading from a custom source).
client.hydrateQuery<User>(
['users', userId],
cachedUser,
updatedAt: DateTime.now().subtract(const Duration(minutes: 3)),
);
QoraSerializer<T>
class QoraSerializer<T> {
const QoraSerializer({
required dynamic Function(T value) toJson,
required T Function(dynamic json) fromJson,
});
}
A pair of conversion functions for type T.
| Property | Type | Description |
|---|---|---|
toJson | dynamic Function(T) | Converts T to a JSON-encodable value (Map, List, String, num, bool, or null). |
fromJson | T Function(dynamic) | Restores T from the value previously produced by toJson. |
Both functions use dynamic (not Map<String, dynamic>) so that primitives, lists, and maps are all supported.
// Object
final userSerializer = QoraSerializer<User>(
toJson: (u) => u.toJson(), // returns Map<String, dynamic>
fromJson: User.fromJson, // accepts Map<String, dynamic>
);
// Primitive
final intSerializer = QoraSerializer<int>(
toJson: (n) => n, // returns int directly
fromJson: (json) => json as int,
);
StorageAdapter
abstract interface class StorageAdapter {
Future<void> init();
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
Future<List<String>> keys();
Future<void> clear();
Future<void> dispose();
}
A minimal key/value contract. PersistQoraClient manages all serialisation; adapters only handle opaque JSON strings.
| Method | Description |
|---|---|
init() | Open the backend. Must be called and awaited before any other method. |
read(key) | Return the value for key, or null if absent. |
write(key, value) | Persist value under key, overwriting any existing value. |
delete(key) | Remove the entry. No-op if absent. |
keys() | Return all stored keys. Order is unspecified. |
clear() | Remove all entries. |
dispose() | Release resources (file handles, DB connections, etc.). |
InMemoryStorageAdapter
class InMemoryStorageAdapter implements StorageAdapter
A Map-backed adapter. All operations complete synchronously (wrapped in resolved Futures). Data is lost when the process terminates.
Use for unit tests, integration tests, and CI environments where no real storage is needed.
final adapter = InMemoryStorageAdapter();
await adapter.init();
final client = PersistQoraClient(storage: adapter);
client.registerSerializer<User>(userSerializer, name: 'User');
await client.hydrate(); // no-op on first run
// ... run queries ...
// Verify persistence
final keys = await adapter.keys();
expect(keys, contains('["users",1]'));
Implementing a Custom Adapter
import 'package:qora/qora.dart';
class MyAdapter implements StorageAdapter {
@override
Future<void> init() async {
// Open database / file
}
@override
Future<String?> read(String key) async {
// Return stored value or null
}
@override
Future<void> write(String key, String value) async {
// Persist value under key
}
@override
Future<void> delete(String key) async {
// Remove entry, no-op if absent
}
@override
Future<List<String>> keys() async {
// Return all stored keys
}
@override
Future<void> clear() async {
// Remove all entries
}
@override
Future<void> dispose() async {
// Release resources
}
}
Storage Eviction Reference
| Operation | In-memory cache | StorageAdapter |
|---|---|---|
invalidate(key) | Marks stale | Untouched |
removeQuery(key) | Removed | Deleted |
clearCache() | Cleared | Untouched |
clear() | Cleared | Cleared |
evictFromStorage(key) | Untouched | Deleted |
clearStorage() | Untouched | Cleared |
persistQuery(key) | Untouched | Written |
Related
- Persistence guide: step-by-step walkthrough
- QoraClient API: base client reference
- QoraOptions:
staleTime,gcTime, and other per-query settings
Mutation API
Complete API reference for MutationController, MutationState, MutationOptions, MutationUpdate, and mutation observability on QoraClient.
Infinite Query
Full API reference for InfiniteData, InfiniteQueryState, InfiniteQueryOptions, InfiniteQueryObserver, InfiniteQueryBuilder, InfiniteQueryController, and the QoraClient infinite-query methods.