⚠️ Early Development - Not Production Ready
Locorda is under active development. APIs may change, features are incomplete, and the specification is evolving.

🔗 Locorda

Sync offline-first apps using your user's remote storage

The rope that connects and weaves local data together

What is Locorda?

Locorda is a Dart/Flutter library for building offline-first applications that sync seamlessly with passive storage backends like Solid Pods, Google Drive, or any file storage system. It uses Conflict-free Replicated Data Types (CRDTs) to ensure safe collaboration without coordination, and stores all data as clean, semantic RDF for maximum interoperability.

📱

Offline-First

Your app works completely offline. Full functionality without any network connection. Sync happens automatically when connectivity is available.

🔄

CRDT Conflict Resolution

State-based CRDT algorithms ensure safe collaboration without coordination. Concurrent edits merge automatically with predictable outcomes.

🌐

Semantic Interoperability

All data stored as clean RDF using standard vocabularies (schema.org). Your data is semantic, portable, and interoperable.

Performance at Scale

Flexible indexing (Full vs. Grouped) and fetch strategies (Prefetch vs. OnDemand) handle datasets from 100 to 100,000+ resources. Smart sharding and header properties enable efficient partial sync.

🔒

User Data Ownership

Users bring their own backend (Solid Pod, Google Drive, etc.). Privacy-preserving architecture maintains complete user data control.

🎯

Developer Friendly

Simple annotations define merge strategies. Work with plain Dart objects. Automatic RDF conversion. Clean, intuitive API.

Get Started in Minutes

Add Locorda to your Flutter project and start building offline-first apps with automatic sync.

// 1. Add to pubspec.yaml
dependencies:
  locorda: ^0.1.0
  locorda_drift: ^0.1.0  # SQLite storage backend

// 2. Define your model with CRDT annotations
@RdfGlobalResource(IriTerm('https://example.org/Note'))
class Note {
  @RdfProperty(Schema.identifier)
  @RdfIriPart()
  String id;

  @RdfProperty(Schema.name)
  @CrdtLwwRegister()  // Last writer wins for title
  String title;

  @RdfProperty(Schema.text)
  @CrdtLwwRegister()  // Last writer wins for content
  String content;

  @RdfProperty(Schema.keywords)
  @CrdtOrSet()  // Tags can be added/removed independently
  Set<String> tags;

  Note({required this.id, required this.title, 
       required this.content, Set<String>? tags})
      : tags = tags ?? {};
}

// 3. Set up the sync system
final locorda = await Locorda.create(
  storage: DriftStorage(
    native: DriftNativeOptions(),
    web: DriftWebOptions(
      sqlite3Wasm: Uri.parse('sqlite3.wasm'),
      driftWorker: Uri.parse('drift_worker.js'),
    ),
  ),
  backends: [
    SolidBackend(auth: solidAuth),  // Optional: for Solid Pod sync
  ],
  mapperInitializer: (context) => initRdfMapper(
    rdfMapper: context.baseRdfMapper,
    // ... further mapper setup
  ),
  config: LocordaConfig(
    resources: [
      ResourceConfig(
        type: Note,
        crdtMapping: Uri.parse('$appBaseUrl/mappings/note-v1.ttl'),
        indices: [FullIndex(itemFetchPolicy: ItemFetchPolicy.prefetch)],
      ),
    ],
  ),
);

// 4. Set up hydration - Locorda syncs, you store in your database
final subscription = await locorda.hydrateWithCallbacks<Note>(
  getCurrentCursor: () => database.getCursor('note'),
  onUpdate: (note) => database.saveNote(note),
  onDelete: (id) => database.deleteNote(id),
  onCursorUpdate: (cursor) => database.saveCursor('note', cursor),
);

// 5. Use it! Works offline immediately
final note = Note(
  id: 'note-1',
  title: 'My First Note',
  content: 'This works offline and syncs to Solid Pods!',
  tags: {'offline-first', 'crdt'},
);

await locorda.save(note);  // Locorda handles sync, hydration updates your DB

// 6. Query YOUR database (Locorda is not a database!)
final notes = await database.getAllNotes();

4-Layer Architecture

Locorda follows a clean separation of concerns with four distinct layers working together.

1. Data Resource Layer

Clean RDF resources using standard vocabularies (schema.org). Your domain data stays semantic and portable.

2. Merge Contract Layer

Public CRDT rules define conflict resolution. Property-level merge strategies via sync: and algo: vocabularies.

3. Indexing Layer

Efficient change detection and performance optimization. Sharded indices with FullIndex and GroupIndex strategies.

4. Indexing & Sync Strategy Layer

Two-dimensional control: Index organization (Full vs. Grouped) determines data partitioning, fetch strategy (Prefetch vs. OnDemand) controls loading behavior. Header properties enable browsing without full document downloads.

Locorda acts as a synchronization "add-on" for passive storage backends while you retain full control over local storage and querying.

CRDT Merge Strategies

Choose the right merge strategy for each property to get predictable conflict resolution.

@CrdtLwwRegister()

Last Writer Wins - The most recent write wins in conflicts. Perfect for titles, descriptions, single-value fields.

@CrdtOrSet()

Observed-Remove Set - Add/remove items independently. All additions merge, explicit removes are preserved. Great for tags, categories, lists.

@CrdtImmutable()

Immutable - Never changes after creation. Use for creation timestamps, IDs, or any data that should never be modified.

@CrdtGRegister()

Greatest Value Wins - Maximum value wins in conflicts. Useful for counters, progress tracking, or monotonically increasing values.

RDF Vocabularies & Mappings

Semantic resources defining the framework's behavior and interoperability contracts.

📚 Vocabularies

🎯 Semantic Mappings