API Reference

QoraKey

Typed, immutable query key with structural equality. Supports both raw List and QoraKey syntax.

Overview

Every Qora query is identified by a key. Qora accepts two equivalent formats:

main.dart
// Raw list: frictionless, no import needed
key: ['users', userId]

// QoraKey wrapper: explicit, typed
key: QoraKey(['users', userId])
key: QoraKey.withId('users', userId)

Both are normalised to the same internal representation and compared with deep structural equality. They are fully interchangeable: you can mix them across calls to the same logical query.


Constructors

QoraKey(List<dynamic> parts)

Direct constructor from a list of parts.

main.dart
QoraKey(['users', userId])
QoraKey(['posts', {'status': 'published', 'page': 1}])

QoraKey.single(String key)

Shorthand for a single-segment key.

main.dart
QoraKey.single('users')  // equivalent to QoraKey(['users'])

QoraKey.withId(String entity, dynamic id)

Shorthand for an entity + ID key.

main.dart
QoraKey.withId('users', userId)     // ['users', userId]
QoraKey.withId('posts', postId)     // ['posts', postId]

QoraKey.withFilter(String entity, Map<String, dynamic> filter)

Shorthand for an entity + filter map key.

main.dart
QoraKey.withFilter('posts', {'status': 'published', 'page': page})
// ['posts', {'status': 'published', 'page': page}]

QoraKey.from(List<dynamic> parts)

Factory constructor that converts a raw list to a QoraKey.

main.dart
final key = QoraKey.from(['users', userId]);

Properties

parts

final List<dynamic> parts

The raw key segments as an unmodifiable list.

main.dart
final key = QoraKey.withId('users', 42);
print(key.parts); // [users, 42]

Equality

QoraKey uses deep structural equality: two keys are equal if their parts are recursively equal:

main.dart
QoraKey(['users', 1]) == QoraKey(['users', 1])  // true
QoraKey(['users', 1]) == QoraKey(['users', 2])  // false

// Raw list and QoraKey are normalised identically
['users', 1] == QoraKey(['users', 1])           // normalised to same cache entry

Supported types in key parts:

TypeSupport
int, double, boolFull structural equality
StringFull structural equality
nullSupported as a key part "../5.api-reference"
ListRecursively compared
MapOrder-independent recursive comparison
Custom objectsMust override operator == and hashCode
Using custom objects (e.g. UserFilter) in key parts without overriding operator == and hashCode causes a new cache entry on every call. Use primitive-serialisable values or implement equality explicitly.

normalizeKey

List<dynamic> normalizeKey(Object key)

Top-level function that normalises any valid key input to an unmodifiable List<dynamic>:

  • Accepts QoraKey or List<dynamic>
  • Validates that parts are serialisable
  • Returns a deep-copied, unmodifiable list

This is called internally by every QoraClient method. You rarely need to call it directly.

main.dart
final parts = normalizeKey(['users', 1]);   // [users, 1] (unmodifiable)
final parts = normalizeKey(QoraKey.withId('users', 1));  // same result

Throws ArgumentError if the key is not a List or QoraKey.


Key Factories Pattern

Centralise your key definitions to avoid typos and simplify invalidation:

lib/query_keys.dart
abstract class Keys {
  // Users
  static List<dynamic> users()               => ['users'];
  static List<dynamic> user(int id)          => ['users', id];
  static List<dynamic> userPosts(int userId) => ['users', userId, 'posts'];

  // Posts
  static List<dynamic> posts(Map<String, dynamic> filters) => ['posts', filters];
  static List<dynamic> post(int id)          => ['posts', id];
}

// Usage
await client.fetchQuery<User>(
  key: Keys.user(userId),
  fetcher: () => api.getUser(userId),
);

client.invalidateWhere((key) => equalsKey(key, Keys.users()));

Best Practices

Do

main.dart
// Consistent structure: entity first, then ID/filters
['users', userId]
['users', userId, 'posts']
['posts', {'status': 'published'}]

// Use primitives: always comparable
['users', userId.toString()]
['posts', {'page': page, 'limit': 20}]

Don't

main.dart
// Inconsistent ordering
['users', userId]
[userId, 'users']  // Bad: Different cache entry

// Custom objects without equality
['users', UserFilter(status: 'active')]  // Bad: Identity equality: always a miss

// Computed values that vary on each call
['posts', DateTime.now().toIso8601String()]  // Bad: Never hits cache
Copyright © 2026