124 lines
3.8 KiB
Markdown
124 lines
3.8 KiB
Markdown
# PocketBase Sync
|
|
|
|
A robust, offline-first synchronization library for [PocketBase](https://pocketbase.io) and Flutter.
|
|
|
|
This package provides a `PocketBaseSyncManager` that handles the complexities of syncing local data with a PocketBase backend, including conflict resolution, bandwidth optimization, and remote deletion support.
|
|
|
|
## Features
|
|
|
|
* **Offline-First**: Read and write data locally (e.g., using Drift or Hive) and sync when online.
|
|
* **3-Way Merge Strategy**: Automatically resolves conflicts. Server changes win on collision, but non-conflicting local changes are preserved.
|
|
* **Remote Deletion**: Detects records deleted on the server and removes them locally during sync.
|
|
* **Bandwidth Optimized**:
|
|
* **Incremental Pull**: Only fetches records updated since the last sync.
|
|
* **Differential Push**: Uses `HybridDiffer` (Myers diff) to send only changed fields to the server.
|
|
* **Tombstones**: Handles local deletions by marking records as deleted and removing them from the server during sync.
|
|
* **Auto-Cleanup**: Automatically cleans up expired tombstones to keep the local database tidy.
|
|
|
|
## Installation
|
|
|
|
Add `pocketbase_sync` to your `pubspec.yaml`:
|
|
|
|
```yaml
|
|
dependencies:
|
|
pocketbase_sync: ^0.0.1
|
|
pocketbase: ^0.18.0
|
|
```
|
|
|
|
## Usage
|
|
|
|
### 1. Implement SyncRepository
|
|
|
|
You need to implement the `SyncRepository` interface to tell the manager how to store and retrieve data locally. This is typically done with a local database like Drift or Hive.
|
|
|
|
```dart
|
|
class MyRepository implements SyncRepository<MyModel> {
|
|
// ... implement CRUD operations ...
|
|
|
|
@override
|
|
Future<List<SyncRecord<MyModel>>> getAll() async {
|
|
// Return all records (including deleted/messy ones)
|
|
}
|
|
|
|
@override
|
|
Future<DateTime> getLastSyncTime() async {
|
|
// Return stored last sync timestamp
|
|
}
|
|
|
|
@override
|
|
Future<void> setLastSyncTime(DateTime time) async {
|
|
// Persist new sync timestamp
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Initialize the Manager
|
|
|
|
Create an instance of `PocketBaseSyncManager`, providing your PocketBase client, collection name, and repository.
|
|
|
|
```dart
|
|
final pb = PocketBase('https://my-app.pockethost.io');
|
|
final repository = MyRepository();
|
|
|
|
final manager = PocketBaseSyncManager<MyModel>(
|
|
pb: pb,
|
|
collection: 'notes',
|
|
repository: repository,
|
|
// Mappers to convert between your Model and JSON
|
|
fromJson: (json) => MyModel.fromJson(json),
|
|
toJson: (model) => model.toJson(),
|
|
);
|
|
```
|
|
|
|
### 3. Perform Operations
|
|
|
|
Use the manager to perform operations instead of writing directly to your local DB or SDK. The manager ensures changes are tracked for sync.
|
|
|
|
```dart
|
|
// Create
|
|
await manager.create(
|
|
manager.generateId(),
|
|
MyModel(content: 'Hello World')
|
|
);
|
|
|
|
// Update
|
|
await manager.update('RECORD_ID', updatedModel);
|
|
|
|
// Delete
|
|
await manager.delete('RECORD_ID');
|
|
```
|
|
|
|
### 4. Sync
|
|
|
|
Call `sync()` to push local changes and pull remote updates. You can do this on an interval, on app start, or manually.
|
|
|
|
```dart
|
|
try {
|
|
await manager.sync();
|
|
print('Sync completed successfully');
|
|
} catch (e) {
|
|
print('Sync failed: $e');
|
|
}
|
|
```
|
|
|
|
## How it Works
|
|
|
|
### Push (Local to Remote)
|
|
1. Iterates through local records marked as `isDirty`.
|
|
2. If `isDeleted` is true, deletes the record on the server.
|
|
3. Otherwise, calculates a diff between the current data and the `baseData` (state at last sync).
|
|
4. Sends a PATCH request with only the changed fields.
|
|
|
|
### Pull (Remote to Local)
|
|
1. Fetches records from PocketBase updated `updated >= lastSyncTime`.
|
|
2. **Reconciliation**: Fetches a list of **all** IDs from the server to detect deletions.
|
|
3. **Merge**:
|
|
* If a record exists locally and remotely, it performs a 3-way merge.
|
|
* If server data conflicts with local changes, server wins.
|
|
* If no conflict, updates are applied.
|
|
4. Updates the local `baseData` and clears the `isDirty` flag for synced records.
|
|
|
|
## License
|
|
|
|
MIT
|