lix 0.18.0

Embeddable version control for apps and AI agents.
Documentation
use lix::{LixError, Value};

simulation_test!(
    internal_storage_relations_are_not_public,
    |sim| async move {
        let engine = sim.boot_engine().await;
        let session = sim.wrap_session(
            engine.open_session().await.expect("session should open"),
            &engine,
        );

        for surface_name in [
            "lix_state",
            "lix_state_by_branch",
            "lix_state_history",
            "lix_label",
            "lix_label_by_branch",
            "lix_label_history",
            "lix_label_assignment",
            "lix_label_assignment_by_branch",
            "lix_label_assignment_history",
            "lix_binary_blob_ref",
            "lix_binary_blob_ref_by_branch",
            "lix_binary_blob_ref_history",
            "lix_branch_descriptor",
            "lix_branch_descriptor_by_branch",
            "lix_branch_descriptor_history",
            "lix_branch_ref",
            "lix_branch_ref_by_branch",
            "lix_branch_ref_history",
            "lix_directory_descriptor",
            "lix_directory_descriptor_by_branch",
            "lix_directory_descriptor_history",
            "lix_file_descriptor",
            "lix_file_descriptor_by_branch",
            "lix_file_descriptor_history",
        ] {
            let error = session
                .execute(&format!("SELECT * FROM {surface_name}"), &[])
                .await
                .expect_err("private storage relation should not resolve");
            assert_eq!(
                error.code,
                LixError::CODE_TABLE_NOT_FOUND,
                "{surface_name}: {error:?}"
            );
        }
    }
);

#[cfg(any())]
simulation_test!(read_only_history_views_reject_dml, |sim| async move {
    let engine = sim.boot_engine().await;
    let session = sim.wrap_session(
        engine.open_session().await.expect("session should open"),
        &engine,
    );

    assert_read_only_error(
        session
            .execute(
                "INSERT INTO lix_file_history (id, path) VALUES ('68697374-6f72-892d-8669-6c6500000000', '/x.txt')",
                &[],
            )
            .await
            .expect_err("history insert should be read-only"),
        "lix_file_history",
        "History views are query-only",
    );

    assert_read_only_error(
        session
            .execute("UPDATE lix_directory_history SET name = 'renamed'", &[])
            .await
            .expect_err("history update should be read-only"),
        "lix_directory_history",
        "History views are query-only",
    );
});

#[cfg(any())]
simulation_test!(read_only_typed_history_views_reject_dml, |sim| async move {
    let engine = sim.boot_engine().await;
    let session = sim.wrap_session(
        engine.open_session().await.expect("session should open"),
        &engine,
    );

    session
        .execute(
            "INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
             VALUES (\
             CAST('{\"$schema\":\"https://lix.dev/schema-v1.json\",\"key\":\"read_only_history_row\",\"columns\":[{\"name\":\"id\",\"type\":\"text\",\"nullable\":false}],\"primary_key\":[\"id\"]}' AS JSONB),\
             false,\
             true\
             )",
            &[],
        )
        .await
        .expect("registered schema insert should succeed");

    assert_read_only_error(
        session
            .execute(
                "INSERT INTO read_only_history_row_history (id) VALUES ('row-a')",
                &[],
            )
            .await
            .expect_err("typed history insert should be read-only"),
        "read_only_history_row_history",
        "History views are query-only",
    );
});

simulation_test!(
    cached_read_syntax_uses_a_fresh_storage_snapshot,
    |sim| async move {
        let engine = sim.boot_engine().await;
        let reader = sim.wrap_session(
            engine
                .open_session()
                .await
                .expect("reader session should open"),
            &engine,
        );
        let writer = sim.wrap_session(
            engine
                .open_session()
                .await
                .expect("writer session should open"),
            &engine,
        );
        let select_sql = "SELECT content FROM lix_file WHERE id = $1";
        let params = [Value::Text(
            "66726573-682d-836e-8170-73686f742d00".to_string(),
        )];

        let before = reader
            .execute(select_sql, &params)
            .await
            .expect("initial cached-syntax read should succeed");
        assert_eq!(before.len(), 0);

        writer
            .execute(
                "INSERT INTO lix_file (id, path, content) VALUES ($1, $2, $3)",
                &[
                    Value::Text("66726573-682d-836e-8170-73686f742d00".to_string()),
                    Value::Text("/fresh-snapshot.txt".to_string()),
                    Value::Blob(b"fresh".to_vec().into()),
                ],
            )
            .await
            .expect("intervening write should commit");

        let after = reader
            .execute(select_sql, &params)
            .await
            .expect("repeated syntax should use a fresh provider snapshot");
        assert_eq!(
            after.rows()[0].values(),
            &[Value::Blob(b"fresh".to_vec().into())]
        );
    }
);