Merge Contracts & Resource Types
This page explains the foundational concepts that govern how your data syncs across devices: resource types and merge contracts.
The Three Resource Types
Section titled “The Three Resource Types”Locorda uses three annotations to classify your data classes, each serving a distinct purpose in the sync architecture.
@RootResource - Syncable Entities
Section titled “@RootResource - Syncable Entities”A RootResource is an independent entity that syncs across storage backends using state-based CRDTs. Each instance has:
- Unique IRI (Internationalized Resource Identifier) - Generated from fields marked with
@RdfIriPart() - Independent lifecycle - Can be created, modified, deleted independently
- Merge contract - Defines CRDT merge strategies for conflict resolution
Real example from the personal notes app:
@RootResource( appVocab, mergeContract: MergeContract( label: 'Personal Note CRDT Document Mapping v1', comment: 'Defines how personal notes should merge when conflicts occur during sync.', ), iriStrategy: RootIriStrategy(RootIriConfig('note')), fullIndex: FullIndex.disabled(), subClassOf: SchemaNoteDigitalDocument.classIri,)class Note { @RdfIriPart() final String id;
@RdfProperty(SchemaNoteDigitalDocument.name) @CrdtLwwRegister() final String title;
@RdfProperty(SchemaNoteDigitalDocument.text) @CrdtLwwRegister() final String content;
@RdfProperty(SchemaNoteDigitalDocument.keywords) @CrdtOrSet() final Set<String> tags;
@RdfProperty(SchemaNoteDigitalDocument.dateCreated) @CrdtImmutable() final DateTime createdAt;
// ...}Examples of RootResources:
- A note in a notes app
- A category for organizing notes
- TODO: Add more examples
@SubResource - Nested Resources with Fragment IRIs
Section titled “@SubResource - Nested Resources with Fragment IRIs”A SubResource is a nested resource within a RootResource that has its own fragment IRI but is not registered globally in the type index. It:
- Fragment IRI - Identified using fragment IRIs derived from parent resource
- Inherits merge contract - Uses merge strategies from parent’s CRDT mapping
- Not globally indexed - Not registered in type index
Real example from the personal notes app:
@SubResource( appVocab, SubIriStrategy('#comment-{id}'), subClassOf: SchemaComment.classIri,)class Comment { @RdfIriPart() final String id; // Used in fragment template
@RdfProperty(SchemaComment.text) @CrdtLwwRegister() final String text;
@RdfProperty(SchemaComment.dateCreated) @CrdtImmutable() final DateTime createdAt;
// ...}Examples of SubResources:
- Comments on a note (IRI-identified sub-resources)
- TODO: Add more examples
When to use SubResource:
- TODO: Document decision criteria
@LocalResource - Blank Nodes (Local RDF Resources)
Section titled “@LocalResource - Blank Nodes (Local RDF Resources)”A LocalResource represents a blank node in RDF - a resource without a globally unique IRI that exists only within the context of a parent resource.
CRDT Merge Identification:
Blank nodes can be identified for CRDT merging in two ways:
-
Single-path blank nodes - Reachable via exactly one property path (e.g.,
Category → displaySettings). No identification annotation needed. -
Property-identified blank nodes - Multiple instances identified by a unique property marked with
@MergeIdentifying().
Real examples from the personal notes app:
Single-path blank node:
@LocalResource(appVocab)class CategoryDisplaySettings { @RdfProperty.define(fragment: 'color') @CrdtLwwRegister() final String? color;
@RdfProperty.define(fragment: 'icon') @CrdtLwwRegister() final String? icon;}Property-identified blank node:
@LocalResource(appVocab, subClassOf: SchemaThing.classIri)class Weblink { @RdfProperty(SchemaThing.url) @MergeIdentifying() // Identifies this blank node for CRDT merging @CrdtImmutable() final String url;
@RdfProperty(SchemaThing.name) @CrdtLwwRegister() final String? title;}Examples of LocalResources:
- Display settings for a category (single-path)
- Weblinks referenced by a note (property-identified)
- TODO: Add more examples
Merge Contracts: Defining CRDT Strategies
Section titled “Merge Contracts: Defining CRDT Strategies”A merge contract is a published RDF document that defines CRDT merge strategies for properties of a RootResource.
CRDT Property Annotations
Section titled “CRDT Property Annotations”Locorda provides three CRDT merge strategies via property annotations:
@CrdtLwwRegister() - Last-Write-Wins Register
Section titled “@CrdtLwwRegister() - Last-Write-Wins Register”TODO: Document:
- How LWW resolution works
- Hybrid Logical Clock usage
- Causality tracking
- Tie-breaking rules
@CrdtOrSet() - Observed-Remove Set
Section titled “@CrdtOrSet() - Observed-Remove Set”TODO: Document:
- How OR-Set merges additions and removals
- Use cases (tags, collections)
- Behavior with re-additions
@CrdtImmutable() - Immutable Values
Section titled “@CrdtImmutable() - Immutable Values”Used for properties that should never change once set:
- Creation timestamps
- Identifying properties
- Write-once data
Example: Mixed CRDT Strategies
Section titled “Example: Mixed CRDT Strategies”@RootResource(appVocab, mergeContract: MergeContract())class Note { @RdfIriPart() final String id;
@RdfProperty(Schema.name) @CrdtLwwRegister() // Last writer wins final String title;
@RdfProperty(Schema.keywords) @CrdtOrSet() // Additions/removals merge independently final Set<String> tags;
@RdfProperty(Schema.dateCreated) @CrdtImmutable() // Never changes final DateTime createdAt;}TODO: Add concrete merge scenario examples
Generated vs External Merge Contracts
Section titled “Generated vs External Merge Contracts”Automatic generation (recommended):
MergeContract( label: 'Note CRDT Mapping v1', comment: 'Defines merge strategies for notes',)The builder scans CRDT annotations on properties and generates merge contract RDF documents.
External contracts (for shared/standard vocabularies):
MergeContract.external('https://vocab.example.org/mappings/note-v1#')References a manually authored CRDT mapping document.
TODO: Document:
- How merge contracts are published
- How clients discover and validate compatibility
- Versioning strategies
How Resource Types and Merge Contracts Interact
Section titled “How Resource Types and Merge Contracts Interact”Hierarchy and Inheritance
Section titled “Hierarchy and Inheritance”Real example from personal notes app:
@RootResource( appVocab, mergeContract: MergeContract( label: 'Personal Note CRDT Document Mapping v1', ),)class Note { @RdfIriPart() final String id;
@RdfProperty(Schema.name) @CrdtLwwRegister() final String title;
@RdfProperty(Schema.relatedLink) @CrdtOrSet() final Set<Weblink> weblinks; // LocalResources (blank nodes)
@RdfProperty(Schema.comment) @CrdtOrSet() final Set<Comment> comments; // SubResources with fragment IRIs}
@SubResource(appVocab, SubIriStrategy('#comment-{id}'))class Comment { @RdfIriPart() final String id;
@RdfProperty(Schema.text) @CrdtLwwRegister() // Inherits from Note's merge contract final String text;}
@LocalResource(appVocab) // Blank nodeclass Weblink { @RdfProperty(Schema.url) @MergeIdentifying() @CrdtImmutable() final String url; // Identifies this blank node
@RdfProperty(Schema.name) @CrdtLwwRegister() // Inherits from Note's merge contract final String? title;}Inheritance rules:
- RootResource - Defines the merge contract
- SubResource - Inherits parent’s merge contract
- LocalResource - Inherits parent’s merge contract
TODO: Add composition examples once the actual patterns are documented
Design Guidelines
Section titled “Design Guidelines”TODO: Document decision criteria based on actual patterns from the implementation:
When to Use RootResource
Section titled “When to Use RootResource”TODO: Define criteria
When to Use SubResource
Section titled “When to Use SubResource”TODO: Document when to use SubResource vs LocalResource
- SubResource: Has fragment IRI, not in type index
- LocalResource: Blank node, no IRI
When to Use LocalResource
Section titled “When to Use LocalResource”TODO: Document blank node patterns:
- Single-path identified (CategoryDisplaySettings)
- Property-identified (Weblink)
Common Patterns
Section titled “Common Patterns”TODO: Add real-world patterns from personal_notes_app and other examples
Next Steps
Section titled “Next Steps”TODO: Add proper links once corresponding pages are created
- 📘 Conflict Resolution - TODO
- 📘 Indexing Strategies - TODO
- 📘 Getting Started Guide - TODO
Key Takeaways
Section titled “Key Takeaways”- RootResource = Independent entity with IRI, defines merge contract
- SubResource = Nested resource with fragment IRI, inherits merge contract
- LocalResource = Blank node (no globally unique IRI), inherits merge contract
- CRDT annotations define property-level merge strategies:
@CrdtLwwRegister()- Last-Write-Wins@CrdtOrSet()- Observed-Remove Set@CrdtImmutable()- Immutable
- Merge contracts are generated from CRDT annotations or referenced externally
- @MergeIdentifying() marks identifying properties for property-identified blank nodes