API Reference

PersistQoraClient

A QoraClient subclass that persists query results to a StorageAdapter and restores them on startup via hydrate().

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,
})
ParameterDescription
storageThe storage backend. Must be initialised via StorageAdapter.init() before calling hydrate().
persistDurationDefault TTL for persisted entries. If null, entries are persisted indefinitely. Pass Duration.zero to disable persistence (entries expire immediately).
configForwarded to QoraClient.
trackerForwarded to QoraClient.
main.dart
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.

ParameterDescription
serializerProvides toJson / fromJson converters for T.
nameThe 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 registerSerializer twice for the same T or the same name replaces the previous entry.
  • Must be called before hydrate().
main.dart
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:

ConditionAction
JSON decode failureDelete from storage, skip
TTL expiredDelete from storage, skip
No serializer registered for the typeLog (if debugMode), skip
fromJson throwsDelete from storage, log, skip
ValidQueue in pending hydration map

Call once at startup, after all serializers are registered and before runApp:

main.dart
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.

main.dart
// 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.

main.dart
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.

main.dart
// On delete: removes from cache and storage
await api.deleteUser(userId);
client.removeQuery(['users', userId]);

clear (override)

Calls super.clear() and calls clearStorage().

main.dart
// 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).

main.dart
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.

PropertyTypeDescription
toJsondynamic Function(T)Converts T to a JSON-encodable value (Map, List, String, num, bool, or null).
fromJsonT 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.

main.dart
// 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.

MethodDescription
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.

test/persistence_test.dart
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

my_adapter.dart
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

OperationIn-memory cacheStorageAdapter
invalidate(key)Marks staleUntouched
removeQuery(key)RemovedDeleted
clearCache()ClearedUntouched
clear()ClearedCleared
evictFromStorage(key)UntouchedDeleted
clearStorage()UntouchedCleared
persistQuery(key)UntouchedWritten

Copyright © 2026