wa-sqlite.BlazorWasmSqlite 1.0.1

dotnet add package wa-sqlite.BlazorWasmSqlite --version 1.0.1
                    
NuGet\Install-Package wa-sqlite.BlazorWasmSqlite -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="wa-sqlite.BlazorWasmSqlite" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="wa-sqlite.BlazorWasmSqlite" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="wa-sqlite.BlazorWasmSqlite" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add wa-sqlite.BlazorWasmSqlite --version 1.0.1
                    
#r "nuget: wa-sqlite.BlazorWasmSqlite, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package wa-sqlite.BlazorWasmSqlite@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=wa-sqlite.BlazorWasmSqlite&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=wa-sqlite.BlazorWasmSqlite&version=1.0.1
                    
Install as a Cake Tool

wa-sqlite.BlazorWasmSqlite

<img src="wa-sqlite.BlazorWasmSqliteIcon.png" alt="wa-sqlite.BlazorWasmSqlite logo" width="96" height="96" align="left" style="margin-right:16px; margin-bottom:8px"/>

NuGet NuGet Downloads Publish License: MIT .NET 8 Tests

A Blazor WebAssembly Razor Class Library that provides a SQLite database backed by IndexedDB, running off the main thread in a Web Worker. Built on @journeyapps/wa-sqlite with Dapper support.


Features

  • Off-main-thread execution — SQLite runs in a dedicated Web Worker; UI never blocks
  • IndexedDB persistence — data survives page reloads via IDBBatchAtomicVFS
  • Dapper integrationSqliteWasmConnection implements IDbConnection; use Dapper as normal
  • High-performance bulk ops — zero-deserialization upsert/insert via SqliteWorkerPayloadBuilder
  • JSPI / Asyncify auto-detection — JSPI on Chrome 137+ / Edge 137+; automatic Asyncify fallback for Safari and Firefox
  • Database utilities — check existence and delete IndexedDB databases without external state flags

Browser Support

Browser Build Minimum Version
Chrome JSPI 137+
Edge JSPI 137+
Safari Asyncify (fallback) 14+ (indexedDB.databases())
Firefox Asyncify (fallback) 72+ (indexedDB.databases())

On browsers without JSPI, the library automatically loads the Asyncify build and logs a warning to the Worker console:

[wa-sqlite] JSPI not supported in this browser — falling back to Asyncify build. Safari and older browsers only.

Note: Safari JSPI support is tracked in Interop 2026. When it ships, the library will pick it up automatically with no code changes required.


Setup

1. Register services

In Program.cs:

builder.Services.AddSqliteWasmInterop();

This registers Dapper type handlers for bool, DateTime, and Guid SQLite mappings.

2. Initialize the JS module

Call once at app startup (e.g. Program.cs after builder.Build()):

await SqliteJsInterop.InitializeAsync();
await SqliteJsInterop.InitWorkerAsync();

Usage

Connection

SqliteWasmConnection is the primary API. It implements IDbConnection and is designed to be long-lived — open once and reuse across operations rather than opening per-query.

// Recommended: persistent connection field in a Blazor component or service
private SqliteWasmConnection? _conn;

private async Task<SqliteWasmConnection> GetConnectionAsync()
{
    if (_conn == null || _conn.State != ConnectionState.Open)
    {
        _conn = new SqliteWasmConnection("MyApp", "MyFile");
        await _conn.OpenAsync();
    }
    return _conn;
}

Then use Dapper as normal:

var conn = await GetConnectionAsync();

var users = await conn.QueryAsync<User>("SELECT * FROM Users WHERE IsDeleted = 0");

await conn.ExecuteAsync(
    "INSERT INTO Users (Id, Name) VALUES (@Id, @Name)",
    new { Id = Guid.NewGuid().ToString(), Name = "Jane" });

var count = await conn.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM Users");

Transactions

Pass transaction: txn to each Dapper operation so it is enlisted in the transaction. Use await using for automatic rollback if the block exits without a commit.

await using var txn = await conn.BeginTransactionAsync();
try
{
    await conn.ExecuteAsync(
        "INSERT INTO Users (Id, Name) VALUES (@Id, @Name)",
        new { Id = Guid.NewGuid().ToString(), Name = "Alice" },
        transaction: txn);

    await conn.ExecuteAsync(
        "INSERT INTO Users (Id, Name) VALUES (@Id, @Name)",
        new { Id = Guid.NewGuid().ToString(), Name = "Bob" },
        transaction: txn);

    await txn.CommitAsync();
}
catch
{
    await txn.RollbackAsync();
    throw;
}
// DisposeAsync also rolls back automatically if CommitAsync was never reached

Upsert Extension

Convenience wrapper over the raw bulk path for moderate record counts:

// Uses INSERT ... ON CONFLICT(Id) DO UPDATE SET
var changes = await conn.UpsertAsync<User>("Users", records, primaryKey: "Id");

Bulk Operations

For large datasets (thousands of rows), use the raw Worker payload path directly — it eliminates per-row JSON overhead.

Bulk Insert (initial seed)

INSERT OR REPLACE — fastest path for seeding into empty tables. Does not preserve existing row data on conflict.

var payload = SqliteWorkerPayloadBuilder.BuildRawPayload("Users", rows, "Id");
using var result = await SqliteJsInterop.BulkInsertRawAsync(conn.ConnectionHandle, payload);
var changes = (int)result.GetPropertyAsDouble("totalChanges");

Bulk Upsert (incremental sync)

INSERT ... ON CONFLICT DO UPDATE SET — preserves existing row data for non-conflicting columns.

var payload = SqliteWorkerPayloadBuilder.BuildUpsertPayload("Users", rows, "Id");
using var result = await SqliteJsInterop.BulkInsertRawUpsertAsync(conn.ConnectionHandle, payload);
var changes = (int)result.GetPropertyAsDouble("totalChanges");

Use GetPropertyAsDouble("totalChanges")GetPropertyAsInt32 silently returns 0 for float64 values returned by the Worker.


Schema

Create tables and views

// Create a table from a C# model ([Table] / [SqliteColumn] attributes)
await SqliteTableCreator.CreateTableAsync<User>(conn);

// Create a view
await SqliteViewCreator.CreateViewAsync(conn, "ActiveUsers",
    "SELECT * FROM Users WHERE IsDeleted = 0");

Inspect schema

// All tables and views in the database
var tables = await conn.QueryAllTablesAsync();

// Column metadata for a specific table
var columns = await conn.QueryTableSchemaAsync("Users");
// SqliteColumnInfo: Name, Type, NotNull, Pk, Dflt_value

Database Utilities

Check if a database exists

Useful for detecting first-run or migration state without relying on LocalStorage.

bool exists = await SqliteJsInterop.CheckDatabaseExistsAsync("MyFile");

Delete a database

The connection must be closed before deletion — otherwise the IndexedDB lock blocks the operation.

await conn.CloseAsync();
await SqliteJsInterop.DeleteDatabaseAsync("MyFile");
// Navigate or reload after deletion

Architecture

Blazor WASM (main thread)
    └── SqliteWasmConnection / SqliteJsInterop   [C# / [JSImport]]
            └── sqlite-interop.js                [main thread bridge]
                    └── sqlite-worker.js         [Web Worker]
                            └── IDBBatchAtomicVFS → IndexedDB

Key implementation details:

  • PRAGMA temp_store = MEMORY — prevents temp B-tree writes from routing through the async IDB VFS, which causes crashes on secondary VFS files
  • PRAGMA page_size = 8192 — halves IDB round-trips vs the SQLite default for wide-row workloads
  • PRAGMA cache_size = -262144 — 256 MB in-memory cache for sustained batch-atomic performance through large tables
  • Both WASM binaries ship with the package; the Worker lazy-loads only the one needed at runtime (JSPI: 1.07 MiB, Asyncify: 2.17 MiB)
  • All Worker operations are serialized through a SemaphoreSlim on the C# side to prevent concurrent await calls from delivering responses to the wrong awaiter

Build

The JS Worker and webpack bundle rebuild automatically as an MSBuild pre-build step:

NpmJS/
  src/
    sqlite-worker.js    ← Worker entry point (JSPI/Asyncify detection lives here)
    source.js           ← Main thread bridge
  webpack.config.js
  • Development: npm run dev (inline source maps)
  • Production: npm run prod (minified, external source maps)

Set SkipNpmBuild=true to suppress the npm build step (e.g. during CI restore-only passes).

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 111 6/7/2026
1.0.0 117 6/6/2026