### Run Synquill Example Application (Bash) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Provides step-by-step instructions for setting up and running the Synquill example project. This includes navigating to the project directory, installing dependencies, generating necessary code, and launching the application. ```bash cd synquill/example flutter pub get dart run build_runner build flutter run ``` -------------------------------- ### Synquill Example Project Directory Structure Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Outlines the directory structure of the Synquill example project, detailing the purpose of key folders and files. It provides a hierarchical view of the `lib/` directory, explaining the role of `main.dart`, generated files, adapters, BLoCs, models, and screens. ```APIDOC lib/ ├── main.dart # App entry point with WorkManager setup ├── synquill.generated.dart # Generated database and repositories ├── adapters/ │ └── base_json_api_adapter.dart # Shared API configuration ├── blocs/ # BLoC state management │ ├── home/ # Home screen state │ ├── todos/ # Todos management state │ └── posts/ # Posts management state ├── models/ # Data models │ ├── user.dart # User model with relationships │ ├── todo.dart # Todo model with custom adapter │ ├── post.dart # Post model with custom adapter │ └── *.g.dart # Generated JSON serialization ├── screens/ # UI screens │ ├── home_screen.dart # Main dashboard │ ├── todos_screen.dart # Todo management │ └── posts_screen.dart # Post management └── generated/ # Additional generated files ``` -------------------------------- ### Synquill Database Version Management Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md This Dart example illustrates the setup of the `SynquillDatabase` with versioning. It uses the `@SynqillDatabaseVersion` annotation and configures a `LazyDatabase` for a native Drift database. Custom callbacks for `onCustomMigration` and `onDatabaseCreated` are provided to handle schema evolution and initial data population, respectively, with support for cross-isolate sharing. ```dart @SynqillDatabaseVersion(1) final database = SynquillDatabase( LazyDatabase(() => driftDatabase( name: 'synced_storage.db', native: DriftNativeOptions( shareAcrossIsolates: true, databaseDirectory: getApplicationSupportDirectory, ), )), onCustomMigration: _performMigration, onDatabaseCreated: _setupInitialData, ); ``` -------------------------------- ### Synquill Comprehensive Documentation and API Reference Overview Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/README.md Provides a structured overview of the Synquill project's documentation, including guides for getting started, configuring storage, advanced features like queue management and dependency resolution, and a complete API reference. These resources cover core concepts, customization, and detailed API specifications. ```APIDOC Documentation: - Getting Started Guide: Core concepts, querying, operations, and relationships - JSON API Adapters: Customizing HTTP methods, headers, and response parsing - Configuration: Storage configuration and background sync setup - Advanced Features: Queue management, dependency resolution, and more - API Reference: Complete API documentation - Advanced Topics: - Queue Management: Sync queue system and task processing - Dependency Resolution: Hierarchical task dependencies ``` -------------------------------- ### Query All Todos with Filtering and Sorting Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Shows how to retrieve all todos from the `SynquillStorage` instance. It demonstrates using `localThenRemote` load policy, sorting by creation date, and filtering for incomplete todos. ```Dart // Find all todos final todos = await SynquillStorage.instance.todos.findAll( loadPolicy: DataLoadPolicy.localThenRemote, queryParams: QueryParams( sorts: [SortParam('createdAt', SortDirection.desc)], filters: [FilterParam('isCompleted', false)], ), ); ``` -------------------------------- ### Integrate Synquill Streams for Reactive UI (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Provides an example of integrating Synquill's `watchAll` method with a BLoC or StatefulWidget to enable automatic UI updates when data changes. It demonstrates managing a `StreamSubscription` for reactive data flow. ```dart // In BLoC or StatefulWidget StreamSubscription>? _todosSubscription; void _startWatching() { _todosSubscription = SynquillStorage.instance.todos .watchAll( queryParams: QueryParams( sorts: [SortParam('createdAt', SortDirection.desc)], ), ) .listen((todos) { // Automatic UI updates add(TodosUpdated(todos)); }); } ``` -------------------------------- ### Enable Synquill Sync in Dart Background Isolates Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md This Dart example illustrates how to use SynquillStorage methods within a background isolate. It shows how to annotate an entry-point function with `@pragma('vm:entry-point')` to allow sync mode control and background task processing in a separate isolate. ```dart @pragma('vm:entry-point') void backgroundTaskHandler() { // Switch modes even in background isolates SynquillStorage.enableBackgroundMode(); SynquillStorage.enableForegroundMode(); // Process background sync tasks await SynquillStorage.processBackgroundSync(); } ``` -------------------------------- ### Control Data Loading with Synquill Policies Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Illustrates various `DataLoadPolicy` options for fetching data. Examples include loading from local storage only, fetching locally immediately and refreshing from remote in the background, and fetching from remote first with a fallback to local on failure. ```dart // Get from local storage only final users = await repository.findAll( loadPolicy: DataLoadPolicy.localOnly, ); // Get from local immediately, refresh from remote in background final users = await repository.findAll( loadPolicy: DataLoadPolicy.localThenRemote, ); // Fetch from remote first, fallback to local on failure final users = await repository.findAll( loadPolicy: DataLoadPolicy.remoteFirst, ); ``` -------------------------------- ### Define Post Data Model with SynquillRepository Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Defines the Post data model using `@SynquillRepository`. It includes properties for title, body, and a many-to-one relationship with the User model, associating posts with users. ```Dart @SynquillRepository( adapters: [JsonApiAdapter, PostApiAdapter], relations: [ ManyToOne(target: User, foreignKeyColumn: 'userId'), ], ) class Post extends SynquillDataModel { final String title; final String body; final String userId; // ... implementation } ``` -------------------------------- ### Synquill Repository-Level Data Querying Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Shows how to query data using Synquill's repository instances, such as `users` and `todos`. It covers `findAll` with comprehensive `QueryParams` for filtering, sorting, and pagination, as well as `findOne` and `findOneOrFail` for retrieving single items. ```dart // Get repository instance final userRepository = SynquillStorage.instance.users; final todoRepository = SynquillStorage.instance.todos; // Find all with filtering, sorting, and pagination final completedTodos = await todoRepository.findAll( queryParams: QueryParams( filters: [ TodoFields.isCompleted.equals(true), TodoFields.createdAt.greaterThan(DateTime.now().subtract(Duration(days: 7))), ], sorts: [ SortCondition( field: TodoFields.createdAt, direction: SortDirection.descending ), ], limit: 20, offset: 0, ), loadPolicy: DataLoadPolicy.localThenRemote, ); // Find single item final user = await userRepository.findOne('user-id'); // Find or throw exception final user = await userRepository.findOneOrFail('user-id'); ``` -------------------------------- ### Synquill Code Generation Workflow Overview Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Describes the complete code generation process in Synquill, from model definitions with annotations to the various generated files and their purposes. It outlines how `json_annotation`, library-generated files, database tables, and API adapters are created. ```APIDOC 1. Model Definitions: Models with @SynquillRepository() annotations 2. Generated Files: Build runner generates: - *.g.dart - JSON serialization with json_annotation - generated/*.g.dart - Library-generated files - synquill.generated.dart - Database tables and repositories 3. Database Setup: Drift-powered SQLite with custom migrations 4. API Adapters: Custom REST API configurations per model ``` -------------------------------- ### Perform Basic Model Operations in Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates how to create, save, and delete model instances using their built-in methods. This highlights the simplicity and directness of persistence operations on individual model objects. ```dart // Create and save final user = User(name: 'John Doe', email: 'john@example.com'); final savedUser = await user.save(); // Delete await savedUser.delete(); // Force refresh from remote is not yet implemented; for now, use findOne with DataLoadPolicy.remoteFirst to fetch the latest data from the server. // final refreshedUser = await savedUser.refresh(); ``` -------------------------------- ### Define User Data Model with SynquillRepository Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Defines the User data model using `@SynquillRepository` for data persistence and synchronization. It specifies adapters for API interaction and one-to-many relationships with Todo and Post models. ```Dart @SynquillRepository( adapters: [JsonApiAdapter, UserApiAdapter], relations: [ OneToMany(target: Todo, mappedBy: 'userId'), OneToMany(target: Post, mappedBy: 'userId'), ], ) class User extends SynquillDataModel { final String id; final String name; // ... implementation } ``` -------------------------------- ### Access Generated Methods for Many-to-One Relationships (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Shows the automatically generated methods for loading and watching related data in a many-to-one relationship, and an example of deleting a parent record with a specified save policy. ```dart // Load related user final user = await todo.loadUser(); // Watch related user todo.watchUser().listen((user) => updateUI(user)); // Delete with cascade (automatically deletes related todos) await userRepository.delete('user-id', savePolicy: DataSavePolicy.localFirst); ``` -------------------------------- ### Apply LocalFirst Data Save Policy Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Demonstrates saving a new todo using the `localFirst` policy. This ensures the data is first saved to the local database before being synchronized with the remote API, ideal for new content creation. ```Dart await todo.save(savePolicy: DataSavePolicy.localFirst); ``` -------------------------------- ### Watch Synquill Data with Real-time Streams (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Demonstrates how to subscribe to real-time updates for collections (todos) and single records using Synquill's `watchAll` and `watchOne` methods. Shows how to integrate with UI updates by listening to the stream. ```dart // Watch all todos with real-time updates SynquillStorage.instance.todos.watchAll( queryParams: QueryParams( sorts: [SortParam('createdAt', SortDirection.desc)], ), ).listen((todos) { // UI automatically updates when data changes setState(() => _todos = todos); }); // Watch single todo SynquillStorage.instance.todos.watchOne('todo-id').listen((todo) { // React to specific todo changes }); ``` -------------------------------- ### Query Todos by User ID Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Demonstrates how to filter todos based on a specific user ID. This query retrieves all todos associated with a given user, showcasing relationship-based filtering. ```Dart // Find todos by user ID final userTodos = await SynquillStorage.instance.todos.findAll( queryParams: QueryParams( filters: [FilterParam('userId', '1')], ), ); ``` -------------------------------- ### SynquillStorage Initialization Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md This Dart code snippet demonstrates how to initialize the `SynquillStorage` instance. It configures connectivity checks using `InternetConnection`, sets up the underlying database, and defines global data handling policies like `defaultSavePolicy` and `defaultLoadPolicy`. It also specifies concurrency for foreground operations. ```dart await SynquillStorage.init( connectivityChecker: () async => await InternetConnection().hasInternetAccess, connectivityStream: InternetConnection().onStatusChange .map((status) => status == InternetStatus.connected), enableInternetMonitoring: true, database: database, config: const SynquillStorageConfig( defaultSavePolicy: DataSavePolicy.localFirst, defaultLoadPolicy: DataLoadPolicy.localThenRemote, foregroundQueueConcurrency: 1, ), initializeFn: initializeSynquillStorage, ); ``` -------------------------------- ### Query Single Todo by ID Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Illustrates how to fetch a single todo by its unique identifier. It uses the `localOnly` load policy, suitable for retrieving data directly from the local database without remote synchronization. ```Dart // Find single todo by ID final todo = await SynquillStorage.instance.todos.findOne( 'todo-id', loadPolicy: DataLoadPolicy.localOnly, ); ``` -------------------------------- ### Define Todo Data Model with SynquillRepository Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Defines the Todo data model using `@SynquillRepository`. It includes properties like title, completion status, and a many-to-one relationship with the User model, linking todos to specific users. ```Dart @SynquillRepository( adapters: [JsonApiAdapter, TodoApiAdapter], relations: [ ManyToOne(target: User, foreignKeyColumn: 'userId'), ], ) class Todo extends ContactBase { final String title; final bool isCompleted; final String userId; // ... implementation } ``` -------------------------------- ### Construct Advanced Synquill Query Parameters (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Shows how to build complex `QueryParams` objects for filtering, sorting, limiting, and offsetting data. Demonstrates combining multiple `FilterParam` and `SortParam` instances to create sophisticated queries. ```dart // Complex filtering and sorting final queryParams = QueryParams( filters: [ FilterParam('isCompleted', false), FilterParam('createdAt', DateTime.now().subtract(Duration(days: 7)), operator: FilterOperator.greaterThan), ], sorts: [ SortParam('priority', SortDirection.desc), SortParam('createdAt', SortDirection.asc), ], limit: 20, offset: 0, ); final recentTodos = await SynquillStorage.instance.todos.findAll( queryParams: queryParams, loadPolicy: DataLoadPolicy.localThenRemote, ); ``` -------------------------------- ### Handle Background Isolate for Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Illustrates the entry-point function for a background isolate, typically used with WorkManager. Within this function, SynquillStorage can be initialized to process pending sync operations and manage cross-isolate database sharing. ```dart @pragma('vm:entry-point') void callbackDispatcher() { // Initialize SynquillStorage for background isolate // Process pending sync operations // Handle cross-isolate database sharing } ``` -------------------------------- ### Initialize SynquillStorage with Internet Connectivity Monitoring Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md Demonstrates how to initialize `SynquillStorage` by integrating with an internet connectivity checker. It uses `internet_connection_checker_plus` to provide a `connectivityChecker` function and a `connectivityStream`, enabling Synquill to monitor network status and adjust its operations accordingly. ```dart import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; // ... await SynquillStorage.init( connectivityChecker: () async => await InternetConnection().hasInternetAccess, connectivityStream: InternetConnection() .onStatusChange .map((status) => status == InternetStatus.connected), enableInternetMonitoring: true, // ... ); ``` -------------------------------- ### Access Generated Methods for One-to-Many Relationships (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Shows the automatically generated methods for loading and watching related data in a one-to-many relationship, simplifying data access and real-time updates for associated collections. ```dart // Load related todos final todos = await user.loadTodos(); // Watch related todos user.watchTodos().listen((todos) => updateUI(todos)); ``` -------------------------------- ### Register Periodic Background Sync with WorkManager (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Demonstrates how to use the `Workmanager` package to schedule a periodic background task for Synquill synchronization. It specifies the task's frequency and constraints like network connectivity and battery level. ```dart // Periodic background sync every 15 minutes Workmanager().registerPeriodicTask( 'synquill_periodic_sync_task', 'sync_task', frequency: const Duration(minutes: 15), constraints: Constraints( networkType: NetworkType.connected, requiresBatteryNotLow: true, ), ); ``` -------------------------------- ### Load and Query Related Data with Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates how to load related data for a model instance using generated extension methods. It shows loading a user's todos with filtering and sorting, and subsequently loading a todo's associated user. ```dart final user = await userRepository.findOne('user-id'); // Load related todos with filtering final userTodos = await user.loadTodos( loadPolicy: DataLoadPolicy.localThenRemote, queryParams: QueryParams( filters: [TodoFields.isCompleted.equals(false)], sorts: [SortCondition( field: TodoFields.createdAt, direction: SortDirection.descending )], ), ); // Load related user for a todo final todo = await todoRepository.findOne('todo-id'); final todoOwner = await todo.loadUser(); ``` -------------------------------- ### Synquill ID Conflict Resolution Scenario Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates a scenario where a server-assigned ID conflicts with an existing local ID, leading to automatic resolution attempts. ```dart // Scenario: Server wants to assign ID "server_123" but it already exists locally final post = ServerManagedPost( id: generateCuid(), // temporary: "cuid_abc" title: 'My Post', content: 'Content', ); await post.save(); // Server responds with ID "server_123" but it already exists ``` -------------------------------- ### Apply RemoteFirst Data Delete Policy Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Illustrates deleting a todo using the `remoteFirst` policy. This policy attempts to delete the data from the remote API first, then updates the local database, suitable for deletion operations. ```Dart await SynquillStorage.instance.todos.delete( todoId, savePolicy: DataSavePolicy.remoteFirst, ); ``` -------------------------------- ### Listen to Fine-Grained Repository Events in Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Shows how to subscribe to repository change events (created, updated, deleted, error) to react to specific data modifications at a fine-grained level, enabling custom logic based on data lifecycle events. ```dart todoRepository.changes.listen((change) { switch (change.type) { case RepositoryChangeType.created: print('New todo created: ${change.model?.title}'); break; case RepositoryChangeType.updated: print('Todo updated: ${change.model?.title}'); break; case RepositoryChangeType.deleted: print('Todo deleted'); break; case RepositoryChangeType.error: print('Error: ${change.error}'); break; } }); ``` -------------------------------- ### Query Related Data via Model Instances (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/example/README.md Illustrates how to access related data (e.g., user's todos, user's posts) directly from a model instance using `loadTodos` and `watchPosts`. Shows usage of `DataLoadPolicy` and `QueryParams` for filtering and real-time updates on relationships. ```dart final user = await SynquillStorage.instance.users.findOne('1'); // Load user's todos final userTodos = await user.loadTodos( loadPolicy: DataLoadPolicy.localThenRemote, queryParams: QueryParams( filters: [FilterParam('isCompleted', false)], ), ); // Watch user's posts with real-time updates user.watchPosts( queryParams: QueryParams( sorts: [SortParam('createdAt', SortDirection.desc)], ), ).listen((posts) { // UI updates when user's posts change }); ``` -------------------------------- ### Define Many-to-One Relationships in Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Illustrates how to define a many-to-one relationship using the `@SynquillRepository` annotation and `ManyToOne` decorator, specifying the foreign key column and cascade delete behavior. ```dart @SynquillRepository( relations: [ ManyToOne( target: User, foreignKeyColumn: 'userId', cascadeDelete: false, ), ], ) class Todo extends SynquillDataModel { final String userId; // ... model definition } ``` -------------------------------- ### Configure Synquill Storage Options Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md Defines the `SynquillStorageConfig` object, specifying default data save and load policies, concurrency limits for foreground and background queues, and retry parameters for failed operations. This configuration object is later used during `SynquillStorage` initialization. ```dart const config = SynquillStorageConfig( // Default policies defaultSavePolicy: DataSavePolicy.localFirst, defaultLoadPolicy: DataLoadPolicy.localThenRemote, // Concurrency limits foregroundQueueConcurrency: 3, backgroundQueueConcurrency: 1, // Retry configuration maxRetryAttempts: 5, initialRetryDelay: Duration(seconds: 1), maxRetryDelay: Duration(minutes: 5), // Connectivity related options are provided when calling // `SynquillStorage.init` (see below) ); ``` -------------------------------- ### Subscribe to Real-time Data Updates in Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Illustrates how to use reactive streams to watch for real-time updates on collections of models or single model instances. It also shows how to watch for changes in related data and emphasizes the importance of cancelling subscriptions to prevent memory leaks. ```dart // Watch all todos with real-time updates StreamSubscription? subscription = todoRepository.watchAll( queryParams: QueryParams( filters: [TodoFields.isCompleted.equals(false)], ), ).listen((todos) { // UI automatically updates when data changes setState(() => _todos = todos); }); // Remember to cancel the subscription when it is no longer needed to avoid memory leaks. // Watch single item todoRepository.watchOne('todo-id').listen((todo) { if (todo != null) { // React to specific todo changes updateUI(todo); } }); // Watch relationship changes user.watchTodos().listen((todos) { // Automatically updated when user's todos change print('User now has ${todos.length} todos'); }); // Remember to cancel the subscription when it is no longer needed to prevent memory leaks. subscription?.cancel(); ``` -------------------------------- ### Registering Model Dependencies with DependencyResolver Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/dependency-resolver.md Demonstrates how Synquill automatically registers `@ManyToOne` relationships as dependencies during the build process, ensuring parent models are synced before their children. This example shows `Todo` and `Project` depending on `User`, and `Task` depending on `Project`. ```dart // Generated during build - registers Todo's dependency on User DependencyResolver.registerDependency('Todo', 'User'); DependencyResolver.registerDependency('Project', 'User'); DependencyResolver.registerDependency('Task', 'Project'); ``` -------------------------------- ### Register Periodic Background Sync Task with Workmanager Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md Sets up a periodic background task using the `Workmanager` plugin to trigger Synquill synchronization. This code snippet initializes Workmanager and registers a task that runs every 15 minutes, with constraints ensuring it only executes when connected to a network and the device battery is not low. ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); Workmanager().initialize( callbackDispatcher, // The top level function, aka callbackDispatcher isInDebugMode: true // If enabled it will post a notification // whenever the task is running. Handy for debugging tasks ); Workmanager().registerPeriodicTask( 'synquill_periodic_sync_task', 'sync_task', frequency: const Duration(minutes: 15), // Adjust as needed initialDelay: const Duration(seconds: 10), // Optional initial delay constraints: Constraints( networkType: NetworkType.connected, // Only run when connected to network requiresBatteryNotLow: true, // Avoid running on low battery requiresCharging: false, // Can run on battery power ), ); // ... } ``` -------------------------------- ### Implement Workmanager Callback for Synquill Background Sync Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md Defines the `callbackDispatcher` function, marked with `@pragma('vm:entry-point')` for isolate accessibility, which is executed by Workmanager in a background isolate. This function initializes `SynquillStorage` for the background context, sets up a `drift` database with isolate sharing, processes background sync tasks, and ensures proper resource cleanup by closing the storage instance. ```dart @pragma('vm:entry-point') void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { try { // Create database instance for background isolate final database = SynquillDatabase( LazyDatabase( () => driftDatabase( name: 'synquill.db', // same database file native: DriftNativeOptions( shareAcrossIsolates: true, // same sync option databaseDirectory: getApplicationSupportDirectory, ), ), ), ); // Initialize SynquillStorage in background isolate await SynquillStorage.initForBackgroundIsolate( database: database, config: SynquillStorageConfig( defaultSavePolicy: DataSavePolicy.localFirst, defaultLoadPolicy: DataLoadPolicy.localThenRemote, backgroundQueueConcurrency: 1, ), initializeFn: initializeSynquillStorage, ); // Process background sync tasks await SynquillStorage.processBackgroundSync(); // close the SynquillStorage instance to avoid resource leaks await SynquillStorage.close(); return true; } catch (e, stackTrace) { //print("Background sync failed: $e"); //print("Stack trace: $stackTrace"); return false; } }); } ``` -------------------------------- ### Default Data Overwriting Rules in Synquill Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Shows the default behavior where remote data overwrites local data when using the `remoteFirst` load policy during synchronization. This ensures that the local state reflects the latest remote data. ```dart // Remote data overwrites local data by default final users = await repository.findAll( loadPolicy: DataLoadPolicy.remoteFirst, ); ``` -------------------------------- ### Control Data Saving with Synquill Policies Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates how to use `DataSavePolicy` to control whether data is saved locally first and then synced, or remotely first with local update on success. Highlights the implications of `remoteFirst` on error handling, where local saving is skipped if the remote operation fails. ```dart // Save locally first, then sync to remote in background await user.save(savePolicy: DataSavePolicy.localFirst); // Save to remote first, then save/update local on success await user.save(savePolicy: DataSavePolicy.remoteFirst); ``` -------------------------------- ### Detecting Circular Dependencies with DependencyResolver Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/dependency-resolver.md Demonstrates how Synquill's `DependencyResolver` identifies and prevents circular dependencies, which would otherwise lead to infinite loops or unresolvable sync orders. It shows an example of a circular dependency between models A, B, and C, and how to check for its presence. ```dart // This would be detected as a circular dependency: DependencyResolver.registerDependency('A', 'B'); DependencyResolver.registerDependency('B', 'C'); DependencyResolver.registerDependency('C', 'A'); // Check for circular dependencies if (DependencyResolver.hasCircularDependencies()) { // Handle circular dependency error } ``` -------------------------------- ### Monitor Synquill Queue Real-time Statistics Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/queues.md Provides an example of a `QueueMonitor` class that periodically fetches and prints real-time statistics for Synquill queues. It uses `SynquillStorage.queueManager.getQueueStats()` to display the number of active and pending tasks per queue type, aiding in system oversight. ```dart // Example queue monitoring class class QueueMonitor { void startMonitoring() { Timer.periodic(Duration(seconds: 5), (timer) { final stats = SynquillStorage.queueManager.getQueueStats(); for (final entry in stats.entries) { final queueType = entry.key; final queueStats = entry.value; print('${queueType.name}: ${queueStats.activeAndPendingTasks} active, ' '${queueStats.pendingTasks} pending'); } }); } } ``` -------------------------------- ### Understand Dependency-Based Sync Ordering in Synquill Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Explains how Synquill automatically manages sync order based on model relationships. This ensures that parent models are always synced before their child models, preventing foreign key constraint violations during synchronization operations. ```dart // Parent models (Users) are always synced before child models (Todos) // This prevents foreign key constraint violations during sync operations // See the "Dependency-Based Sync Ordering" section for detailed information ``` -------------------------------- ### Implement Mixed ID Strategies in Synquill Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Synquill allows for a flexible approach where different data models can use different ID generation strategies within the same application. This enables scenarios where some models (e.g., `User`) maintain stable client-generated IDs for offline usage, while others (e.g., `BlogPost`) integrate with backend systems using server-generated IDs. ```Dart // User uses client-generated IDs (stable across offline usage) @SynquillRepository() // defaults to IdGenerationStrategy.client class User extends SynquillDataModel { // ID never changes, always client-generated } // Post uses server-generated IDs (integrates with existing blog system) @SynquillRepository(idGeneration: IdGenerationStrategy.server) class BlogPost extends SynquillDataModel { final String userId; // References stable client-generated User ID // Post ID will be replaced with server ID after sync } ``` -------------------------------- ### Define a Custom API Adapter for User Data (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-reference.md Shows how to create a custom API adapter by mixing `UserApiAdapter` with `BasicApiAdapter`. This example demonstrates overriding properties like `baseUrl`, `type`, and `baseHeaders` to configure specific API request behaviors for user data, including dynamic authorization tokens. ```dart mixin UserApiAdapter on BasicApiAdapter { @override Uri get baseUrl => Uri.parse('https://api.example.com/v1/'); @override String get type => 'user'; @override Future> get baseHeaders async => { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${await getAuthToken()}', }; } ``` -------------------------------- ### Manually Control Synquill Sync Modes in Dart Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/configuration.md This Dart snippet shows how to manually switch between foreground and background sync modes using `SynquillStorage.enableForegroundMode()` and `SynquillStorage.enableBackgroundMode()`. It also demonstrates how to check if the background sync manager is ready and trigger background sync tasks. ```dart // Switch to foreground mode for immediate responsiveness // Optional forceSync parameter triggers immediate sync processing SynquillStorage.enableForegroundMode(forceSync: true); // Switch to background mode for battery optimization SynquillStorage.enableBackgroundMode(); // Check if background sync manager is ready final isReady = SynquillStorage.backgroundSyncManager.isReadyForBackgroundSync; if (isReady) { // Manually trigger background sync processing await SynquillStorage.instance.processBackgroundSyncTasks(); } ``` -------------------------------- ### Define Synquill Data Models (User and Todo) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/README.md Illustrates how to define data models (`User` and `Todo`) using `SynquillDataModel`, `JsonSerializable`, and `SynquillRepository` annotations. This setup demonstrates how to establish relationships (`OneToMany`, `ManyToOne`) and define constructors for both database and JSON serialization, forming the foundation for Synquill's model-driven approach and automatic synchronization. ```Dart import 'package:synquill/synquill.dart'; import 'package:json_annotation/json_annotation.dart'; part 'user.g.dart'; @JsonSerializable() @SynquillRepository( adapters: [JsonApiAdapter, UserApiAdapter], relations: [ OneToMany(target: Todo, mappedBy: 'userId'), ], ) class User extends SynquillDataModel { @override final String id; final String name; final String email; User({ String? id, required this.name, required this.email, }) : id = id ?? generateCuid(); User.fromDb({ required this.id, required this.name, required this.email, /// The following fields are optional. You can omit them if you do not require access. DateTime? createdAt, DateTime? updatedAt, SyncStatus? syncStatus, }) { /// The following fields are optional. You can omit them if you do not require access. this.createdAt = createdAt; this.updatedAt = updatedAt; this.syncStatus = syncStatus ?? SyncStatus.synced; } factory User.fromJson(Map json) => _$UserFromJson(this); @override Map toJson() => _$UserToJson(this); } @JsonSerializable() @SynquillRepository( adapters: [JsonApiAdapter, TodoApiAdapter], relations: [ ManyToOne(target: User, foreignKeyColumn: 'userId'), ], ) class Todo extends SynquillDataModel { @override final String id; final String title; final bool isCompleted; final String userId; Todo({ String? id, required this.title, this.isCompleted = false, required this.userId, }) : id = id ?? generateCuid(); Todo.fromDb({ required this.id, required this.title, required this.isCompleted, required this.userId, /// Example constructor omitting createdAt and updatedAt fields }); factory Todo.fromJson(Map json) => _$TodoFromJson(this); @override Map toJson() => _$TodoToJson(this); } ``` -------------------------------- ### Synquill ID Conflict Exception Handling Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates how to catch and handle `IdConflictException` when `post.save()` fails due to an ID conflict. The exception provides details like temporary ID, proposed server ID, and model type, allowing for manual conflict resolution or retry logic. ```dart try { await post.save(); } on IdConflictException catch (e) { print('ID conflict: ${e.message}'); print('Temporary ID: ${e.temporaryId}'); print('Server ID: ${e.proposedServerId}'); print('Model type: ${e.modelType}'); // Handle conflict manually or retry later } ``` -------------------------------- ### Handle HTTP 410 Gone Status with Synquill Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Demonstrates how Synquill automatically removes local items from the database when the API returns an HTTP 410 Gone status for a specific item. This behavior ensures that items deleted on the server are properly cleaned up locally, maintaining consistency between remote and local data stores. ```dart // If API returns 410 Gone for a todo: try { final todo = await todoRepository.findOne('deleted-todo-id'); // todo will be null - automatically removed from local DB } catch (e) { // Item was deleted both remotely and locally } ``` -------------------------------- ### Handle Synquill Exceptions in Dart Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/advanced-features.md This Dart example illustrates robust error handling for Synquill operations. It uses a `try-on` block to catch specific exceptions like `NotFoundException` for missing records, `ApiException` for general API errors, and `SynquillStorageException` for database-related issues, allowing for tailored error messages and application flow control. ```dart try { final user = await userRepository.findOneOrFail('invalid-id'); } on NotFoundException catch (e) { print('User not found: ${e.message}'); } on ApiException catch (e) { print('API error: ${e.message}, Status: ${e.statusCode}'); } on SynquillStorageException catch (e) { print('Storage error: ${e.message}'); } ``` -------------------------------- ### Create Models with Server-Generated IDs Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md When creating a new model that uses server-generated IDs, you must still provide a temporary client-side ID, typically generated using `generateCuid()`. This temporary ID will be replaced by the server's permanent ID during the background synchronization process after the model is saved. ```Dart // Create with temporary client ID final post = ServerManagedPost( id: generateCuid(), // Temporary ID title: 'My Post', content: 'Post content', ); // Save the model - server will assign permanent ID final savedPost = await post.save(); // savedPost.id will contain the server-assigned ID after sync ``` -------------------------------- ### Implement Custom Headers and Authentication for Synquill API Adapter (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-adapters.md This Dart example illustrates how to add custom HTTP headers, including authentication tokens, to API requests in Synquill. By overriding `baseHeaders`, it fetches an authentication token asynchronously and includes it along with `Content-Type` and `X-API-Version` headers. ```dart mixin AuthenticatedAdapter on BasicApiAdapter { @override FutureOr> get baseHeaders async { final token = await getAuthToken(); return { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token', 'X-API-Version': '2.0', }; } } ``` -------------------------------- ### Defining a Task Model with Complex ManyToOne Relations Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/dependency-resolver.md Illustrates how to define a `Task` model with multiple `@ManyToOne` relationships (to `User` and `Category`) using Synquill's annotations. This setup results in a diamond dependency pattern, where `Task` depends on two independent root models, `User` and `Category`. ```dart @SynquillRepository( relations: [ ManyToOne(target: User, foreignKeyColumn: 'userId'), ManyToOne(target: Category, foreignKeyColumn: 'categoryId'), ], ) class Task extends SynquillDataModel { final String userId; final String categoryId; // ... model definition } // Results in dependency ordering: // Level 0: User, Category (independent) // Level 1: Task (depends on both User and Category) ``` -------------------------------- ### Synquill ID Conflict Strategy: Conflict Marking and Manual Resolution Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Illustrates the fallback strategy when automatic ID conflict resolution fails. The local record retains its temporary ID, the sync queue task is marked as 'conflict', and detailed information is logged, requiring manual intervention or custom resolution logic. ```dart // If conflict cannot be resolved automatically: // - Keep temporary ID for the local record // - Mark sync queue task as "conflict" status // - Log detailed conflict information // - Result: Manual resolution required ``` ```dart // Check for conflicts in sync queue: final syncQueueDao = SyncQueueDao(database); final allTasks = await syncQueueDao.getAllItems(); final conflicts = allTasks.where((task) => task['id_negotiation_status'] == 'conflict' ).toList(); for (final conflict in conflicts) { print('Conflict: ${conflict['model_id']} vs server ID'); print('Error: ${conflict['last_error']}'); // Handle manually or implement custom resolution logic } ``` -------------------------------- ### Synquill QueryParams HTTP Translation Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Documents how Synquill's `QueryParams` are automatically translated into HTTP querystring parameters for API calls when using `DataLoadPolicy.localThenRemote` or `DataLoadPolicy.remoteFirst`. It details the default format for filters (e.g., `filter[field][operator]=value`), sorts (e.g., `sort=field:direction`), and pagination (e.g., `limit=X&offset=Y`), noting that this behavior can be customized. ```APIDOC QueryParams: - Filters: `filter[field][operator]=value` (e.g., `filter[isCompleted][equals]=true`) - Sorts: `sort=field:direction,field2:direction` (e.g., `sort=createdAt:desc,name:asc`) - Pagination: `limit=X&offset=Y` Customization: - Override `queryParamsToHttpParams` method in API adapter mixins. ``` -------------------------------- ### SynquillStorage Class API Reference Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-reference.md Comprehensive API documentation for the `SynquillStorage` class, detailing its initialization, static properties, and instance/static methods for managing the synced storage system. ```APIDOC SynquillStorage: init({ required GeneratedDatabase database, SynquillStorageConfig? config, Logger? logger, void Function(GeneratedDatabase)? initializeFn, Stream? connectivityStream, Future Function()? connectivityChecker, bool enableInternetMonitoring = true, }) description: Initializes the synced storage system. Must be called once before any other operations. parameters: database: The Drift database instance to use for local storage config: Optional configuration for the storage system logger: Optional custom logger implementation initializeFn: Optional function to call after database setup (typically the generated `initializeSynquillStorage` function) connectivityStream: Optional stream that emits connectivity status connectivityChecker: Optional function to check current connectivity enableInternetMonitoring: Whether to enable internet connection monitoring (defaults to true) ``` ```APIDOC SynquillStorage: instance: static SynquillStorage get description: Returns the singleton instance. Throws StateError if not initialized. config: static SynquillStorageConfig? get description: Returns the global configuration. database: static GeneratedDatabase get description: Returns the global database instance. logger: static Logger get description: Returns the global logger instance. queueManager: static RequestQueueManager get description: Returns the global queue manager instance. retryExecutor: static RetryExecutor get description: Returns the global retry executor instance. dependencyResolver: static DependencyResolver get description: Returns the global dependency resolver instance. ``` ```APIDOC SynquillStorage: getRepository>(): T description: Retrieves a repository instance for the given model type. getRepositoryByName(modelTypeName: String): SynquillRepositoryBase>? description: Retrieves a repository instance by model type string name. processBackgroundSyncTasks(): Future description: Triggers background sync tasks to be processed immediately. processBackgroundSync(): static Future description: Static method to trigger background sync tasks without an instance. enableBackgroundMode(): static void description: Switches the retry executor to background mode for battery optimization. enableForegroundMode({bool forceSync = false}): static void description: Switches the retry executor to foreground mode for active use. close(): static Future description: Closes the synced storage system and releases all resources. reset(): static Future description: Resets the singleton instance and configuration (primarily for testing). ``` -------------------------------- ### Configure Server-Generated IDs in Synquill Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md To enable server-generated IDs for a Synquill data model, add the `idGeneration` parameter to the `@SynquillRepository` annotation and set its value to `IdGenerationStrategy.server`. This overrides the default client-side ID generation. ```Dart @SynquillRepository( idGeneration: IdGenerationStrategy.server, // default: IdGenerationStrategy.client adapters: [MyApiAdapter], ) class ServerManagedPost extends SynquillDataModel { @override final String id; final String title; final String content; ServerManagedPost({ required this.id, required this.title, required this.content, }); // ... toJson, fromJson, fromDb methods } ``` -------------------------------- ### Define One-to-Many Relationships in Synquill (Dart) Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Illustrates how to define a one-to-many relationship using the `@SynquillRepository` annotation and `OneToMany` decorator. It shows configuring cascade delete behavior, ensuring related records are removed when the parent is deleted. ```dart @SynquillRepository( relations: [ OneToMany( target: Todo, mappedBy: 'userId', cascadeDelete: true, // Delete todos when user is deleted ), ], ) class User extends SynquillDataModel { // ... model definition } ``` -------------------------------- ### SynquillRepositoryProvider API Reference Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-reference.md Documents the static methods of the `SynquillRepositoryProvider` class, which manages the registration and retrieval of repository factories and instances within the Synquill framework. It allows for dynamic access to repositories by type or name. ```APIDOC class SynquillRepositoryProvider { static void register>(RepositoryFactory factory) static SynquillRepositoryBase get>() static SynquillRepositoryBase>? getByTypeName(String typeName) static void reset() } ``` -------------------------------- ### SynquillRepositoryBase Class API Reference Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-reference.md API documentation for the `SynquillRepositoryBase` class, the base for synchronized repositories, detailing its properties for handling data change events. ```APIDOC SynquillRepositoryBase: changes: Stream> get description: A broadcast stream of repository change events (created, updated, deleted, error). ``` -------------------------------- ### Initialize Synquill Storage System in Flutter/Dart Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/README.md Demonstrates the initialization process for the Synquill storage system within a Flutter application's `main` function. It covers setting up the underlying Drift database with native options for isolate sharing and directory configuration, then configuring Synquill with data save/load policies, concurrency settings for foreground and background queues, logging, and integrating a real-time connectivity checker. ```dart import 'package:path_provider/path_provider.dart'; import 'package:synquill/synquill.generated.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize database final database = SynquillDatabase( LazyDatabase( () => driftDatabase( name: 'synquill.db', native: DriftNativeOptions( shareAcrossIsolates: true, // to sync between main thread and background isolate databaseDirectory: getApplicationSupportDirectory, ), ), ), ); // Configure and initialize Synquill await SynquillStorage.init( database: database, config: const SynquillStorageConfig( defaultSavePolicy: DataSavePolicy.localFirst, defaultLoadPolicy: DataLoadPolicy.localThenRemote, foregroundQueueConcurrency: 3, backgroundQueueConcurrency: 1, ), logger: Logger('MyApp'), initializeFn: initializeSynquillStorage, // provide your own connectivity stream and check function connectivityChecker: () async => await InternetConnection().hasInternetAccess, connectivityStream: InternetConnection() .onStatusChange .map((status) => status == InternetStatus.connected), ); runApp(MyApp()); } ``` -------------------------------- ### Run synquill Code Generation with build_runner Source: https://github.com/andreystavitsky/synquill/blob/main/synquill_gen/README.md Execute the code generation process for `synquill` models and repositories using the `build_runner` tool from the Dart SDK. This command should be run in your project's root directory after configuring the `pubspec.yaml` to generate necessary files. ```sh dart run build_runner build ``` -------------------------------- ### Synquill ID Conflict Strategy: Same Record Detection Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Explains how Synquill resolves ID conflicts when the existing local record is identical to the server's proposed record. This strategy cleans up temporary data and uses the existing ID, resulting in no conflict. ```dart // If the existing local record is actually the same entity: // - Compare all non-ID fields (name, content, etc.) // - If records are identical, cleanup temporary record and use existing ID // - Result: No conflict, existing record ID is used ``` -------------------------------- ### SynquillStorageConfig Class API Reference Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/api-reference.md API documentation for the `SynquillStorageConfig` class, outlining its constructor parameters and key properties for customizing SynquillStorage behavior. ```APIDOC SynquillStorageConfig: const SynquillStorageConfig({ this.dio, this.keepConnectionAlive = true, this.foregroundQueueConcurrency = 1, this.backgroundQueueConcurrency = 2, this.defaultSavePolicy = DataSavePolicy.localFirst, this.defaultLoadPolicy = DataLoadPolicy.localThenRemote, this.initialRetryDelay = const Duration(seconds: 2), this.maxRetryDelay = const Duration(minutes: 5), this.backoffMultiplier = 2.0, this.jitterPercent = 0.2, this.maxRetryAttempts = 50, this.foregroundPollInterval = const Duration(seconds: 5), this.backgroundPollInterval = const Duration(minutes: 5), this.minRetryDelay = const Duration(seconds: 1), // ... additional configuration options }) ``` ```APIDOC SynquillStorageConfig: foregroundQueueConcurrency: Concurrency level for foreground operations backgroundQueueConcurrency: Concurrency level for background operations defaultSavePolicy: Default save policy for all repositories defaultLoadPolicy: Default load policy for all repositories initialRetryDelay: Initial retry delay for failed sync operations maxRetryDelay: Maximum retry delay for failed sync operations maxRetryAttempts: Maximum number of retry attempts ``` -------------------------------- ### Synquill ID Conflict Strategy: Concurrent Operation Handling Source: https://github.com/andreystavitsky/synquill/blob/main/synquill/doc/guide.md Describes how Synquill handles ID conflicts arising from concurrent operations. It involves waiting for other operations to complete and retrying conflict resolution with an exponential backoff mechanism (1s, 2s, 4s) until resolved. ```dart // If the existing record is from another ongoing operation: // - Wait for other operation to complete // - Retry conflict resolution after delay // - Use exponential backoff (1s, 2s, 4s) // - Result: Resolved after other operation completes ```