Skip to main content

syncache_drift 0.6.0 copy "syncache_drift: ^0.6.0" to clipboard
syncache_drift: ^0.6.0 copied to clipboard

Drift (SQLite) storage backend for Syncache. Provides durable persistent caching with full support for tag-based invalidation, pattern matching, and a mutation outbox sharing one SQL connection with the cache.

example/syncache_drift_example.dart

// ignore_for_file: unused_local_variable, avoid_print

import 'dart:io';

import 'package:syncache/syncache.dart';
import 'package:syncache_drift/syncache_drift.dart';

/// Example model with JSON serialization.
class User {
  final String id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as String,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
        'email': email,
      };

  @override
  String toString() => 'User($id, $name, $email)';
}

Future<void> main() async {
  // Open ONE drift database — the cache and the outbox share it.
  final tempDir = await Directory.systemTemp.createTemp('drift_example_');
  final db = SyncacheDriftDatabase.openFile('${tempDir.path}/app.sqlite');

  // Wrap the shared database in a store and an outbox.
  final store = DriftStore<User>(
    db: db,
    fromJson: User.fromJson,
    toJson: (user) => user.toJson(),
  );
  final outbox = DriftMutationOutbox(db: db);

  // Wire both into Syncache: durable cache + durable offline writes.
  final cache = Syncache<User>(store: store, outbox: outbox);

  // --- Basic Usage ---

  // Fetch and cache a user.
  final user = await cache.get(
    key: 'user:1',
    fetch: (req) async {
      // Simulate API call.
      return User(id: '1', name: 'Alice', email: 'alice@example.com');
    },
  );
  print('Fetched: $user');

  // Subsequent calls return the cached value (no fetch).
  final cachedUser = await cache.get(
    key: 'user:1',
    fetch: (req) async {
      throw StateError('Should not be called - using cache');
    },
  );
  print('Cached: $cachedUser');

  // --- Using Tags ---

  await cache.get(
    key: 'user:2',
    fetch: (req) async => User(id: '2', name: 'Bob', email: 'bob@example.com'),
    tags: ['team:engineering', 'role:developer'],
  );

  await cache.get(
    key: 'user:3',
    fetch: (req) async =>
        User(id: '3', name: 'Carol', email: 'carol@example.com'),
    tags: ['team:engineering', 'role:manager'],
  );

  // Invalidate all entries with a specific tag (a real SQL index backs this).
  await cache.invalidateTag('team:engineering');
  print('Invalidated all engineering team members');

  // --- Pattern-Based Operations ---

  await cache.get(
    key: 'user:active:4',
    fetch: (req) async =>
        User(id: '4', name: 'Dave', email: 'dave@example.com'),
  );

  // Invalidate by pattern (glob-style wildcards).
  await cache.invalidate('user:active:*');
  print('Invalidated all active users');

  // --- Optimistic Mutations (offline writes) ---

  // While offline, a mutation is applied optimistically and mirrored into
  // the mutation_outbox table of the same database.
  final offline = Syncache<User>(
    store: store,
    outbox: outbox,
    network: const _Offline(),
  );
  await offline.mutate(
    key: 'user:1',
    mutation: Mutation<User>(
      apply: (current) =>
          User(id: current.id, name: 'Alice', email: 'alice@new.example.com'),
      send: (next) async => next, // would hit the API
    ),
    payload: {'op': 'update-email', 'id': '1'},
  );
  offline.dispose();
  print('Pending mutations while offline: ${(await outbox.loadAll()).length}');

  // --- Recovery (after a restart) ---

  // A fresh Syncache over the same database replays unsynced writes.
  final online = Syncache<User>(store: store, outbox: outbox);
  final restored = await online.recoverPendingMutations(
    (record) async => Mutation<User>(
      apply: (current) => current,
      send: (next) async => next,
    ),
  );
  print('Recovered $restored pending mutation(s)');

  // The replay drains asynchronously; pump until the record is settled.
  for (var i = 0; i < 200 && (await outbox.loadAll()).isNotEmpty; i++) {
    await Future<void>.delayed(const Duration(milliseconds: 5));
  }
  print('Outbox after recovery: ${(await outbox.loadAll()).length} record(s)');
  online.dispose();

  // --- Direct Store Operations ---

  final keys = await store.getKeysByPattern('user:*');
  print('Keys matching "user:*": $keys');

  final tags = await store.getTags('user:1');
  print('Tags for user:1: $tags');

  // --- The shared-connection bonus ---

  // Cache writes and outbox writes can run in ONE SQL transaction — either
  // both commit, or neither does:
  await db.transaction(() async {
    await store.writeWithTags(
      'user:1',
      Stored(
        value: user,
        meta: Metadata(version: 1, storedAt: DateTime.now()),
      ),
      ['team:engineering'],
    );
    await outbox.save(
      PendingMutationRecord(
        id: 'op-1',
        key: 'user:1',
        seq: 1,
        payload: {'op': 'update-email', 'id': '1'},
        queuedAt: DateTime.now(),
      ),
    );
  });
  print('Cache + outbox updated atomically');

  // --- Cleanup ---

  cache.dispose();
  await store.close(); // closes the shared database

  await tempDir.delete(recursive: true);

  print('Done!');
}

/// A [Network] that is always offline, for the offline-mutation demo.
class _Offline implements Network {
  const _Offline();

  @override
  bool get isOnline => false;
}
0
likes
130
points
94
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Drift (SQLite) storage backend for Syncache. Provides durable persistent caching with full support for tag-based invalidation, pattern matching, and a mutation outbox sharing one SQL connection with the cache.

Repository (GitHub)
View/report issues
Contributing

Topics

#cache #offline-first #drift #sqlite #persistence

License

MIT (license)

Dependencies

drift, syncache

More

Packages that depend on syncache_drift