Skip to content

opral/lix

Repository files navigation

Lix

Version control system for every file format

weekly downloads on NPM Discord GitHub Stars X (Twitter)

Lix tracks, reviews, branches, merges, and rolls back changes across Markdown, DOCX, XLSX, JSON, PDFs, and custom file formats.

Use Lix standalone or as the change-control backend for editors, knowledge bases, AI workflows, and file-based products. Lix stores files, tracks semantic changes, exposes history through SQL, and brings version control workflows beyond source code.

  • 📌 Supports any file format. Create drafts, branches, checkpoints, and releases for Markdown, DOCX, XLSX, JSON, PDFs, and custom formats.
  • 🔍 Track semantic changes. See the paragraph, cell, property, clause, or custom entity that changed.
  • 🔀 Review and merge changes. Build change proposals, accept/reject flows, rollback, and merge workflows around files.
  • 🤝 Sync in real time. Keep versions and changes in sync across users, agents, devices, and runtimes.
  • Validate and automate. Run checks, enforce rules, and trigger workflows when files change.
  • 🧠 Query everything with SQL. Ask what changed, where, when, and by whom without rereading whole files.

Try a demo app

Flashtype is a Markdown editor for Claude and Codex built on Lix. Open local Markdown files, let agents edit them, review changes as diffs, and restore previous versions from history.

Flashtype app preview

Getting started

JavaScript JavaScript · Python Python · Rust Rust · Go Go

npm install @lix-js/sdk
import { FsBackend, openLix } from "@lix-js/sdk";

const lix = await openLix({
	backend: new FsBackend({ path: "./workspace" }),
});

await lix.execute(
	"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
	["/notes/status.txt", new TextEncoder().encode("draft")],
);

const main = await lix.activeBranchId();

const draft = await lix.createBranch({ name: "Explore" });

await lix.switchBranch({ branchId: draft.id });

await lix.execute(
	"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
	["/notes/status.txt", new TextEncoder().encode("ready for review")],
);

await lix.switchBranch({ branchId: main });

const changes = await lix.execute(
	"SELECT schema_key, count(*) AS count FROM lix_change GROUP BY schema_key",
);

Why Lix?

Version control should not stop at source code.

Most version control systems assume source code and text diffs. But many important products edit files where the useful change is a paragraph, spreadsheet cell, JSON property, PDF section, knowledge-base page, or custom entity.

Lix is built for those files. Plugins translate file updates into semantic changes that can be queried, reviewed, branched, merged, and rolled back.

Flashtype, a Markdown editor for Claude and Codex, uses Lix so every local Markdown edit can be checkpointed, reviewed as a diff, and restored.

How does Lix compare to Git? →

What Lix provides

Import as a library

Import Lix and open it inside your worker, service, CLI, browser, desktop app, or server-side runtime. No daemon, no protocol.

import { FsBackend, openLix } from "@lix-js/sdk";

const lix = await openLix({
	backend: new FsBackend({ path: "./workspace" }),
});

ACID transactions

Write files, blobs, and history in one transaction.

const tx = await lix.beginTransaction();

try {
	await tx.execute(
		"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
		["/spec.docx", body],
	);
	await tx.execute(
		"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
		["/spec.png", image],
	);
	await tx.commit();
} catch (error) {
	await tx.rollback();
	throw error;
}

Parallel branches

Give every draft, user, tool, or AI agent its own isolated branch.

const main = await lix.activeBranchId();

const copy = await lix.createBranch({ name: "Copy draft" });
const pricing = await lix.createBranch({ name: "Pricing draft" });
const qa = await lix.createBranch({ name: "QA draft" });

await lix.switchBranch({ branchId: copy.id });
await lix.execute(
	"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
	["/landing.md", copyDraft],
);

await lix.switchBranch({ branchId: pricing.id });
await lix.execute(
	"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
	["/plans.json", priceModel],
);

await lix.switchBranch({ branchId: qa.id });
await lix.execute(
	"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
	["/checks/report.json", testRun],
);

await lix.switchBranch({ branchId: main });

Semantic changes

Lix can track structured entities: XLSX rows, DOCX clauses, JSON properties, app records, custom entities, and more.

const changes = await lix.execute(`
  SELECT created_at, schema_key, entity_pk, snapshot_content
  FROM lix_change
  ORDER BY created_at DESC
  LIMIT 20
`);

For example, a workflow edits an orders spreadsheet:

Before:
| order_id | product  | status  |
| -------- | -------- | ------- |
| 1001     | Widget A | shipped |
| 1002     | Widget B | pending |

After:
| order_id | product  | status  |
| -------- | -------- | ------- |
| 1001     | Widget A | shipped |
| 1002     | Widget B | shipped |

A text-based diff can only tell you the file changed:

-Binary files differ

Lix can expose the row field that changed:

order_id 1002 status:

- pending
+ shipped

Read more about semantic changes →

SQL interface

Answer version-control questions with SQL instead of whole-file rereads.

Claude Code asks: Which orders changed status in this branch? Executing SQL

const rows = await lix.execute(`
  SELECT created_at, schema_key, entity_pk, snapshot_content
  FROM lix_change
  ORDER BY created_at DESC
  LIMIT 20
`);

Every change, across every file and every branch, is a row in lix_change. Filter by branch, file, schema, or time without re-reading whole files.

Portable runtimes and storage

Use Lix standalone or plug it into the infrastructure your product already runs.

SQLite SQLite · Postgres Postgres · S3 S3 · Cloudflare Workers Cloudflare Workers · Supabase Supabase

import { FsBackend, openLix } from "@lix-js/sdk";

const lix = await openLix({
	backend: new FsBackend({ path: "./workspace" }),
});

Use SqliteBackend when a single .lix SQLite file is the application document itself, for example when defining a new file format and using Lix as the application's file format.

How Lix works

Lix runs in-process inside your runtime.

It owns the version-control model: files, blobs, versions, history, transactions, and semantic changes. Use it standalone with FsBackend for filesystem workspaces, use SQLite for single-file application formats, or plug it into whatever backend you need: in-memory, Postgres, S3, Cloudflare, or your own adapter.

SQL is the query interface on top. Products, scripts, and agents can ask what changed without rereading whole files.

┌─────────────────────────────────────────────────┐
│                  Your runtime                   │
│       browser · desktop · server · CLI · worker  │
│                                                 │
│   ┌─────────────────────────────────────────┐   │
│   │                  Lix                    │   │
│   │  Filesystem · Versions · History · SQL  │   │
│   └────────────────────┬────────────────────┘   │
│                        │                        │
└────────────────────────┼────────────────────────┘
                         ▼
┌─────────────────────────────────────────────────┐
│                    Backend                      │
│      SQLite, Postgres, S3, Cloudflare, custom   │
└─────────────────────────────────────────────────┘

Read more about Lix architecture →

Learn More

License

MIT

About

Version control system for any file format

Topics

Resources

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages