Build a simple app that tracks JSON file changes with Lix.
npm install @lix-js/sdk @lix-js/plugin-jsonOpen a Lix with plugins for the file formats you want to track. Plugins teach Lix what constitutes a meaningful change in each format.
In this example, we're using the default in-memory mode. For persistence to disk, cloud storage, or browser storage, see Persistence.
import { openLix, createCheckpoint } from "@lix-js/sdk";
import { plugin as jsonPlugin } from "@lix-js/plugin-json";
const lix = await openLix({
providePlugins: [jsonPlugin],
});Without plugins, Lix treats files as binary blobs. The jsonPlugin enables entity-level tracking by detecting that the age property changed from 50 to 51, rather than just "line 3 changed."
Files are stored as Uint8Array (binary data), making Lix format-agnostic. Once inserted, every change is automatically tracked.
Insert a file:
import { openLix, createCheckpoint } from "@lix-js/sdk";
import { plugin as jsonPlugin } from "@lix-js/plugin-json";
const lix = await openLix({
providePlugins: [jsonPlugin],
});const json = {
name: "Peter",
age: 50,
};
await lix.db
.insertInto("file")
.values({
path: "/example.json",
data: new TextEncoder().encode(JSON.stringify(json)),
})
.execute();
const firstCheckpoint = await createCheckpoint({ lix });Update the file:
import { openLix, createCheckpoint } from "@lix-js/sdk";
import { plugin as jsonPlugin } from "@lix-js/plugin-json";
const lix = await openLix({
providePlugins: [jsonPlugin],
});
const json = {
name: "Peter",
age: 50,
};
await lix.db
.insertInto("file")
.values({
path: "/example.json",
data: new TextEncoder().encode(JSON.stringify(json)),
})
.execute();
const firstCheckpoint = await createCheckpoint({ lix });// we update the user's age to 51
await lix.db
.updateTable("file")
.where("path", "=", "/example.json")
.set({
data: new TextEncoder().encode(
JSON.stringify({ name: "Peter", age: 51 }),
),
})
.execute();
const secondCheckpoint = await createCheckpoint({ lix });The JSON plugin detects precisely what changed (in this case, the age property from 50 to 51) while preserving all previous states.
Query file state at any point in time using SQL and checkpoints:
import { openLix, createCheckpoint } from "@lix-js/sdk";
import { plugin as jsonPlugin } from "@lix-js/plugin-json";
const lix = await openLix({
providePlugins: [jsonPlugin],
});
const json = {
name: "Peter",
age: 50,
};
await lix.db
.insertInto("file")
.values({
path: "/example.json",
data: new TextEncoder().encode(JSON.stringify(json)),
})
.execute();
const firstCheckpoint = await createCheckpoint({ lix });
// we update the user's age to 51
await lix.db
.updateTable("file")
.where("path", "=", "/example.json")
.set({
data: new TextEncoder().encode(
JSON.stringify({ name: "Peter", age: 51 }),
),
})
.execute();
const secondCheckpoint = await createCheckpoint({ lix });// Query file history at each checkpoint
for (const { label, checkpoint } of [
{ label: "Second checkpoint", checkpoint: secondCheckpoint },
{ label: "First checkpoint", checkpoint: firstCheckpoint },
]) {
const fileState = await lix.db
.selectFrom("file_history")
.where("path", "=", "/example.json")
.where("lixcol_depth", "=", 0)
.where("lixcol_root_commit_id", "=", checkpoint.id)
.select(["path", "data"])
.executeTakeFirstOrThrow();
console.log(`${label}:`, {
...fileState,
data: JSON.parse(new TextDecoder().decode(fileState.data)),
});
}The file_history view reconstructs file state by walking backwards from a commit (lixcol_root_commit_id). Use lixcol_depth = 0 to get the exact state at that checkpoint.
Lix automatically commits every change. Checkpoints are optionally labelled commits for important moments like before/after a migration or deployment.
Track changes in your app:
Add more file types: