Lix provides history APIs built on automatically captured changes in state. This enables the creation of powerful features like audit trails, version history, or restore functionality.
The change set graph in Lix is global and shared across all versions. This means you don't need to provide a version ID when querying history—you simply query at a specific change set. All versions share the same understanding of history because they all reference the same global change set graph.
To query the history of a file, you need to specify which change set you want to view the history from. The lixcol_root_change_set_id
field is used to filter the results to a specific point in the change set graph.
import { openLix } from "@lix-js/sdk";
// Get the current change set from the active version
const activeVersion = await lix.db
.selectFrom("active_version")
.innerJoin("version", "active_version.version_id", "version.id")
.select("version.change_set_id")
.executeTakeFirstOrThrow();
// Get all files in the current version
const currentFiles = await lix.db
.selectFrom("file")
.selectAll()
.execute();
console.log("Files in current version:");
currentFiles.forEach(file => {
console.log(`- ${file.path} (updated: ${file.lixcol_updated_at})`);
});
// For demonstration, show the file history concept using the example.json file
const exampleFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (exampleFile) {
console.log("\nFile history for /example.json:");
console.log(JSON.stringify({
path: exampleFile.path,
current_data: JSON.parse(new TextDecoder().decode(exampleFile.data)),
change_id: exampleFile.lixcol_change_id,
updated_at: exampleFile.lixcol_updated_at
}, null, 2));
}
You can also query the history of a single entity, like a paragraph in a Markdown file or a row in a CSV. This is useful for building features like comment threads or fine-grained audit trails.
import { openLix } from "@lix-js/sdk";
// Get the current change set from the active version
const activeVersion = await lix.db
.selectFrom("active_version")
.innerJoin("version", "active_version.version_id", "version.id")
.select("version.change_set_id")
.executeTakeFirstOrThrow();
// Get all files in the current version
const currentFiles = await lix.db
.selectFrom("file")
.selectAll()
.execute();
console.log("Files in current version:");
currentFiles.forEach(file => {
console.log(`- ${file.path} (updated: ${file.lixcol_updated_at})`);
});
// For demonstration, show the file history concept using the example.json file
const exampleFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (exampleFile) {
console.log("\nFile history for /example.json:");
console.log(JSON.stringify({
path: exampleFile.path,
current_data: JSON.parse(new TextDecoder().decode(exampleFile.data)),
change_id: exampleFile.lixcol_change_id,
updated_at: exampleFile.lixcol_updated_at
}, null, 2));
}
// Get all entities in the current state to demonstrate entity history
const allEntities = await lix.db
.selectFrom("state")
.selectAll()
.execute();
console.log("\nEntity history (current state):");
allEntities.forEach(entity => {
console.log(`- ${entity.entity_id} (${entity.schema_key})`);
console.log(` Created: ${entity.created_at}`);
console.log(` Updated: ${entity.updated_at}`);
console.log(` Change ID: ${entity.change_id}`);
});
To get the history of a file for a specific version, you simply query using that version's change set ID as the root. The history view will automatically traverse the change set graph to show all historical states.
import { openLix } from "@lix-js/sdk";
// Get the current change set from the active version
const activeVersion = await lix.db
.selectFrom("active_version")
.innerJoin("version", "active_version.version_id", "version.id")
.select("version.change_set_id")
.executeTakeFirstOrThrow();
// Get all files in the current version
const currentFiles = await lix.db
.selectFrom("file")
.selectAll()
.execute();
console.log("Files in current version:");
currentFiles.forEach(file => {
console.log(`- ${file.path} (updated: ${file.lixcol_updated_at})`);
});
// For demonstration, show the file history concept using the example.json file
const exampleFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (exampleFile) {
console.log("\nFile history for /example.json:");
console.log(JSON.stringify({
path: exampleFile.path,
current_data: JSON.parse(new TextDecoder().decode(exampleFile.data)),
change_id: exampleFile.lixcol_change_id,
updated_at: exampleFile.lixcol_updated_at
}, null, 2));
}
// Get all entities in the current state to demonstrate entity history
const allEntities = await lix.db
.selectFrom("state")
.selectAll()
.execute();
console.log("\nEntity history (current state):");
allEntities.forEach(entity => {
console.log(`- ${entity.entity_id} (${entity.schema_key})`);
console.log(` Created: ${entity.created_at}`);
console.log(` Updated: ${entity.updated_at}`);
console.log(` Change ID: ${entity.change_id}`);
});
// Get version information to show version-specific history
const allVersions = await lix.db
.selectFrom("version")
.selectAll()
.execute();
console.log("\nVersion-specific history:");
allVersions.forEach(version => {
console.log(`- Version: ${version.name} (ID: ${version.id})`);
console.log(` Change Set: ${version.change_set_id}`);
console.log(` Created: ${version.lixcol_created_at}`);
console.log(` Hidden: ${version.hidden ? 'Yes' : 'No'}`);
});
History is simply querying state at a specific change set. Because the change set graph is global, no version needs to be specified when querying history. Every change set is part of the same unified structure, allowing you to traverse the history without worrying about which version you are in.
Imagine a file, config.json
, changes over time. Each change creates a new change set that points to its predecessor:
config.json
is created with { "setting": "A" }
.config.json
is updated to { "setting": "B" }
.config.json
is updated to { "setting": "C" }
.config.json
is updated to { "setting": "D" }
.Querying the history at change set CS3 entails traversing the graph backward from that point:
import { openLix } from "@lix-js/sdk";
// Get the current change set from the active version
const activeVersion = await lix.db
.selectFrom("active_version")
.innerJoin("version", "active_version.version_id", "version.id")
.select("version.change_set_id")
.executeTakeFirstOrThrow();
// Get all files in the current version
const currentFiles = await lix.db
.selectFrom("file")
.selectAll()
.execute();
console.log("Files in current version:");
currentFiles.forEach(file => {
console.log(`- ${file.path} (updated: ${file.lixcol_updated_at})`);
});
// For demonstration, show the file history concept using the example.json file
const exampleFile = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirst();
if (exampleFile) {
console.log("\nFile history for /example.json:");
console.log(JSON.stringify({
path: exampleFile.path,
current_data: JSON.parse(new TextDecoder().decode(exampleFile.data)),
change_id: exampleFile.lixcol_change_id,
updated_at: exampleFile.lixcol_updated_at
}, null, 2));
}
// Get all entities in the current state to demonstrate entity history
const allEntities = await lix.db
.selectFrom("state")
.selectAll()
.execute();
console.log("\nEntity history (current state):");
allEntities.forEach(entity => {
console.log(`- ${entity.entity_id} (${entity.schema_key})`);
console.log(` Created: ${entity.created_at}`);
console.log(` Updated: ${entity.updated_at}`);
console.log(` Change ID: ${entity.change_id}`);
});
// Get version information to show version-specific history
const allVersions = await lix.db
.selectFrom("version")
.selectAll()
.execute();
console.log("\nVersion-specific history:");
allVersions.forEach(version => {
console.log(`- Version: ${version.name} (ID: ${version.id})`);
console.log(` Change Set: ${version.change_set_id}`);
console.log(` Created: ${version.lixcol_created_at}`);
console.log(` Hidden: ${version.hidden ? 'Yes' : 'No'}`);
});
// Example of the change set graph concept
console.log("History Data Model Example:");
console.log("Imagine config.json changes over time:");
console.log("CS1: { setting: 'A' } -> CS2: { setting: 'B' } -> CS3: { setting: 'C' } -> CS4: { setting: 'D' }");
// Query history from CS3's perspective would show:
const mockHistoryFromCS3 = [
{
data: { setting: "C" },
lixcol_change_set_id: "CS3",
lixcol_root_change_set_id: "CS3",
lixcol_depth: 0
},
{
data: { setting: "B" },
lixcol_change_set_id: "CS2",
lixcol_root_change_set_id: "CS3",
lixcol_depth: 1
},
{
data: { setting: "A" },
lixcol_change_set_id: "CS1",
lixcol_root_change_set_id: "CS3",
lixcol_depth: 2
}
];
console.log("History from CS3 perspective:");
console.log(JSON.stringify(mockHistoryFromCS3, null, 2));
console.log("Notice: CS4 is not included because it comes after CS3 in the graph");