Skip to content

Add BLite.Wasm: OPFS & IndexedDB storage backends for browser WASM (Issues 1-4)#63

Merged
mrdevrobot merged 2 commits intomainfrom
copilot/implementations-defined-in-wasm-support
Apr 17, 2026
Merged

Add BLite.Wasm: OPFS & IndexedDB storage backends for browser WASM (Issues 1-4)#63
mrdevrobot merged 2 commits intomainfrom
copilot/implementations-defined-in-wasm-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 16, 2026

Summary

Implements Issues 1-4 from WASM_SUPPORT.md, delivering a complete BLite.Wasm NuGet package that enables BLite to run with persistent storage in browser WASM environments.

New BLite.Wasm Package

Project: src/BLite.Wasm/BLite.Wasm.csproj — targets net10.0-browser

Storage Backends

Backend Class Best For
OPFS OpfsPageStorage High-throughput Worker contexts (Chrome 102+, Firefox 111+, Safari 15.2+)
IndexedDB IndexedDbPageStorage Universal compatibility, main-thread Blazor WASM
In-Memory MemoryPageStorage (existing) Tests, ephemeral caches

WAL Implementations

Backend Class Description
OPFS OpfsWriteAheadLog Crash recovery via dedicated .wal OPFS file
IndexedDB IndexedDbWriteAheadLog Crash recovery via IDB object store

Factory API

// Auto-selects best backend (OPFS → IndexedDB → InMemory):
var engine = await BLiteWasm.CreateAsync("mydb");

// Explicit backend selection:
var engine = await BLiteWasm.CreateAsync("mydb", WasmStorageBackend.IndexedDb);

// Blazor DI registration:
builder.Services.AddBLiteWasm("mydb");

Changes to BLite.Core

  • Added BLiteEngine.CreateFromStorage(StorageEngine, BLiteKvOptions?) public factory method for external projects to create engines from custom storage backends.

Architecture

  • OPFS backend uses [JSImport] with synchronous Span<byte> marshalling for high-perf page I/O via FileSystemSyncAccessHandle
  • IndexedDB backend uses [JSImport] with base64 string encoding for async JS interop (required because [JSImport] does not support byte[] on Task-returning methods)
  • Both backends implement the existing IPageStorage / IWriteAheadLog interfaces, plugging directly into the StorageEngine with zero changes to the core engine

Files Added

File Description
src/BLite.Wasm/BLite.Wasm.csproj Package project
src/BLite.Wasm/BLiteWasm.cs Factory with auto-detection
src/BLite.Wasm/BLiteWasmServiceExtensions.cs Blazor DI extension
src/BLite.Wasm/Storage/OpfsPageStorage.cs OPFS page storage
src/BLite.Wasm/Storage/IndexedDbPageStorage.cs IndexedDB page storage
src/BLite.Wasm/Transactions/OpfsWriteAheadLog.cs OPFS WAL
src/BLite.Wasm/Transactions/IndexedDbWriteAheadLog.cs IndexedDB WAL
src/BLite.Wasm/Interop/OpfsInterop.cs JS interop bridge (OPFS)
src/BLite.Wasm/Interop/IndexedDbInterop.cs JS interop bridge (IDB)
src/BLite.Wasm/wwwroot/blite-opfs.mjs JavaScript OPFS module
src/BLite.Wasm/wwwroot/blite-indexeddb.mjs JavaScript IndexedDB module

Testing

  • All 2024 existing tests pass (6 pre-existing skips)
  • Added CreateFromStorage_InsertAndFind_Works test for the new factory method
  • BLite.Wasm compiles cleanly with zero warnings
  • Browser-level integration testing requires a Blazor WASM test harness (Issue 5)

Remaining Work

Issue 5 (Blazor WASM sample + docs) is the only remaining item from WASM_SUPPORT.md.

Copilot AI and others added 2 commits April 16, 2026 23:17
…ry API

Implements Issues 1-4 from WASM_SUPPORT.md:
- OpfsPageStorage: OPFS SyncAccessHandle-based page I/O via JSImport
- IndexedDbPageStorage: IndexedDB async page I/O with base64 marshalling
- OpfsWriteAheadLog: OPFS-backed WAL for crash recovery
- IndexedDbWriteAheadLog: IndexedDB-backed WAL for crash recovery
- BLiteWasm factory: auto-selects OPFS → IndexedDB → InMemory
- AddBLiteWasm Blazor DI extension
- BLiteEngine.CreateFromStorage public factory method
- JavaScript interop modules (blite-opfs.mjs, blite-indexeddb.mjs)
- Test for CreateFromStorage in InMemoryStorageTests

Agent-Logs-Url: https://github.com/EntglDb/BLite/sessions/d18510cd-be4e-47fe-a1ef-788758aafe5a

Co-authored-by: mrdevrobot <12503462+mrdevrobot@users.noreply.github.com>
@mrdevrobot mrdevrobot marked this pull request as ready for review April 17, 2026 08:00
Copilot AI review requested due to automatic review settings April 17, 2026 08:00
@mrdevrobot mrdevrobot merged commit 830e83e into main Apr 17, 2026
1 check passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new BLite.Wasm package intended to enable BLite persistence in browser WASM via OPFS and IndexedDB backends, and exposes a core factory (BLiteEngine.CreateFromStorage) to allow external storage engines to plug into BLite.

Changes:

  • Introduces BLite.Wasm project (net10.0-browser) with OPFS/IndexedDB IPageStorage + IWriteAheadLog implementations and a BLiteWasm.CreateAsync factory.
  • Adds JS interop modules (blite-opfs.mjs, blite-indexeddb.mjs) and corresponding [JSImport] bridges.
  • Adds BLiteEngine.CreateFromStorage(...) plus a small integration test.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
tests/BLite.Tests/InMemoryStorageTests.cs Adds integration test for BLiteEngine.CreateFromStorage.
src/BLite.Wasm/wwwroot/blite-opfs.mjs OPFS JS module: open/read/write/flush/truncate/close + availability probe.
src/BLite.Wasm/wwwroot/blite-indexeddb.mjs IndexedDB JS module: page storage, meta, and WAL support.
src/BLite.Wasm/Transactions/OpfsWriteAheadLog.cs OPFS-backed WAL implementation.
src/BLite.Wasm/Transactions/IndexedDbWriteAheadLog.cs IndexedDB-backed WAL implementation.
src/BLite.Wasm/Storage/OpfsPageStorage.cs OPFS page storage implementation.
src/BLite.Wasm/Storage/IndexedDbPageStorage.cs IndexedDB page storage implementation.
src/BLite.Wasm/Interop/OpfsInterop.cs [JSImport] bridge for OPFS module.
src/BLite.Wasm/Interop/IndexedDbInterop.cs [JSImport] bridge for IndexedDB module.
src/BLite.Wasm/BLiteWasmServiceExtensions.cs Blazor DI registration helper (AddBLiteWasm).
src/BLite.Wasm/BLiteWasm.cs WASM factory (CreateAsync) + backend selection logic.
src/BLite.Wasm/BLite.Wasm.csproj New package project definition.
src/BLite.Core/BLiteEngine.cs Adds CreateFromStorage(StorageEngine, BLiteKvOptions?) factory method.
WASM_SUPPORT.md Updates roadmap to mark Issues 1–4 as implemented and documents new APIs.
BLite.slnx Adds BLite.Wasm to the solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +46
/// <param name="dbName">Logical database name. The WAL file will be <c>{dbName}.wal</c>.</param>
/// <param name="writeTimeoutMs">Timeout in milliseconds for acquiring the internal lock.</param>
public OpfsWriteAheadLog(string dbName, int writeTimeoutMs = 5_000)
{
if (string.IsNullOrWhiteSpace(dbName))
throw new ArgumentException("Database name must not be null or empty.", nameof(dbName));

_dbName = dbName + ".wal";
_writeTimeoutMs = writeTimeoutMs;
public uint NextPageId => _nextPageId;

/// <summary>Returns <c>true</c> if IndexedDB is available in the current browser context.</summary>
public static bool IsAvailable() => IndexedDbInterop.IsAvailable();
Comment on lines +56 to +58
// Open with pageSize=1 — we manage our own offsets.
await OpfsInterop.OpenAsync(_dbName, 1);
_opened = true;
Comment on lines +15 to +26
internal static partial class IndexedDbInterop
{
private const string ModuleName = "./blite-indexeddb.mjs";

// ─── Page storage ────────────────────────────────────────────────────────

[JSImport("idbOpen", ModuleName)]
internal static partial Task<double> OpenAsync(string dbName);

[JSImport("idbReadPage", ModuleName)]
internal static partial Task<string> ReadPageAsync(string dbName, int pageId, int pageSize);

Comment on lines +170 to +194
public uint AllocatePage()
{
ThrowIfDisposed();
ThrowIfNotOpened();

lock (_allocationLock)
{
if (_freeList.Count > 0)
return _freeList.Pop();

return _nextPageId++;
}
}

/// <inheritdoc/>
public void FreePage(uint pageId)
{
ThrowIfDisposed();
if (pageId == 0)
throw new InvalidOperationException("Cannot free the header page (page 0).");

lock (_allocationLock)
{
_freeList.Push(pageId);
}
public uint NextPageId => _nextPageId;

/// <summary>Returns <c>true</c> if the OPFS SyncAccessHandle API is available in the current browser context.</summary>
public static bool IsAvailable() => OpfsInterop.IsAvailable();
Comment on lines +232 to +233
var buffer = new byte[fileSize];
OpfsInterop.ReadPage(_dbName, 0, buffer.AsSpan());
Comment on lines +103 to +108
* @returns {boolean}
*/
export function opfsIsAvailable() {
return typeof navigator !== "undefined"
&& typeof navigator.storage !== "undefined"
&& typeof navigator.storage.getDirectory === "function";
Comment on lines +1 to +21
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0-browser</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

<PackageId>BLite.Wasm</PackageId>
<Version>4.3.1</Version>
<Authors>BLite Team</Authors>
<Description>BLite browser storage backends for .NET WASM — OPFS and IndexedDB page storage and WAL implementations</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://github.com/EntglDb/BLite</RepositoryUrl>
<PackageTags>database;embedded;bson;nosql;wasm;blazor;browser;opfs;indexeddb</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
Comment on lines +159 to +187
public uint AllocatePage()
{
ThrowIfDisposed();
ThrowIfNotOpened();

lock (_allocationLock)
{
if (_freeList.Count > 0)
return _freeList.Pop();

var id = _nextPageId++;
// Persist the counter so it survives browser restarts.
IndexedDbInterop.SaveNextPageIdAsync(_dbName, (int)_nextPageId).GetAwaiter().GetResult();
return id;
}
}

/// <inheritdoc/>
public void FreePage(uint pageId)
{
ThrowIfDisposed();
if (pageId == 0)
throw new InvalidOperationException("Cannot free the header page (page 0).");

lock (_allocationLock)
{
_freeList.Push(pageId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants