WitDatabase runs wherever .NET 10 runs, and it runs the same way, because it is managed code with nothing native to deploy beside it. Windows, Linux, macOS, containers, mobile through MAUI: the same package, the same behaviour, no platform-specific build to pick.

The browser is the interesting case, and it is interesting because of how it works rather than because it needed special treatment.

Where pages go

Everything the engine writes goes through IStorage, which reads and writes fixed-size pages and knows nothing about what is in them.

Key What it is
file A file on disk. The default
memory Nothing durable. Behind Data Source=:memory:
encrypted A wrapper over another backend, encrypting on the way past
indexeddb The browser's IndexedDB, from OutWit.Database.Core.IndexedDb

Browser support is that last row. It is one implementation of one interface, sitting where the file backend usually sits, with the storage engine, the SQL, the transactions and the indexes above it unchanged. Putting the database in S3 or on a network volume is the same shape of work. See architecture.

Desktop, server, mobile

Nothing to configure. A .witdb file written on Windows opens on Linux and on a phone, byte for byte, and MAUI applications use the same file backend as everything else.

Two ordinary platform manners still apply. Use the per-application directory the platform gives you rather than a path you composed yourself, and remember that iOS and Android will suspend your process, so a database with unflushed work wants a flush when the application goes to the background.

Blazor WebAssembly

The state of this is worth reading before you plan around it. A database can be built, read and written in the browser through the key-value API. It cannot yet be written through SQL there: every statement runs inside an implicit transaction, the commit flushes, and the flush writes the database header through the synchronous storage path, which the browser does not have. CREATE TABLE succeeds because it writes nothing; the first INSERT throws.

ExecuteNonQueryAsync does not help, since it runs the synchronous path on a thread-pool thread and a browser has no thread pool. Closing an asynchronous statement path is what this needs, and until it exists, treat SQL in the browser as unfinished and use the Core API instead.

The rest of what follows applies to the key-value path, and comes from two facts about the browser: no file system, and one thread.

Storage is IndexedDB. Install the package and reference its scripts:

xml
<PackageReference Include="OutWit.Database.Core.IndexedDb" />
html
<script src="_content/OutWit.Database.Core.IndexedDb/witdb-indexeddb.js"></script>
<script src="_content/OutWit.Database.Core.IndexedDb/witdb-indexeddb-index.js"></script>

Everything is async. WebAssembly is single-threaded, so a synchronous read would block the one thread the browser has, and the engine refuses rather than pretending. Synchronous methods on this backend throw PlatformNotSupportedException, the database is built through BuildAsync, and it is closed through DisposeAsync.

csharp
@inject IJSRuntime JSRuntime

@code {
    private WitDatabase? m_db;

    protected override async Task OnInitializedAsync()
    {
        m_db = await new WitDatabaseBuilder()
            .WithIndexedDbStorage("MyAppDatabase", JSRuntime)
            .WithBTree()
            .WithTransactions()
            .BuildAsync();
    }

    private async Task Save(string key, byte[] value) =>
        await m_db!.PutAsync(Encoding.UTF8.GetBytes(key), value);
}

Use ChaCha20 rather than AES. There is no AES-NI in the browser, so AES falls back to software and is slow enough to notice. OutWit.Database.Core.BouncyCastle brings ChaCha20-Poly1305, which was designed for exactly this:

csharp
m_db = await new WitDatabaseBuilder()
    .WithIndexedDbStorage("SecureApp", JSRuntime)
    .WithBTree()
    .WithBouncyCastleEncryption(userPassword)
    .WithTransactions()
    .BuildAsync();

Calling WithBouncyCastleEncryption is enough to load the package, since calling an extension method on a type loads the assembly it lives in. Selecting the algorithm through a connection string is not: nothing has touched the assembly, so its module initializer has never run and the provider is not registered. Call BouncyCastleProviderRegistration.EnsureRegistered() once at startup if you go that way. The same applies to any provider a third party registers from a module initializer.

Key derivation is deliberately slow, and in the browser you feel it. A database created with a lower iteration count opens faster and still opens anywhere, since the count travels in the file. See encryption.

The LSM store is unavailable, since it wants a directory of files. Use the B+Tree store, which is the default anyway.

Keep the cache small. A few hundred pages is a reasonable start. Browser memory is not your machine's memory.

How much you can store

Browsers decide this themselves and change their minds between versions. Chrome and Edge allow a large fraction of free disk space, Firefox something similar, Safari less and with prompting.

What matters more than the number is that browser storage can be evicted. A user clearing site data takes the database with it, and browsers reclaim storage from sites the user has not visited in a while. Data your application cannot afford to lose belongs on a server, with the browser database holding the working copy.

Feature differences

Desktop, server, mobile Blazor WebAssembly
Key-value API Yes Yes
SQL: reading Yes Yes
SQL: writing Yes Not yet
B+Tree store Yes Yes
LSM store Yes No
MVCC and transactions Yes Yes, under the key-value API
Secondary indexes Yes Yes
AES-GCM Fast Slow, software only
ChaCha20-Poly1305 Yes Yes, and preferred
Synchronous API Yes No

Where to go next