Unlike traditional version control that treats files as opaque blobs of text, Lix understands the structure of your data. Diffs are computed at the entity level, where an "entity" is a semantic unit defined by a plugin. For example:
This fine-grained, semantic approach makes changes easier to review, merge, and build powerful user experiences around.
By operating on entities instead of raw text lines, Lix provides several key advantages over traditional diffing methods.
Benefit | Description |
---|---|
Semantic Meaning | Diffs represent meaningful changes ("price changed from $10 to $12") not just arbitrary line edits. |
Precise Attribution | Pinpoint exactly who changed what and when, down to a single cell or property, avoiding whole-file noise. |
Fewer Conflicts | Merging changes to different entities (e.g., two different JSON properties) is trivial, even if they are on the same text line. |
Pluggable Granularity | Your domain model drives the diff. Plugins can define what constitutes a "change," tailoring diffs to your specific needs. |
The most straightforward way to see a diff is to query two versions of a file and compare them. The following example gets the current version and the previous version of a JSON file to generate a simple textual diff.
import { openLix } from "@lix-js/sdk";
const plugin = (await lix.plugin.getAll()).find(
(p: any) => p.key === "lix_plugin_csv",
);
if (plugin?.renderDiff) {
console.log("Plugin renderDiff available:", true);
} else {
console.log("No renderDiff function found for CSV plugin");
}
// 1. Get the current state of a JSON file from the getting-started example
const currentFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (currentFile) {
const currentData = JSON.parse(new TextDecoder().decode(currentFile.data));
console.log("Current file data:", currentData);
// For demo purposes, create a mock previous version
const previousData = { name: "Peter", age: 50 };
const afterState = currentData;
const beforeState = previousData;
// 2. Compare the two states to generate a diff.
//
// This is a simplified example. In a real app,
// you would likely use a diffing library.
const diffOutput: string[] = [];
if (beforeState.name !== afterState.name) {
diffOutput.push(`- name: ${beforeState.name}`);
diffOutput.push(`+ name: ${afterState.name}`);
}
if (beforeState.age !== afterState.age) {
diffOutput.push(`- age: ${beforeState.age}`);
diffOutput.push(`+ age: ${afterState.age}`);
}
// 3. The diff can then be displayed in your application.
console.log("Diff output:");
console.log(diffOutput.join("\n"));
} else {
console.log(
"File /example.json not found - run getting-started example first",
);
}
Lix's primary role is to provide you with the "before" and "after" states of your data. Your application can then use this data to calculate and render a diff.
As shown in the Quick Start, you can query a file at two different versions to get its complete state at both points in time. This is useful for showing a full before-and-after view of a resource.
For better performance and more granular control, you can query a single entity at two different versions. This is highly efficient as it avoids loading the entire file when you only care about a specific part of the data.
The same principle applies to any entity defined by a Lix plugin, such as a paragraph in a Markdown document or a cell in a CSV file.
import { openLix } from "@lix-js/sdk";
const plugin = (await lix.plugin.getAll()).find(
(p: any) => p.key === "lix_plugin_csv",
);
if (plugin?.renderDiff) {
console.log("Plugin renderDiff available:", true);
} else {
console.log("No renderDiff function found for CSV plugin");
}
// 1. Get the current state of a JSON file from the getting-started example
const currentFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (currentFile) {
const currentData = JSON.parse(new TextDecoder().decode(currentFile.data));
console.log("Current file data:", currentData);
// For demo purposes, create a mock previous version
const previousData = { name: "Peter", age: 50 };
const afterState = currentData;
const beforeState = previousData;
// 2. Compare the two states to generate a diff.
//
// This is a simplified example. In a real app,
// you would likely use a diffing library.
const diffOutput: string[] = [];
if (beforeState.name !== afterState.name) {
diffOutput.push(`- name: ${beforeState.name}`);
diffOutput.push(`+ name: ${afterState.name}`);
}
if (beforeState.age !== afterState.age) {
diffOutput.push(`- age: ${beforeState.age}`);
diffOutput.push(`+ age: ${afterState.age}`);
}
// 3. The diff can then be displayed in your application.
console.log("Diff output:");
console.log(diffOutput.join("\n"));
} else {
console.log(
"File /example.json not found - run getting-started example first",
);
}
// Get current entities from the state table to demonstrate entity diffing
const currentEntities = await lix.db
.selectFrom("state")
.selectAll()
.limit(2)
.execute();
if (currentEntities.length >= 2) {
console.log("Comparing two entities from the current state:");
console.log(
`Entity 1: ${currentEntities[0].entity_id} (${currentEntities[0].schema_key})`,
);
console.log(
`Entity 2: ${currentEntities[1].entity_id} (${currentEntities[1].schema_key})`,
);
// Show their content differences
console.log("Entity 1 content:", currentEntities[0].snapshot_content);
console.log("Entity 2 content:", currentEntities[1].snapshot_content);
} else {
console.log("Not enough entities to compare - need at least 2 entities");
}
Once you have the "before" and "after" data, you have complete control over how the changes are visualized. There are three common approaches.
Approach | Best For | Control Level |
---|---|---|
Use a Plugin's UI | Quick integration with standard, pre-built components. | Low |
Use a Diff Library | Custom integration with powerful, pre-built diffing logic. | Medium |
Build Your Own UI | Complete control for bespoke or complex visualizations. | High |
Lix plugins can provide ready-to-use diff components that can be rendered directly in your application. This is the fastest way to display changes, but it limits you to the UI and rendering logic provided by the plugin.
import { openLix } from "@lix-js/sdk";
const plugin = (await lix.plugin.getAll()).find(
(p: any) => p.key === "lix_plugin_csv",
);
if (plugin?.renderDiff) {
console.log("Plugin renderDiff available:", true);
} else {
console.log("No renderDiff function found for CSV plugin");
}
You can use a dedicated library to visualize the changes in your data. Popular options include general-purpose libraries like diff for text, or specialized solutions like the @lix-js/html-diff package.
The Lix HTML diff is particularly powerful because it can compare the rendered HTML output of your components, making it a universal tool for visualizing UI changes in any web app.
For complete control, you can always implement your own diff rendering logic. Because Lix provides structured "before" and "after" states, you can build any kind of diff UI you can imagine—from text-based diffs to visual comparisons of images, designs, or even 3D models.