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:
<PackageReference Include="OutWit.Database.Core.IndexedDb" /><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.
@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:
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
- Architecture,
IStorageand writing your own - Encryption, choosing an algorithm and an iteration count
- Installation, the packages
- Storage engines, what sits above the backend