{"id":13629462,"url":"https://github.com/b-straub/DexieNET","last_synced_at":"2025-04-17T09:33:57.089Z","repository":{"id":65085416,"uuid":"576901903","full_name":"b-straub/DexieNET","owner":"b-straub","description":"DexieNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB","archived":false,"fork":false,"pushed_at":"2024-02-26T07:24:00.000Z","size":1018,"stargazers_count":37,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-04-10T02:47:33.255Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/b-straub.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-12-11T11:12:10.000Z","updated_at":"2024-02-24T01:39:11.000Z","dependencies_parsed_at":"2023-12-16T14:27:25.435Z","dependency_job_id":"7a30d6dc-1797-467c-863a-d41db84a19d0","html_url":"https://github.com/b-straub/DexieNET","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b-straub%2FDexieNET","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b-straub%2FDexieNET/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b-straub%2FDexieNET/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b-straub%2FDexieNET/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/b-straub","download_url":"https://codeload.github.com/b-straub/DexieNET/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751134,"owners_count":17196577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T22:01:11.331Z","updated_at":"2025-04-17T09:33:57.082Z","avatar_url":"https://github.com/b-straub.png","language":"C#","readme":"DexieNET\n========\n\nDexieCloudNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB see https://dexie.org with cloud support see https://dexie.org/cloud/ .\n\n*'DexieNET' used with permission of David Fahlander*\n\n*and made with*\n\u003cimg src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/Rider.png\" alt=\"Rider logo.\" style=\"width:100px;\"\u003e [*Support for Open-Source Projects*](https://www.jetbrains.com/community/opensource/#support) !\n\n##  News\n\n- Added Declarative Web Push support. Please check the [Explanation](https://www.webkit.org/blog/16535/meet-declarative-web-push/#how-to-use-declarative-web-push) and PushServer [ReadMe](DexieNETCloudPushServer/README.md)\n    - Declarative WebPush is only support for iOS and iPadOS \u003e= 18.4\n    - Especially on iOS \u003c 18.4 clicking on notifications currently does not work reliable [notificationclick events in serviceworkers not firing](https://bugs.webkit.org/show_bug.cgi?id=268797)\n    - Clicking on notifications currently does not work reliable for Chrome on MacOS \u003e= 15 in addition to several other problems, use Safari for MacOS desktop PWA instead.\n- Released [DexieCloud](https://dexie.org/cloud/)\n- Please register with **DexieCloud** and test the [ToDoSample](DexieNETCloudSample). The configuration script can be found here [configure-app.ps1](DexieNETCloudSample/Dexie/configure-app.ps1) (Windows) or here [configure-app.sh](DexieNETCloudSample/Dexie/configure-app.sh) (Nix - jq required).\n- Published a new helper library [RxBlazorLight](https://github.com/b-straub/RxBlazorLight)  \n\n---\n\n##  Basic\n\n**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**.\n\nI 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*, ...\n\nIt's designed to work within a Blazor Webassembly application with minimal effort. \n\n#### Hello World\n\n- create a Blazor WebAssembly\n- Add the **DexieNET** Nuget\n- Add the HelloWorld Component and update the index\n\n*Program.cs*\n\n```c#\nusing DexieNET;\nusing YourNamspace.Pages;\n....\n\nbuilder.Services.AddDexieNET\u003cFriendsDB\u003e();\n```\n\n*HelloWorld.razor*\n\n```razor\n@page \"/helloWorld\"\n@using DexieNET.Component\n@inherits DexieNET\u003cFriendsDB\u003e\n\nFriends:\n@if (_friends is null)\n{\n    \u003cp\u003eLoading...\u003c/p\u003e\n}\nelse if (_friends.Count() == 0)\n{\n    \u003cp\u003eNo items...\u003c/p\u003e\n}\nelse\n{\n    \u003cul style=\"list-style: square inside;\"\u003e\n        @foreach (var friend in _friends)\n        {\n            \u003cli\u003e\n                Name: @friend.Name, Age: @friend.Age\n            \u003c/li\u003e\n        }\n    \u003c/ul\u003e\n}\n\n\u003chr /\u003e\n\nLogs:\n@if (_logs is null)\n{\n    \u003cp\u003eLoading...\u003c/p\u003e\n}\nelse if (_logs.Count() == 0)\n{\n    \u003cp\u003eNo items...\u003c/p\u003e\n}\nelse\n{\n    \u003cul style=\"list-style: square inside;\"\u003e\n        @foreach (var logEntry in _logs)\n        {\n            \u003cli\u003e\n                Message: @logEntry.Message, TimeStamp: @logEntry.TimeStamp.ToLongTimeString();\n            \u003c/li\u003e\n        }\n    \u003c/ul\u003e\n}\n\n\u003chr /\u003e\n\n\u003cdiv style=\"display: flex; column-gap: 50px\"\u003e\n    \u003cbutton class=\"btn btn-primary\" style=\"flex: 0 1 auto\" @onclick=\"PopulateDatabase\"\u003e\n        PopulateDatabase\n    \u003c/button\u003e\n\n    \u003cbutton class=\"btn btn-secondary\" style=\"flex: 0 1 auto\" @onclick=\"GoodTransaction\"\u003e\n        GoodTransaction\n    \u003c/button\u003e\n\n    \u003cbutton class=\"btn btn-secondary\" style=\"flex: 0 1 auto\" @onclick=\"FailedTransaction\"\u003e\n        FailedTransaction\n    \u003c/button\u003e\n\n    \u003cbutton class=\"btn btn-secondary\" style=\"flex: 0 1 auto\" @onclick=\"ClearDatabase\"\u003e\n        ClearDatabase\n    \u003c/button\u003e\n\u003c/div\u003e\n```\n\n*HelloWorld.razor.cs*\n\n```c#\nusing DexieNET;\nusing System.ComponentModel.DataAnnotations;\nusing System.Runtime.InteropServices;\nusing System.Xml.Linq;\n\nnamespace DexieNETHelloWorld.Pages\n{\n    public interface IFriendsDB : IDBStore { };\n\n    public partial record Friend\n    (\n        [property: Index] string Name,\n        [property: Index] int Age\n    ) : IFriendsDB;\n\n    public partial record LogEntry\n    (\n        [property: Index] string? Message,\n        [property: Index] DateTime TimeStamp\n    ) : IFriendsDB;\n\n    public partial class HelloWorld\n    {\n        private IEnumerable\u003cFriend\u003e? _friends;\n        private IEnumerable\u003cLogEntry\u003e? _logs;\n\n        protected override async Task OnInitializedAsync()\n        {\n            await base.OnInitializedAsync();\n            await Dexie.Version(1).Stores();\n            await FillTables();\n        }\n\n        private async Task FillTables()\n        {\n            _friends = await Dexie.Friends().ToArray();\n            _logs = await Dexie.LogEntries().OrderBy(l =\u003e l.TimeStamp).Reverse().ToArray();\n            await InvokeAsync(StateHasChanged);\n        }\n\n        private async Task LogMessage(string? message)\n        {\n            await Dexie.Transaction(async _ =\u003e\n            {\n                await Dexie.LogEntries().Add(new LogEntry(message, DateTime.Now));\n            }, TAType.TopLevel);\n        }\n        private async Task ClearDatabase()\n        {\n            await Dexie.Friends().Clear();\n            await Dexie.LogEntries().Clear();\n\n            await FillTables();\n        }\n\n        private async Task PopulateDatabase()\n        {\n            await LogMessage(\"PopulateDatabase\");\n\n            Random rand = new();\n            await Dexie.Friends().Add(new Friend(\"Jane Doe\", rand.Next(1, 99)));\n            await Dexie.Friends().Add(new Friend(\"John Doe\", rand.Next(1, 99)));\n\n            await FillTables();\n        }\n\n        private async Task GoodTransaction()\n        {\n            await LogMessage(\"GoodTransaction\");\n\n            await Dexie.Transaction(async ta =\u003e\n            {\n                Random rand = new();\n                var key = await Dexie.Friends().Add(new Friend(\"Luke\", rand.Next(1, 99)));\n                var friend = await Dexie.Friends().Get(key);\n\n                if (friend?.Name == \"Luke\" || ta.Collecting)\n                // ta.Collecting, this means the first pass of the transaction, in which the table names are collected\n                // if a second table is hidden behind a conditional statement, it must also be visited in the first pass\n                {\n                    await Dexie.Friends().Add(new Friend(\"John\", rand.Next(1, 99)));\n                    await Dexie.LogEntries().Add(new LogEntry(\"TA executed\", DateTime.Now));\n                }\n            });\n\n            await FillTables();\n        }\n\n        private async Task FailedTransaction()\n        {\n            await LogMessage(\"ProvokeFail\");\n\n            try\n            {\n                await Dexie.Transaction(async ta =\u003e\n                {\n                    await Dexie.Friends().Clear();\n                    var key = await Dexie.Friends().Add(new Friend(\"Test\", 33));\n                    var friend = await Dexie.Friends().Get(key);\n\n                    if (friend?.Name == \"Test\" || ta.Collecting)\n                    {\n                        await LogMessage(\"TA will fail\");\n                    }\n                    await Dexie.Friends().Add(friend); // this will fail\n                });\n            }\n            catch (Exception ex)\n            {\n                var firstDot = ex.Message.IndexOf('.');\n                var message = firstDot \u003c= 0 ? ex.Message : ex.Message[..firstDot];\n                await LogMessage($\"TA failed: {message}\");\n            }\n\n            await FillTables();\n        }\n    }\n}\n```\n\n## Advanced\n\n### Naming\n\n- the Source Generator will create the following classes from an *IDBStore* derived class, struct, record:\n\t- **PIndentifier** -\u003e Plural of *Identifier* provided by *Humanizer.Core* (English only), be aware the plural form might not be always obvious e.g. Person -\u003e People\n\t- service: *PIndentifier***DB**\n\t- table: *PIndentifier*\n\t\n    ```c#\n    // Record\n    public partial record Friend\n    (\n        [property: Index] string Name,\n        [property: Index] int Age\n    ) : IDBStore;\n\n    ......\n    // Service\n    builder.Services.AddDexieNET\u003cFriendsDB\u003e();\n    ......\n    [Inject]\n    public IDexieNETService\u003cFriendsDB\u003e? DB { get; set; }\n\n    ......\n    // Table\n    var table = await DB.Friends();\n    ```\n\t\t\n- You can have multiple stores in one database\n\t\n\t```c#\n\t[DBName(\"TestDB\")] // optional -\u003e default name = interface name without leading 'I' -\u003e PersonsDB\n    public interface IPersonsDB : IDBStore\n    {\n    }\n\n    // Records\n    [CompoundIndex(\"FirstName\", \"LastName\")]\n    public partial record Person\n    (\n        [property: Index] string FirstName,\n        [property: Index] string LastName,\n        Guid? AddressKey\n    ) : IPersonsDB;\n\n    [CompoundIndex(\"City\", \"Street\")]\n    [CompoundIndex(\"Zip\", \"Street\")]\n    public partial record Address\n    (\n        [property: Index] string Street,\n        [property: Index] string Housenumber,\n        [property: Index] string City,\n        [property: Index] string ZIP,\n        [property: Index] string Country\n\n    ) : IPersonsDB;\n\n\t......\n\t// Service\n\tusing DexieNET;\n\t.......\n\tbuilder.Services.AddDexieNET\u003cTestDB\u003e();\n\t\n\t// Component\n\t[Inject]\n\tpublic IDexieNETService\u003cTestDB\u003e? TestDB { get; set; }\n\n\t......\n\t// Table\n\tvar persons = await TestDB.Persons();\n\tvar addresses = await TestDB.Addresses();\n\t```\n\n### Transactions\n\n- transactions capture the table names in two passes\n- if a second table is hidden behind a conditional statement, it must also be visited in the first pass\n- nested transactions contain all required table names\n- top level transactions must use *TAType.TopLevel*, the root transaction is implictly a top level transaction\n- parallel transactions are composed by a root transaction with *TAType.TopLevel* and top level child transactions\n\n### Samples\n\nThe tests from [TestCases](DexieNETTest/TestBase/Test/TestCases) will cover all possible *DexieNET Api* calls. Those calls are as close as possible modelled after the original *Dexie.js* API.","funding_links":[],"categories":["Contributors Welcome for those","Libraries \u0026 Extensions"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","2D/3D Rendering engines"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb-straub%2FDexieNET","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb-straub%2FDexieNET","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb-straub%2FDexieNET/lists"}