Hosting
A hosted repository lives on a server. The server owns its storage and authentication. Clients can execute directly on the server or keep a synchronized local replica.
A service can access the repository through SDK calls, an agent sandbox can synchronize a filesystem directory, and a browser can keep a local replica in OPFS. All three connect to the same server. See the setup examples for client configuration and storage adapters.
There are two ways to get a server:
| Option | Who runs it | Use for |
|---|---|---|
| lixray.com | The Lix team | Getting started, and teams without a host |
| Your own host | You | Your infrastructure, your auth, your data |
Both speak the same protocol. Only the URL changes in client code.
Create and delete repositories
Use createLix({ server: { url: hostOrigin, headers } }) to create a hosted repository programmatically. It returns { id, url }; pass url to openLix({ server: { url, headers } }). Add storage and explicitly set server.mode: "partial_replica" to keep a partial replica with on-demand sync. Omitting the mode defaults to remote SQL and does not accept client storage.
createLix({ server, from: localLix }) creates a point-in-time copy including history and untracked rows. It does not connect the source handle. Use deleteLix({ server: { url, headers } }) to delete the hosted repository; closing a session does not delete it. See the API reference.
Rust
use lix::{create_lix, delete_lix, open_lix, ServerOptions};
let local = open_lix().await?;
let repository = create_lix()
.with_server(ServerOptions::new("https://example.com"))
.from_lix(&local)
.await?;
let remote = open_lix()
.with_server(ServerOptions::new(&repository.url))
.await?;
remote.execute("SELECT * FROM lix_file", &[]).await?;
remote.close().await?;
// Use a durable adapter supplied by a storage package.
let replica = open_lix()
.with_storage(storage)
.with_server(ServerOptions::new(&repository.url))
.await?;
replica.close().await?;
delete_lix()
.with_server(ServerOptions::new(&repository.url))
.await?;
Omit .from_lix(&local) to create an empty hosted repository. The result is HostedLix { id, url }. The copy is taken at one point in time and preserves history and untracked rows. It does not connect the source. Pass .with_idempotency_key(key) to get the same result on retries; when omitted, a key is generated for each call. Configure credentials with ServerOptions::with_headers.
To reconnect the original durable storage to the hosted copy: pause writes, create the hosted copy, close the source, and reopen that same storage with the returned server URL. Diverged local history is rejected.
Opening a missing server repository returns an error. Deleting a hosted repository does not delete its local replicas, and reopening a replica cannot recreate a deleted hosted repository.
Official host: lixray.com
LixRay is the official Lix host. Copy the immutable Lix connection URL and pass it to openLix():
import { openLix } from "@lix-js/sdk";
const lix = await openLix({
server: {
url: "https://lixray.com/lix/01936f4e-7b6c-7c3d-8f9a-123456789abc",
headers: async () => ({
Authorization: `Bearer ${await getAccessToken()}`,
}),
},
});
The connection URL is an absolute HTTPS URL whose path is exactly /lix/{uuid}. HTTP is accepted only for loopback development. It carries no query, fragment, credentials, or deployment-path prefix. Human-readable namespace and project URLs are separate web-page addresses, not Lix connection URLs.
Files, SQL, branches, history, and observe() work the same way they do locally. See Collaboration.
Host it yourself
Companies that must keep repositories on their own infrastructure can run their own host. There are three interoperable approaches:
- Deploy the ready-made Lix reference server behind your authentication gateway.
- Embed the
lixcrate's protocol handler in a custom Rust host. - Implement the documented Lix Server Protocol independently in another language or architecture.
The reference server is a supported example, not the protocol authority. Every compatible implementation exposes the same wire contract to clients.
Deploy the reference server
The reference server uses SlateDB and S3-compatible object storage and is published as ghcr.io/opral/lix-server. It provides runtime pooling, caching, stream leases, timeouts, and graceful recovery. Authentication, authorization, and resource-existence policy remain the responsibility of a trusted gateway. See its README for configuration and security requirements.
Embed the Rust handler
Enable the feature:
[dependencies]
lix = { version = "0.11", features = ["server-protocol"] }
Open a repository and keep one LixServerProtocol for as long as it is hosted:
use lix::server_protocol::{
ServerProtocolContext, ServerProtocolPrincipal,
};
let protocol = lix::open_lix()
.with_storage(storage)
.serve()
.with_lix_id("01936f4e-7b6c-7c3d-8f9a-123456789abc")
.await?;
// Your host authenticates first, then calls the protocol.
let context = ServerProtocolContext {
principal: ServerProtocolPrincipal::Authenticated {
account_id: account_id.to_owned(),
idempotency_scope: "identity-provider:user-123".to_owned(),
},
durable_terminal_storage_notifier: None,
};
let response = protocol.handle(request, context).await;
with_lix_id binds the host's stable resource UUID. It can differ from the portable identity stored inside a restored snapshot. The protocol validates that every request targets this bound UUID.
request is a ServerProtocolRequest, which is http::Request<ServerProtocolBody>. The response is http::Response<ServerProtocolBody>. Converting your framework's body type into ServerProtocolBody is the only adapter code you write.
Your host is responsible for three things:
- Authenticate the request and choose a principal. The protocol does not
read tokens, cookies, or certificates. Never derive an
account_idfrom an unverified header. - Resolve the repository identified by
{lix_id}without creating an unknown target. Pass the complete root/lix/v1/{lix_id}/...request to the protocol; it validates the immutable ID before dispatch. If your product is mounted below a deployment prefix, strip that prefix at the reverse-proxy boundary before dispatch. SDK connection locators themselves never contain a deployment prefix. - Forward the request and the response. Preserve protocol status codes, headers, and body bytes. Keep the Lix runtime alive until every streaming response body closes, including server-sent events (SSE) and snapshot downloads.
Use ServerProtocolPrincipal::Anonymous only where you deliberately allow anonymous access. Reject bad credentials with 401 before dispatch. Call LixServerProtocol::close() on shutdown.
Clients connect exactly as they do to lixray.com. Only the URL changes.
The SDK accepts https://host/lix/{lix_id}, derives the versioned API URL, opens a session, and reconnects observation streams on its own. HTTPS is required except for HTTP loopback addresses used in local development.
For the wire format, session behavior, and the OpenAPI document, see Lix Server Protocol.
Storage on your own host
The host chooses where bytes live. SlateDB stores a repository on S3-compatible object storage; RocksDB stores it on a local disk. Clients never configure this:
JS client ── HTTP ──▶ your Lix server ──▶ SlateDB ──▶ S3
See Storage.