SafeEn

A typed, checksummed, single-file database. Zero dependencies, no_std, and it runs in your browser through WebAssembly.

crates.io docs.rs

What it is

A whole database in one portable file. Every column has a declared type, every value is checked before it is stored, and every file carries a checksum so corruption is reported rather than silently loaded.

Typed schema

Columns declare a type. A value that does not fit is refused, not coerced.

Verified files

Magic number, format version and CRC-32. A damaged file is an error, never bad data.

No dependencies

The Rust crate pulls in nothing. Neither does the JavaScript package.

no_std

Needs only alloc. Runs on bare metal and in the browser.

Durable

A snapshot plus an append-only journal, so a crash loses nothing you were told was written.

Transactions

Group writes so they all happen or none do — one journal record, atomic by construction.

In Rust

use safe_en::{database, query, row};

let mut db = database! {
    name: "shop",
    orders {
        id: I64,
        customer: String,
        city: String,
        total: F64,
        coupon: String?,        // nullable
        tags: [String],         // array
        visits: I64 = 0_i64,    // default
    }
};

let orders = db.table("orders").unwrap();
orders.insert(row![1_i64, "Ahmet", "İzmir", 249.90_f64, "WELCOME", vec!["vip"], 0_i64])?;

for order in orders.get_where(query!(city == "İzmir" && total > 100.0)) {
    println!("{}", order);
}

db.save("shop.sfn")?;

Durability and transactions

use safe_en::storage::{FileStorage, SyncPolicy};
use safe_en::{fields, Durable, Filter};

let mut db = Durable::open(FileStorage::new("app.sfn"))?
    .with_sync_policy(SyncPolicy::Always);

db.transaction(|tx| {
    tx.set("accounts", &Filter::col("name").eq("ahmet"), fields! { balance: 40_i64 })?;
    tx.set("accounts", &Filter::col("name").eq("ayse"),  fields! { balance: 60_i64 })?;
    Ok(())
})?;

A transaction is written as one journal record, so the length-and-checksum framing that already wraps every record gives atomicity for free. Kill the process mid-write and the group is discarded whole.

In JavaScript

The same database compiled to WebAssembly. No bindgen, no bundler, no runtime dependency — the module has zero imports, so one build runs unchanged in the browser, Node, Deno and Bun.

npm install safe-en
import { create } from "safe-en";

const db = await create("shop");
db.createTable("orders", {
  id: "I64 primary",                 // unique and never null
  customer: "String",
  total: "F64 range(0, 100000)",     // inclusive bounds
  coupon: "String? max_len(32)",
  placed: "Timestamp",               // takes a Date, reads back as ISO-8601
});

db.table("orders").insert({ id: 1, customer: "Ahmet", total: 249.9,
                            coupon: null, placed: new Date() });

db.table("orders").select({ total: { gt: 100 } });
db.table("orders").select(c => c("coupon").isNull().or(c("total").gt(1000)));

const bytes = db.toBytes();   // the whole persistence interface

The demo is a small database browser — create tables, insert rows, edit cells, run queries, and store the result in your own browser's IndexedDB or localStorage. Everything runs locally; nothing is uploaded.

Open the demo

Where it runs

Target How
Native Rust Default features; save and load use the filesystem
Bare metal --no-default-features; verified against thumbv7em-none-eabihf
Browser WebAssembly; persist to IndexedDB or localStorage
Node / Bun The npm package, or the filesystem directly
Deno The same package; filesystem or Deno KV

Without a filesystem the whole interface is to_bytes and from_bytes. Anywhere you can keep bytes, you can keep a database.

A note on trust

On the web the stored bytes belong to the user, who can edit them. The loader never panics on malformed input — which matters most in WebAssembly, where a panic aborts the whole module with no unwinding to catch it. Every byte of a database is corrupted at every offset in the test suite, and truncated at every length.

The binary format is not a security measure. It is readable in DevTools in seconds, and the CRC-32 catches accidental corruption but is trivially recomputed, so it is not tamper-evidence. If you need that, verify an HMAC server-side; a key cannot be hidden in a WebAssembly bundle.