Function: selectVersionDiff()

selectVersionDiff(args: { lix: Lix; source: Pick<Version, "id">; target?: Pick<{ commit_id: string; hidden?: LixGenerated<undefined | boolean>; id: LixGenerated<string>; inherits_from_version_id?: null | string; name: LixGenerated<string>; working_commit_id: LixGenerated<string>; }, "id">; }): SelectQueryBuilder<any, "diff", DiffRow>

Compares two versions and returns differences between their entities.

This function is modeled for merging a source version into a target version, which is why the source always wins in conflict scenarios (when both versions modified the same entity). It performs a full outer join between source and target versions to identify created, updated, deleted, and unchanged entities.

Note: More sophisticated diff strategies and proper conflict handling are planned for the future. Please upvote https://github.com/opral/lix-sdk/issues/368 if you need conflict detection and resolution capabilities.

The returned query builder allows for flexible filtering and composition before executing the diff. When no target is specified, compares against the active version.

Diff status meanings:

  • created: Entity exists only in source version (new addition)
  • deleted: Entity explicitly deleted in source (tombstone present)
  • updated: Entity exists in both but with different change_ids (source wins)
  • unchanged: Entity has same change_id in both OR exists only in target without explicit deletion

Visual representation (source → target):

Status | Source | Target | before_version_id | after_version_id | before_* | after_* ------------|--------|--------|-------------------|------------------|----------|---------- created | ✓ | ✗ | null | source.id | null | source deleted | ✓* | ✓ | target.id | source.id | target | tombstone updated | ✓ | ✓ | target.id | source.id | target | source unchanged | ✓ | ✓ | target.id | source.id | same | same unchanged | ✗ | ✓ | target.id | target.id | target | target
  • Source ✓* indicates a tombstone (explicit deletion)

Performance tips:

  • Filter by status to exclude unchanged entities (most common)
  • Filter by file_id when diffing specific documents
  • Filter by schema_key when interested in specific entity types

Examples

// Get all changes between two versions
const changes = await selectVersionDiff({ lix, source, target })
  .where('diff.status', '!=', 'unchanged')
  .execute();
// Compare specific file between source and active version
const fileDiff = await selectVersionDiff({ lix, source })
  .where('diff.file_id', '=', 'file1.json')
  .where('diff.status', '!=', 'unchanged')
  .orderBy('diff.entity_id')
  .execute();
// Get only entities of a specific schema that were modified
const schemaDiff = await selectVersionDiff({ lix, source, target })
  .where('diff.schema_key', '=', 'message')
  .where('diff.status', 'in', ['created', 'updated', 'deleted'])
  .execute();
// Find entities that exist only in target (no explicit delete in source)
const targetOnly = await selectVersionDiff({ lix, source, target })
  .where('diff.status', '=', 'unchanged')
  .whereRef('diff.after_version_id', '=', 'diff.before_version_id')
  .execute();
// Check if specific entities changed
const entityDiff = await selectVersionDiff({ lix, source, target })
  .where('diff.entity_id', 'in', ['entity1', 'entity2', 'entity3'])
  .where('diff.status', '!=', 'unchanged')
  .execute();

if (entityDiff.length > 0) {
  // Some entities changed
}

## Understanding Common Ancestor Behavior

Imagine you and a colleague both start from the same document (common ancestor):
- You create a "source" version and make changes
- Your colleague creates a "target" version and makes different changes

When comparing these versions, the diff needs to know:
1. What did YOU intentionally delete? (will be removed from target)
2. What did your colleague add that you never knew about? (will be kept)

The system tracks deletions using "tombstones" - special markers that say
"this entity was deleted". When you delete something, a tombstone is created.

This means:
- If you deleted "entity A" that existed in the common ancestor →
  Status: "deleted" (tombstone present, will be removed from target)
- If "entity B" only exists in target (added after you created your version)  Status: "unchanged" (no tombstone, you never knew about it, so it stays)

Without this logic, the system couldn't tell the difference between
"I deleted this" and "I never had this".

Parameters

ParameterType
args{ lix: Lix; source: Pick<Version, "id">; target?: Pick<{ commit_id: string; hidden?: LixGenerated<undefined | boolean>; id: LixGenerated<string>; inherits_from_version_id?: null | string; name: LixGenerated<string>; working_commit_id: LixGenerated<string>; }, "id">; }
args.lixLix
args.sourcePick<Version, "id">
args.target?Pick<{ commit_id: string; hidden?: LixGenerated<undefined | boolean>; id: LixGenerated<string>; inherits_from_version_id?: null | string; name: LixGenerated<string>; working_commit_id: LixGenerated<string>; }, "id">

Returns

SelectQueryBuilder<any, "diff", DiffRow>