Datum Documentation

website·Indexed 31 days ago

https://datum.shreeman.dev

A data orchestration and synchronization framework for Dart and Flutter projects. Datum v1.0.3 provides a type-safe system for managing data models via DatumEntity, featuring a Core module for sync engines, a Query module for filtering and pagination, and a flexible Adapter system supporting local storage (Hive, Isar, SQLite) and remote backends (Supabase, Firebase, REST API). It includes tools for code generation, relationship management, and schema migrations.

Tokens
74.7K
Snippets
298
Records
354
Agent score
56%

What's inside Datum

  1. Overview of Datum relationship types

    Datum supports four primary relationship types between entities:

    • BelongsTo: The current entity holds the foreign key pointing to another entity.
    • HasMany: Other entities hold the foreign key pointing to this entity (one-to-many).
    • HasOne: Other entity holds the foreign key pointing to this entity (one-to-one).
    • ManyToMany: A many-to-many relationship implemented via a pivot/junction table.
  2. Overview of Datum offline-first data synchronization framework

    Datum is an offline-first data synchronization framework designed to manage data consistency and availability across clients and servers, ensuring applications remain functional without a constant internet connection.
  3. Overview of Datum Offline-First Synchronization

    Datum is a framework for Dart and Flutter applications that ensures data remains functional without a network connection by synchronizing a local database with a remote server. It employs an 'offline-first' approach where all data operations are performed locally first for UI responsiveness, then automatically synced to the remote backend when connectivity is available.
  4. Overview of Supabase Remote Adapter for Datum

    The Supabase remote adapter enables live data synchronization in Datum by leveraging Supabase's real-time database capabilities. It utilizes Supabase realtime subscriptions for live sync and PostgREST for performing CRUD operations on PostgreSQL.
  5. Understand Datum Core Concepts and Architecture

    Datum's architecture is based on several key components:

    • DatumEntity: The base class for data models, requiring a unique id, userId, and synchronization metadata.
    • RelationalDatumEntity: An extension of DatumEntity that supports relationships between entities.
    • Adapter: Bridges Datum to data sources. LocalAdapter handles device persistence (e.g., Hive, Isar, SQLite), and RemoteAdapter handles backend communication (e.g., REST API, Supabase, Firestore).
    • Datum: The main entry point and singleton API for CRUD operations, queries, and synchronization.
  6. Datum Licensing and Cost

    Datum is free and open-source, released under the MIT License. It can be used in personal or commercial projects without fees. However, users are responsible for the costs of the backend infrastructure (e.g., Supabase, Firebase, or self-hosted servers) used to connect to the library.
  7. Choose between Standard and Isolated Hive Local Adapters for Datum

    Datum provides two Hive-based implementations of the LocalAdapter interface for data persistence in Flutter applications:

    1. HiveLocalAdapter: The standard adapter intended for use in regular Flutter application foreground tasks.
    2. IsolatedHiveLocalAdapter: A specialized adapter designed for background processing and isolates, ensuring thread-safe data access.
  8. Choose the appropriate local database adapter for Datum

    Select a database adapter based on your data model complexity and performance requirements.

    • Hive: Best for simple reads/writes, small bundle size, and fast setup. Poor for complex queries and relationships.
    • SQLite: Best for complex queries, relational data, and large datasets. Moderate setup complexity and bundle size.
    • In-Memory: Best for testing and simple reads/writes. Not suitable for large datasets or persistent storage.
    | Operation        | Hive  | SQLite | In-Memory |
    | ---------------- | ----- | ------ | --------- |
    | Simple Read      | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐   | ⭐⭐⭐⭐⭐     |
    | Simple Write     | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐   | ⭐⭐⭐⭐⭐     |
    | Complex Query    | ⭐⭐    | ⭐⭐⭐⭐⭐  | ⭐⭐⭐⭐⭐     |
    | Relationships    | ⭐⭐    | ⭐⭐⭐⭐⭐  | ⭐⭐⭐⭐      |
    | Large Datasets   | ⭐⭐⭐   | ⭐⭐⭐⭐⭐  | ⭐⭐        |
    | Setup Complexity | ⭐⭐⭐   | ⭐⭐     | ⭐⭐⭐⭐⭐     |
    | Bundle Size      | ⭐⭐⭐⭐⭐ | ⭐⭐⭐    | ⭐⭐⭐⭐⭐     |
  9. Manage relational data in Datum

    Datum supports managing relationships between different DatumEntity types (specifically RelationalDatumEntity). You can either perform a one-time fetch of related entities or watch them for real-time updates.
    // Fetch related tasks for a specific project
    Future<List<Task>> getTasksForProject(Project project) async {
      return await Datum.instance.fetchRelated<Project, Task>(project, 'tasks');
    }
    
    // Watch related tasks for a specific project (real-time updates)
    StreamSubscription? projectTasksSubscription;
    void startWatchingProjectTasks(Project project) {
      projectTasksSubscription = Datum.instance.watchRelated<Project, Task>(project, 'tasks')?.listen((tasks) {
        print('Project "${project.name}" Tasks Updated (${tasks.length})');
      });
    }
  10. Perform scoped or forced synchronization

    Synchronization can be controlled using DatumSyncScope to filter which data is synced and DatumSyncOptions to define the behavior of the sync process (e.g., forcing a full sync or changing the sync direction).
    // Example 1: Sync only active tasks using DatumSyncScope
    final scope = DatumSyncScope(
      query: DatumQueryBuilder<Task>()
        .where('isCompleted', equals, false)
        .build(),
    );
    
    final result = await Datum.manager<Task>().synchronize(
      'user123',
      scope: scope,
    );
    
    // Example 2: Force a full sync using DatumSyncOptions
    final result = await Datum.manager<Task>().synchronize(
      'user123',
      options: DatumSyncOptions<Task>(
        forceFullSync: true,
        resolveConflicts: true,
      ),
    );
  11. Best practices for defining automated relationships

    When using relationship annotations in Datum, follow these guidelines to ensure maintainability and correctness:

    1. Field Naming: Use descriptive placeholder names for relationship fields (e.g., _posts, _profile).
    2. Cascade Behavior: Always explicitly define the cascadeDelete parameter to avoid ambiguity.
    3. Documentation: Use comments to explain complex relationships, especially Many-to-Many mappings involving pivot tables.
    4. Organization: Group all relationship annotations together within the class definition.
    // Example of explicit cascade delete
    @HasManyRelation<Comment>('postId', cascadeDelete: 'cascade')
    final List<Comment>? _comments = null;
    
    // Example of documented Many-to-Many relationship
    // Tags associated with this post through the post_tags pivot table
    @ManyToManyRelation<Tag, PostTag>(
      pivotEntity: PostTag,
      thisForeignKey: 'postId',
      otherForeignKey: 'tagId',
    )
    final List<Tag>? _tags = null;
  12. Implement connection-aware synchronization

    Create a custom connectivity checker by extending DatumConnectivityChecker and providing it to DatumConfig. This allows the application to adapt synchronization intervals or behavior based on the current network quality.
    class SmartConnectivityChecker extends DatumConnectivityChecker {
      @override
      Future<bool> isConnected() async {
        final quality = await checkConnectionQuality();
        return quality != ConnectionQuality.none;
      }
    
      Future<ConnectionQuality> checkConnectionQuality() async {
        // Return poor/fair/good/none
      }
    }
    
    final config = DatumConfig(
      connectivityChecker: SmartConnectivityChecker(),
    );
    
    // Adaptive sync intervals
    if (await connectivityChecker.isConnected()) {
      manager.startAutoSync('user123', interval: Duration(minutes: 5));
    } else {
      manager.startAutoSync('user123', interval: Duration(hours: 1));
    }