SkillAgentSearch skills...

DexieNET

DexieNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB

Install / Use

/learn @b-straub/DexieNET
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

DexieNET

DexieCloudNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB see https://dexie.org with cloud support see https://dexie.org/cloud/ .

'DexieNET' used with permission of David Fahlander

and made with <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/Rider.png" alt="Rider logo." style="width:100px;"> Free edition !

Known Issues

  • please make sure that the latest .NET SDK is installed and used, otherwise you will get an error for Microsoft.CodeAnalysis.Analyzers 4.14.0
  • Members and Invites table is not working in small mode, investigating ...

News

  • Starting with revision 1.5, R3 is used as reactive core. Please refer to the samples for usage.
  • Added experimental support for 'share' via a magic link. See the updates in the ToDoSample. Unfortunately, the basic idea of using this for an iOS/iPadOS shortcut doesn't work as expected. The shortcut scheme "webapp://" does not handle query parameters. On macOS using the normal "https://" scheme it's working as expected!
  • You can find the experimental shortcut here AddToDO
  • Added Declarative Web Push support. Please check the Explanation and PushServer ReadMe
    • Declarative WebPush is only supported for iOS and iPadOS >= 18.4 and macOS >= 15.5
    • Especially on iOS < 18.4 clicking on notifications currently does not work reliable notificationclick events in serviceworkers not firing
    • Clicking on notifications currently does not work reliably for Chrome on MacOS >= 15 in addition to several other problems, use Safari for MacOS desktop PWA instead.
  • Released DexieCloud
  • Please register with DexieCloud and test the ToDoSample. The configuration script can be found here configure-app.ps1 (Windows) or here configure-app.sh (Nix - jq required).
  • Published a new helper library RxBlazorLight

Basic

DexieNET aims to be a feature complete .NET wrapper for Dexie.js the famous Javascript IndexedDB wrapper from David Fahlander including support for cloud sync.

It consists of two parts, a source generator converting a C# record, class, struct to a DB store and a set of wrappers around the well known Dexie.js API constructs such as Table, WhereClause, Collection, ...

It's designed to work within a Blazor Webassembly application with minimal effort.

Hello World

  • create a Blazor WebAssembly
  • Add the DexieNET Nuget
  • Add the HelloWorld Component and update the index

Program.cs

using DexieNET;
using YourNamspace.Pages;
....

builder.Services.AddDexieNET<FriendsDB>();

HelloWorld.razor

@page "/helloWorld"
@using DexieNET.Component
@inherits DexieNET<FriendsDB>

Friends:
@if (_friends is null)
{
    <p>Loading...</p>
}
else if (_friends.Count() == 0)
{
    <p>No items...</p>
}
else
{
    <ul style="list-style: square inside;">
        @foreach (var friend in _friends)
        {
            <li>
                Name: @friend.Name, Age: @friend.Age
            </li>
        }
    </ul>
}

<hr />

Logs:
@if (_logs is null)
{
    <p>Loading...</p>
}
else if (_logs.Count() == 0)
{
    <p>No items...</p>
}
else
{
    <ul style="list-style: square inside;">
        @foreach (var logEntry in _logs)
        {
            <li>
                Message: @logEntry.Message, TimeStamp: @logEntry.TimeStamp.ToLongTimeString();
            </li>
        }
    </ul>
}

<hr />

<div style="display: flex; column-gap: 50px">
    <button class="btn btn-primary" style="flex: 0 1 auto" @onclick="PopulateDatabase">
        PopulateDatabase
    </button>

    <button class="btn btn-secondary" style="flex: 0 1 auto" @onclick="GoodTransaction">
        GoodTransaction
    </button>

    <button class="btn btn-secondary" style="flex: 0 1 auto" @onclick="FailedTransaction">
        FailedTransaction
    </button>

    <button class="btn btn-secondary" style="flex: 0 1 auto" @onclick="ClearDatabase">
        ClearDatabase
    </button>
</div>

HelloWorld.razor.cs

using DexieNET;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using System.Xml.Linq;

namespace DexieNETHelloWorld.Pages
{
    public interface IFriendsDB : IDBStore { };

    public partial record Friend
    (
        [property: Index] string Name,
        [property: Index] int Age
    ) : IFriendsDB;

    public partial record LogEntry
    (
        [property: Index] string? Message,
        [property: Index] DateTime TimeStamp
    ) : IFriendsDB;

    public partial class HelloWorld
    {
        private IEnumerable<Friend>? _friends;
        private IEnumerable<LogEntry>? _logs;

        protected override async Task OnInitializedAsync()
        {
            await base.OnInitializedAsync();
            await Dexie.Version(1).Stores();
            await FillTables();
        }

        private async Task FillTables()
        {
            _friends = await Dexie.Friends().ToArray();
            _logs = await Dexie.LogEntries().OrderBy(l => l.TimeStamp).Reverse().ToArray();
            await InvokeAsync(StateHasChanged);
        }

        private async Task LogMessage(string? message)
        {
            await Dexie.Transaction(async _ =>
            {
                await Dexie.LogEntries().Add(new LogEntry(message, DateTime.Now));
            }, TAType.TopLevel);
        }
        private async Task ClearDatabase()
        {
            await Dexie.Friends().Clear();
            await Dexie.LogEntries().Clear();

            await FillTables();
        }

        private async Task PopulateDatabase()
        {
            await LogMessage("PopulateDatabase");

            Random rand = new();
            await Dexie.Friends().Add(new Friend("Jane Doe", rand.Next(1, 99)));
            await Dexie.Friends().Add(new Friend("John Doe", rand.Next(1, 99)));

            await FillTables();
        }

        private async Task GoodTransaction()
        {
            await LogMessage("GoodTransaction");

            await Dexie.Transaction(async ta =>
            {
                Random rand = new();
                var key = await Dexie.Friends().Add(new Friend("Luke", rand.Next(1, 99)));
                var friend = await Dexie.Friends().Get(key);

                if (friend?.Name == "Luke" || ta.Collecting)
                // ta.Collecting, this means the first pass of the transaction, in which the table names are collected
                // if a second table is hidden behind a conditional statement, it must also be visited in the first pass
                {
                    await Dexie.Friends().Add(new Friend("John", rand.Next(1, 99)));
                    await Dexie.LogEntries().Add(new LogEntry("TA executed", DateTime.Now));
                }
            });

            await FillTables();
        }

        private async Task FailedTransaction()
        {
            await LogMessage("ProvokeFail");

            try
            {
                await Dexie.Transaction(async ta =>
                {
                    await Dexie.Friends().Clear();
                    var key = await Dexie.Friends().Add(new Friend("Test", 33));
                    var friend = await Dexie.Friends().Get(key);

                    if (friend?.Name == "Test" || ta.Collecting)
                    {
                        await LogMessage("TA will fail");
                    }
                    await Dexie.Friends().Add(friend); // this will fail
                });
            }
            catch (Exception ex)
            {
                var firstDot = ex.Message.IndexOf('.');
                var message = firstDot <= 0 ? ex.Message : ex.Message[..firstDot];
                await LogMessage($"TA failed: {message}");
            }

            await FillTables();
        }
    }
}

Advanced

Naming

  • the Source Generator will create the following classes from an IDBStore derived class, struct, record:

    • PIndentifier -> Plural of Identifier provided by Humanizer.Core (English only), be aware the plural form might not be always obvious e.g. Person -> People
    • service: PIndentifierDB
    • table: PIndentifier
    // Record
    public partial record Friend
    (
        [property: Index] string Name,
        [property: Index] int Age
    ) : IDBStore;
    
    ......
    // Service
    builder.Services.AddDexieNET<FriendsDB>();
    ......
    [Inject]
    public IDexieNETService<FriendsDB>? DB { get; set; }
    
    ......
    // Table
    var table = await DB.Friends();
    
  • You can have multiple stores in one database

    [DBName("TestDB")] // optional -> default name = interface name without leading 'I' -> PersonsDB
    public interface IPersonsDB : IDBStore
    {
    }
    
    // Records
    [CompoundIndex("FirstName", "LastName")]
    public partial record Person
    (
        [property: Index] string FirstName,
        [property: Index] string LastName,
        Guid? AddressKey
    ) : IPersonsDB;
    
    [CompoundIndex("City", "Street")]
    [CompoundIndex("Zip", "Street")]
    public partial record Address
    (
        [property: Index] string Street,
        [property: Index] string Housenumber,
        [property: Index] string City,
        [property: Index] string ZIP,
        [property: Index] string Country
    
    ) : IPersonsDB;
    
    ......
    // Service
    using DexieNET;
    .......
    builder.Services.AddDexieNET<TestDB>();
    
    // Component
    [Inject]
    public IDexieNETSe
    
View on GitHub
GitHub Stars50
CategoryDevelopment
Updated2mo ago
Forks5

Languages

C#

Security Score

95/100

Audited on Jan 5, 2026

No findings