Note
Try the demo.
Lix is a change control system that runs in the browser and an SDK to build web apps.
A change control system allows storing, tracking, querying, and reviewing changes in different file formats, e.g., .xlsx
, .sqlite
, or .inlang
. Compared to a version control system, a change control system understands “what” changed in a file, e.g., cell C43 in the financials spreadsheet changed.
Tip
Lix is not a git replaced, nor designed for software engineering.
The goal is to bring change control workflows to industries other than software engineering like health, finance, policy making, design, etc.
- File Storage: Store and manage files.
- Versioning: The possibility to create divergent state (branches in git).
- Collaboration: Lix comes with built-in sync which enables asynchronous (git-like) and real-time collaboration (Google Docs-like) capabilities.
- Validation Rules: (WIP #239)Define and enforce validation rules for your data.
- Change proposals: (WIP #242)Similar workflow to pull requests in git.
- 🔍 Everything is SQL: Querying lix happens via SQL. No new query language to learn. The lix SDK conveniently exposes Kysely, a type-safe SQL query builder.
- 🌐 Browser-based: Lix targets the browser as target platform. Node/unix dependencies are ruled out.
Tip
The documentation is a work in progress. If you have any questions, please open issues in the GitHub repository.
Note
The getting started uses a JSON file but you can store any file in lix, even a SQLite database, as long as a plugin for the file format is provided.
Install the Lix SDK package:
npm install @lix-js/sdk
Note
We plan to have locally installed plugins in the future with no need to provide them upfront, see #241.
Create and open a new Lix file:
import { newLixFile, openLixInMemory } from "@lix-js/sdk";
import { plugin as jsonPlugin } from "@lix-js/plugin-json";
// Create a new empty Lix file
const lixFile = await newLixFile();
// Open the Lix file in memory
const lix = await openLixInMemory({
blob: lixFile,
providePlugins: [jsonPlugin],
});
Inserting an examplary JSON file into lix:
const json = {
name: "Hello World",
version: "1.0.0",
settings: {
enableFeatureX: true,
maxUsers: 10,
},
};
// Insert the file
const file = await lix.db
.insertInto("file")
.values({
path: "/example.json",
data: new TextEncoder().encode(JSON.stringify(json)),
})
.returningAll()
.executeTakeFirstOrThrow();
console.log("JSON file inserted with ID:", file.id);
Let's update our JSON file with some changes:
{
name: "Hello World",
- version: "1.0.0",
+ version: "1.1.0",
settings: {
enableFeatureX: true,
- maxUsers: 10,
+ maxUsers: 20,
},
};
// updating the json from the previous example
json["version"] = "1.1.0";
json["settings"]["maxUsers"] = 20;
// Update the file
await lix.db
.updateTable("file")
.set({
data: new TextEncoder().encode(JSON.stringify(updatedJson)),
})
.where("path", "=", "/example.json")
.execute();
Lix detect changes in file updates via the file-queue and the provided plugin which detects changes.
Now we can query the changes that were made to the JSON file:
// Get all changes for this file
const changes = await lix.db
.selectFrom("change")
.where("file_id", "=", file.id)
.innerJoin("snapshot", "snapshot.id", "change.snapshot_id")
.execute();
console.log("Changes for the JSON file:", changes);
const versionB = await createVersion({ lix, name: "B" });
// Create a new version based on the parent version
await switchVersion({ lix, to: versionB });
console.log("New version created:", versionB.id);
We'll update the JSON again by bumping the version to 1.2.0 with a new maxUsers count of 30:
{
name: "Hello World",
- version: "1.1.0",
+ version: "1.2.0",
settings: {
enableFeatureX: true,
- maxUsers: 20,
+ maxUsers: 30,
},
};
Querying changes in a specific version is simple by using the changeInVersion()
filter:
import { createChange } from "@lix-js/sdk";
const changes = await lix.db
.selectFrom("change")
.where("file_id", "=", file.id)
.where(changeInVersion(versionB))
.innerJoin("snapshot", "snapshot.id", "change.snapshot_id")
.execute();
console.log(
`Changes for the JSON file in version ${versionB.name}:`, changes
);
The file table is derived from the changes in the current version. Hence, querying the file after updating the json in the previous step will return the updated file:
const file = await lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirstOrThrow();
console.log(file)
{
"name": "Hello World",
"version": "1.2.0",
"settings": {
"enableFeatureX": true,
"maxUsers": 30,
},
}
Switch back to version A and you will see that the changes of version B are not present:
await switchVersion({ lix, to: versionA })
console.log("Switched to version: main");
// Get the file data in this version
const originalFile = lix.db
.selectFrom("file")
.where("path", "=", "/example.json")
.selectAll()
.executeTakeFirstOrThrow();
const originalContent = JSON.parse(originalFile.content);
console.log("Original content in main version:", originalContent);
{
"name": "Hello World",
"version": "1.1.0",
"settings": {
"enableFeatureX": true,
"maxUsers": 20,
},
}
Merge changes from version B into version A:
await mergeVersion({lix, sourceVersion: versionB, targetVersion: versionA});
// Check the merged state
const file_after_merge = lix.db
.selectFrom("file")
.where("id", "=", file.id)
.select("data")
.executeTakeFirstOrThrow();
console.log("File state after merge:", JSON.parse(file_after_merge.data));
{
"name": "Hello World",
"version": "1.2.0",
"settings": {
"enableFeatureX": true,
"maxUsers": 30,
},
}
Lix uses plugins to understand and track changes in different file formats. Documentation is work in progress. Find example plugins here:
Apps that revolve around changes are prime use cases to be built on lix. Having an “edit -> review -> automate” workflow is a good indicator for an app that revolves around changes:
- document editors (markdown, PDF, etc.)
- accounting solutions (auditing)
- content creation tools (Figma, Adobe Premiere)
- spreadsheets (Excel, Google Sheets)
- CAD software (Fusion 360, Autodesk)
- data science (Jupyter Notebooks)
- dashboards (Analytics, Infrastructure)
Developing a web app with lix is reduced to providing a user interface that opens and saves a file.
Backend features like auth, permissions, change control, and automations are solved on a file rather than an application-specific level. Lix knows who the actors are (auth), allows actors to conduct changes (permissions), is aware of how data changes over time (change history), can merge changes (collaboration), and uses the knowledge of changes to trigger automations (automation pipelines).
In contrast, cloud-based apps are substantially more difficult to build because they require:
- Auth (could be solved with a backend as a service)
- Integrations (no interop between apps)
- Ensure data privacy and security
- Scaling complexity of storing all customer data
- Replicating change control features like history, branching, etc.