├── .vscode ├── settings.json └── launch.json ├── DexieNETTest ├── Client │ ├── Pages │ │ ├── Administration.razor │ │ ├── Benchmark.razor │ │ ├── Playground.razor │ │ ├── ComponentTest.razor │ │ └── Tests.razor │ ├── wwwroot │ │ ├── favicon.png │ │ ├── icon-192.png │ │ ├── css │ │ │ └── open-iconic │ │ │ │ ├── font │ │ │ │ └── fonts │ │ │ │ │ ├── open-iconic.eot │ │ │ │ │ ├── open-iconic.otf │ │ │ │ │ ├── open-iconic.ttf │ │ │ │ │ └── open-iconic.woff │ │ │ │ └── ICON-LICENSE │ │ └── index.html │ ├── Shared │ │ ├── MainLayout.razor │ │ ├── NavMenu.razor.css │ │ ├── NavMenu.razor │ │ └── MainLayout.razor.css │ ├── App.razor │ ├── _Imports.razor │ ├── Program.cs │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── FolderProfile.pubxml │ │ └── launchSettings.json │ └── DexieNETTest.Client.csproj ├── Server │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── DexieNETTest.Server.csproj │ ├── Pages │ │ ├── Error.cshtml.cs │ │ └── Error.cshtml │ ├── Program.cs │ └── Properties │ │ └── launchSettings.json ├── TestBase │ ├── _Imports.razor │ ├── Test │ │ ├── Data │ │ │ ├── DexieTest.cs │ │ │ └── HelperExtensions.cs │ │ ├── DBTests.razor │ │ └── TestCases │ │ │ ├── Generell │ │ │ ├── Persistance.cs │ │ │ ├── OpenClose.cs │ │ │ ├── LiveQueryWriteTest.cs │ │ │ ├── Reverse.cs │ │ │ └── VersionUpdate.cs │ │ │ ├── Table │ │ │ ├── Delete.cs │ │ │ ├── AddSecond.cs │ │ │ ├── ToCollection.cs │ │ │ ├── Add.cs │ │ │ ├── BulkGet.cs │ │ │ ├── Put.cs │ │ │ ├── ClearCount.cs │ │ │ ├── AddClass.cs │ │ │ ├── BulkDelete.cs │ │ │ ├── TableFilter.cs │ │ │ ├── AddComplex.cs │ │ │ ├── BulkAdd.cs │ │ │ ├── Update.cs │ │ │ └── Get.cs │ │ │ ├── Where │ │ │ ├── NoneOf.cs │ │ │ └── NotEqual.cs │ │ │ └── Collection │ │ │ ├── LimitOffset.cs │ │ │ ├── CollectionFilter.cs │ │ │ ├── CollectionPrimaryKeys.cs │ │ │ └── CollectionFirstLast.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Components │ │ ├── DBComponentTest.razor.css │ │ ├── DBAdmin.razor │ │ └── DBComponentTest.razor │ └── DexieNETTest.TestBase.csproj ├── Tests │ ├── _Imports.razor │ ├── Properties │ │ └── launchSettings.json │ ├── Tests │ │ └── DexieNETTests.cs │ ├── Infrastructure │ │ └── BrowserFixtures.cs │ └── DexieNETTest.Tests.csproj └── TableGeneratorTest │ ├── DexieNETTest.TableGeneratorTest.csproj │ ├── Tests │ ├── ReservedStoreName.cs │ ├── AutoIncrementWithoutPrimary.cs │ ├── NonMultiEntryNotArray.cs │ └── RecordCompoundIndexNotFound.cs │ └── Helpers │ └── GeneratorFactory.cs ├── .nuget └── .gitignore ├── DexieNETCloudSample ├── Pages │ └── AdministrationPage.razor ├── wwwroot │ ├── favicon.png │ ├── icon-192.png │ ├── icon-512.png │ ├── checklist-512.png │ ├── warning-512.png │ ├── css │ │ └── open-iconic │ │ │ ├── font │ │ │ └── fonts │ │ │ │ ├── open-iconic.eot │ │ │ │ ├── open-iconic.otf │ │ │ │ ├── open-iconic.ttf │ │ │ │ └── open-iconic.woff │ │ │ └── ICON-LICENSE │ ├── manifest.webmanifest.json │ ├── service-worker.js │ └── index.html ├── Components │ ├── SharePushContainer.razor │ ├── Share.razor │ ├── MemberForm.razor │ ├── ToDoDBItemAddOrUpdate.razor │ └── InviteForm.razor ├── Shared │ ├── NavMenu.razor │ └── MainLayout.razor ├── App.razor ├── Administration │ ├── AdministrationData.cs │ ├── AdministrationService.State.cs │ └── Administration.razor.cs ├── Dexie │ ├── configure-app.sh │ ├── configure-app.ps1 │ ├── importfile.json │ └── Services │ │ ├── ToDoItemService.State.cs │ │ └── ToDoListMemberService.MemberRowScope.cs ├── _Imports.razor ├── Dialogs │ ├── ConfirmDialog.razor │ ├── AboutDialog.razor │ ├── AuthenticateEMail.razor │ ├── AddToDoList.razor │ ├── AddToDoItem.razor │ ├── AuthenticateOTP.razor │ └── GetClientKeys.razor ├── Program.cs ├── Properties │ └── launchSettings.json └── Logic │ └── DataHelper.cs ├── DexieCloudNET ├── yarn │ ├── serviceworker │ │ ├── dexieCloudNETServiceWorker.ts │ │ ├── dexieCloudNETSWBroadcast.ts │ │ ├── tsconfig.json │ │ ├── webpackDEV.config.js │ │ ├── package.json │ │ └── webpackREL.config.js │ ├── dexieServiceworker │ │ ├── dexieServiceWorker.ts │ │ ├── tsconfig.json │ │ ├── webpackDEV.config.js │ │ ├── package.json │ │ └── webpackREL.config.js │ └── dexiecloud │ │ ├── dexieCloudNET.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ ├── dexieCloudNETBase.ts │ │ ├── webpackDEV.config.js │ │ └── webpackREL.config.js ├── _Imports.razor ├── README.md ├── Component │ └── DexieCloudNET.cs └── DexieCloudNET │ └── Cloud │ ├── DexieCloudNETUser.cs │ ├── DexieCloudNETShared.cs │ └── DexieCloudNETSync.cs ├── DexieNET ├── _Imports.razor ├── yarn │ ├── src │ │ ├── dexieNET.ts │ │ ├── dexieNETBase.ts │ │ ├── dexieNETLiveQuery.ts │ │ ├── dexieNETTable.ts │ │ └── dexieNETTransactions.ts │ ├── tsconfig.json │ ├── webpackDEV.config.js │ ├── package.json │ └── webpackREL.config.js ├── README.md ├── DexieNET │ ├── Base │ │ └── DexieNETShared.cs │ └── Service │ │ └── DexieNETService.cs └── Component │ └── DexieNET.cs ├── Directory.Build.targets ├── DexieNETCloudPushServer ├── Services │ ├── ISecretsConfigurationService.cs │ ├── FileSecretsConfigurationService.cs │ ├── BWSSecretsConfigurationService.cs │ └── CloudRecords.cs ├── QuartzDBEntities │ ├── QrtzLock.cs │ ├── QrtzPausedTriggerGrp.cs │ ├── QrtzCalendar.cs │ ├── QrtzSchedulerState.cs │ ├── QrtzBlobTrigger.cs │ ├── QrtzCronTrigger.cs │ ├── QrtzSimpleTrigger.cs │ ├── QrtzJobDetail.cs │ ├── QrtzFiredTrigger.cs │ ├── QrtzSimpropTrigger.cs │ └── QrtzTrigger.cs ├── Program.cs └── README.md ├── NuGet.config ├── DexieNETTableGenerator ├── Properties │ └── launchSettings.json ├── Helpers │ └── RecordHelper.cs ├── AnalyzerReleases.Shipped.md └── DexieNETTableGenerator.csproj ├── Dockerfile ├── .gitignore ├── DexieNETTableGeneratorCodeFix └── DexieNETTableGeneratorCodeFix.csproj └── .github └── workflows ├── PublishNuget.yml └── Build.yml /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "DexieNET.sln" 3 | } -------------------------------------------------------------------------------- /DexieNETTest/Client/Pages/Administration.razor: -------------------------------------------------------------------------------- 1 | @page "/administration" 2 | 3 | -------------------------------------------------------------------------------- /.nuget/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /DexieNETCloudSample/Pages/AdministrationPage.razor: -------------------------------------------------------------------------------- 1 | @page "/administration" 2 | 3 | 4 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/dexieCloudNETServiceWorker.ts: -------------------------------------------------------------------------------- 1 | export * from "./dexieCloudNETServiceWorkerBase"; -------------------------------------------------------------------------------- /DexieNET/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.JSInterop 3 | @using R3 -------------------------------------------------------------------------------- /DexieCloudNET/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.JSInterop 3 | @using R3 -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.5.0 4 | 5 | -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/favicon.png -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/icon-192.png -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/icon-512.png -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/favicon.png -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/icon-192.png -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexieServiceworker/dexieServiceWorker.ts: -------------------------------------------------------------------------------- 1 | export * from "./node_modules/dexie-cloud-addon/dist/modern/service-worker"; -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/checklist-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/checklist-512.png -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/warning-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/warning-512.png -------------------------------------------------------------------------------- /DexieNETTest/Client/Pages/Benchmark.razor: -------------------------------------------------------------------------------- 1 | @page "/benchmark" 2 | 3 | Benchmark 4 | 5 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Pages/Playground.razor: -------------------------------------------------------------------------------- 1 | @page "/playground" 2 | 3 | Playground 4 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Pages/ComponentTest.razor: -------------------------------------------------------------------------------- 1 | @page "/component" 2 | 3 | Component Test 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETCloudSample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b-straub/DexieNET/HEAD/DexieNETTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /DexieNETTest/Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.JSInterop 3 | @using DexieNET 4 | @using DexieNET.Component 5 | @using DexieNETTest.TestBase.Test 6 | -------------------------------------------------------------------------------- /DexieNETTest/Tests/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.JSInterop 3 | @using DexieNET 4 | @using DexieNET.Component 5 | @using DexieNETTest.TestBase.Test 6 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/dexieCloudNET.ts: -------------------------------------------------------------------------------- 1 | export * from "./dexieCloudNETBase"; 2 | export * from "./dexieCloudNETCloud"; 3 | export * from "./dexieCloudNETObservables"; 4 | export * from "./dexieCloudNETPush"; -------------------------------------------------------------------------------- /DexieNETTest/Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/Services/ISecretsConfigurationService.cs: -------------------------------------------------------------------------------- 1 | namespace DexieNETCloudPushServer.Services; 2 | 3 | public interface ISecretsConfigurationService 4 | { 5 | public Dictionary GetSecrets(); 6 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/Components/SharePushContainer.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @attribute [Route(PushConstants.HomeRoute)] 3 | @attribute [Route(PushConstants.PushRoute)] 4 | 5 | @inherits RxBLServiceSubscriber 6 | 7 | 8 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Pages/Tests.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @page "/{TestName}" 3 | 4 | Tests 5 | 6 | 7 | @code { 8 | [Parameter] 9 | public string? TestName { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /DexieNET/yarn/src/dexieNET.ts: -------------------------------------------------------------------------------- 1 | export * from "./dexieNETBase"; 2 | export * from "./dexieNETLiveQuery"; 3 | export * from "./dexieNETTable"; 4 | export * from "./dexieNETTransactions"; 5 | export * from "./dexieNETCollection"; 6 | export * from "./dexieNETPersistence"; -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 2 | Home 3 | Administration 4 | 5 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzLock.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzLock 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string LockName { get; init; } = null!; 10 | } 11 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/Data/DexieTest.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal abstract class DexieTest(T db) where T : IDBBase 6 | { 7 | public abstract string Name { get; } 8 | 9 | protected T DB { get; } = db; 10 | 11 | public abstract ValueTask RunTest(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DexieNETTest/Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DexieNETTest.Tests": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:60841;http://localhost:60845" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /DexieNETTableGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DexieNETTableGenerator": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:60842;http://localhost:60843" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DexieNETTest.TestBase": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:60840;http://localhost:60844" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzPausedTriggerGrp.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzPausedTriggerGrp 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerGroup { get; init; } = null!; 10 | } 11 | -------------------------------------------------------------------------------- /DexieNET/yarn/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "strictPropertyInitialization": true, 5 | "alwaysStrict": false, 6 | "noImplicitAny": false, 7 | "noEmitOnError": true, 8 | "removeComments": false, 9 | "target": "ESNext", 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "inlineSourceMap": true, 13 | "inlineSources": true 14 | } 15 | } -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzCalendar.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzCalendar 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string CalendarName { get; init; } = null!; 10 | 11 | public byte[] Calendar { get; init; } = null!; 12 | } 13 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/dexieCloudNETSWBroadcast.ts: -------------------------------------------------------------------------------- 1 | export const DexieCloudNETBroadcastOut = "SW_TO_DEXIECLOUDNET"; 2 | export const DexieCloudNETBroadcastIn = "SW_FROM_DEXIECLOUDNET"; 3 | export const DexieCloudNETUpdateFound = "SW_UPDATE_FOUND"; 4 | export const DexieCloudNETReloadPage = "SW_RELOAD_PAGE"; 5 | export const DexieCloudNETSubscriptionChanged = "SW_SUBSCRIPTION_CHANGED"; 6 | export const DexieCloudNETSkipWaiting = "SW_SKIP_WAITING"; 7 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "strictPropertyInitialization": true, 5 | "alwaysStrict": false, 6 | "noImplicitAny": false, 7 | "noEmitOnError": true, 8 | "removeComments": false, 9 | "target": "ESNext", 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "inlineSourceMap": true, 13 | "inlineSources": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "strictPropertyInitialization": true, 5 | "alwaysStrict": false, 6 | "noImplicitAny": false, 7 | "noEmitOnError": true, 8 | "removeComments": false, 9 | "target": "ESNext", 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "inlineSourceMap": true, 13 | "inlineSources": true 14 | } 15 | } -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexieServiceworker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "strictPropertyInitialization": true, 5 | "alwaysStrict": false, 6 | "noImplicitAny": false, 7 | "noEmitOnError": true, 8 | "removeComments": false, 9 | "target": "ESNext", 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "inlineSourceMap": true, 13 | "inlineSources": true 14 | } 15 | } -------------------------------------------------------------------------------- /DexieNETTest/Client/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | About 11 |
12 | 13 |
14 | @Body 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzSchedulerState.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzSchedulerState 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string InstanceName { get; init; } = null!; 10 | 11 | public long LastCheckinTime { get; init; } 12 | 13 | public long CheckinInterval { get; init; } 14 | } 15 | -------------------------------------------------------------------------------- /DexieNETCloudSample/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /DexieNETTest/Client/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /DexieNETTest/Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using DexieNETTest.Client 10 | @using DexieNETTest.Client.Shared 11 | @using DexieNETTest.TestBase.Components 12 | @using DexieNETTest.TestBase.Test 13 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzBlobTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public class QrtzBlobTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerName { get; init; } = null!; 10 | 11 | public string TriggerGroup { get; init; } = null!; 12 | 13 | public byte[]? BlobData { get; init; } 14 | 15 | public QrtzTrigger QrtzTrigger { get; init; } = null!; 16 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Components/DBComponentTest.razor.css: -------------------------------------------------------------------------------- 1 | div { 2 | margin-bottom: 10px; 3 | } 4 | 5 | 6 | label { 7 | display: inline-block; 8 | width: 100px; 9 | text-align: right; 10 | } 11 | 12 | #centerBox { 13 | width: 500px; 14 | margin: auto 0; 15 | text-align: center; 16 | } 17 | 18 | #btn_s { 19 | width: 90px; 20 | } 21 | 22 | #btn_m { 23 | width: 150px; 24 | } 25 | 26 | .flex-container { 27 | display: flex; 28 | column-gap: 50px; 29 | } 30 | 31 | .flex-item { 32 | flex: 0 1 auto; 33 | } 34 | 35 | ::deep ul { 36 | flex: 0 1 auto; 37 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/manifest.webmanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DexieNETCloudSample ToDo", 3 | "short_name": "DexieNETCloudSample", 4 | "id": "./", 5 | "start_url": "./", 6 | "display": "standalone", 7 | "background_color": "#ffffff", 8 | "theme_color": "#03173d", 9 | "prefer_related_applications": false, 10 | "icons": [ 11 | { 12 | "src": "icon-512.png", 13 | "type": "image/png", 14 | "sizes": "512x512" 15 | }, 16 | { 17 | "src": "icon-192.png", 18 | "type": "image/png", 19 | "sizes": "192x192" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /DexieNET/README.md: -------------------------------------------------------------------------------- 1 | DexieNET 2 | ======== 3 | 4 | DexieNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB see https://dexie.org 5 | 6 | *'DexieNET' used with permission of David Fahlander* 7 | 8 | **DexieNET** aims to be a feature complete .NET wrapper for **Dexie.js** the famous Javascript IndexedDB wrapper from David Fahlander. 9 | 10 | I 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*, ... 11 | 12 | It's designed to work within a Blazor Webassembly application with minimal effort. -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzCronTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzCronTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerName { get; init; } = null!; 10 | 11 | public string TriggerGroup { get; init; } = null!; 12 | 13 | public string CronExpression { get; init; } = null!; 14 | 15 | public string? TimeZoneId { get; init; } 16 | 17 | public virtual QrtzTrigger QrtzTrigger { get; init; } = null!; 18 | } 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env 2 | WORKDIR /pushserver 3 | ARG BUILD_CONFIGURATION=Release 4 | ARG TARGETARCH 5 | 6 | # Copy everything 7 | COPY . ./ 8 | # Restore as distinct layers 9 | RUN dotnet restore "DexieNETCloudPushServer/DexieNETCloudPushServer.csproj" -a $TARGETARCH 10 | # Build and publish a release 11 | RUN dotnet publish "DexieNETCloudPushServer/DexieNETCloudPushServer.csproj" -c $BUILD_CONFIGURATION -o out -a $TARGETARCH 12 | 13 | # Build runtime image 14 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 15 | WORKDIR /pushserver 16 | COPY --from=build-env /pushserver/out . 17 | ENTRYPOINT ["dotnet", "DexieNETCloudPushServer.dll"] -------------------------------------------------------------------------------- /DexieNETCloudSample/Administration/AdministrationData.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DexieNETCloudSample.Administration 4 | { 5 | public class CloudKeyData(string? placeholderClientId = null, string? placeholderClientSecret = null) 6 | { 7 | [Required] 8 | public string ClientId { get; set; } = string.Empty; 9 | 10 | [Required] 11 | public string ClientSecret { get; set; } = string.Empty; 12 | 13 | public string PlaceholderClientId { get; set; } = placeholderClientId ?? string.Empty; 14 | public string PlaceholderClientSecret { get; set; } = placeholderClientSecret ?? string.Empty; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzSimpleTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzSimpleTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerName { get; init; } = null!; 10 | 11 | public string TriggerGroup { get; init; } = null!; 12 | 13 | public long RepeatCount { get; init; } 14 | 15 | public long RepeatInterval { get; init; } 16 | 17 | public long TimesTriggered { get; init; } 18 | 19 | public virtual QrtzTrigger QrtzTrigger { get; init; } = null!; 20 | } 21 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dexie/configure-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ ! -f "./dexie-cloud.json" ]; then 4 | echo "Please run:" 5 | echo " npx dexie-cloud create" 6 | echo "or: " 7 | echo " npx dexie-cloud connect " 8 | echo "...to create a database in the cloud" 9 | echo "Then retry this script!" 10 | exit 1; 11 | fi 12 | 13 | echo "Adding demo users to your application..." 14 | npx dexie-cloud import importfile.json 15 | 16 | URLS=$(jq -r '.profiles.https.applicationUrl' ../Properties/launchSettings.json) 17 | 18 | IFS='; ' read -ra URLA <<< $URLS 19 | for i in "${URLA[@]}"; do 20 | echo "Whitelisting origin: $i" 21 | npx dexie-cloud whitelist $i 22 | done -------------------------------------------------------------------------------- /DexieNETTest/Server/DexieNETTest.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 3077633a-989f-4828-97dd-a3997b639ae2 8 | Debug;Release 9 | AnyCPU 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Program.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | using DexieNETTest.Client; 3 | using DexieNETTest.TestBase.Components; 4 | using DexieNETTest.TestBase.Test; 5 | using Microsoft.AspNetCore.Components.Web; 6 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 7 | 8 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 9 | builder.RootComponents.Add("#app"); 10 | builder.RootComponents.Add("head::after"); 11 | 12 | builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 13 | builder.Services.AddDexieNET(); 14 | builder.Services.AddDexieNET(); 15 | builder.Services.AddDexieNET(); 16 | 17 | await builder.Build().RunAsync(); 18 | -------------------------------------------------------------------------------- /DexieNETTest/Server/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using System.Diagnostics; 4 | 5 | namespace DexieNETTest.Server.Pages 6 | { 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel(ILogger logger) : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger = logger; 16 | 17 | public void OnGet() 18 | { 19 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/Data/HelperExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace DexieNETTest.TestBase.Test 3 | { 4 | internal static class HelperExtensions 5 | { 6 | 7 | public static bool True(this bool? value) 8 | { 9 | return value.GetValueOrDefault(false); 10 | } 11 | 12 | public static bool SequenceEqual(this IEnumerable> values, IEnumerable> tests) 13 | { 14 | foreach (var (item, index) in values.Select((value, i) => (value, i))) 15 | { 16 | if (!item.SequenceEqual(tests.ElementAt(index))) 17 | { 18 | return false; 19 | } 20 | } 21 | 22 | return true; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/service-worker.js: -------------------------------------------------------------------------------- 1 | // In development, always fetch from the network and do not enable offline support. 2 | // This is because caching would make development more difficult (changes would not 3 | // be reflected on the first load after each change). 4 | 5 | import { notifyUpdate } from './_content/DexieCloudNET/js/dexieCloudNETServiceWorker.js'; 6 | // use the import below together with .WithTryUseServiceWorker(true) in DexieCloudOptions 7 | //import './_content/DexieCloudNET/js/dexieServiceWorker.js'; 8 | 9 | self.addEventListener('fetch', () => { 10 | }); 11 | 12 | self.addEventListener('install', async () => { 13 | const { installing, waiting, active } = await self.registration; 14 | if (waiting || active) { 15 | notifyUpdate(); 16 | } 17 | }); -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "DexieNETTest", 9 | "type": "blazorwasm", 10 | "request": "launch", 11 | "cwd": "${workspaceFolder}/DexieNETTest/Client" 12 | }, 13 | { 14 | "type": "blazorwasm", 15 | "browser": "chrome", 16 | "name": "DexieNETCloudSample", 17 | "request": "launch", 18 | "url": "http://localhost:5223", 19 | "cwd": "${workspaceFolder}/DexieNETCloudSample" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /DexieNET/DexieNET/Base/DexieNETShared.cs: -------------------------------------------------------------------------------- 1 | /* 2 | DexieNETShared.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | namespace DexieNET 22 | { 23 | public interface IDBStore 24 | { 25 | } 26 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Components/DBAdmin.razor: -------------------------------------------------------------------------------- 1 | 2 |

DB Administration

3 | 4 | 5 | 6 | 7 | @code { 8 | [Inject] 9 | public IDexieNETService? SecondDB { get; set; } 10 | 11 | [Inject] 12 | public IDexieNETService? TestDB { get; set; } 13 | 14 | [Inject] 15 | public IDexieNETService? FriendsDB { get; set; } 16 | 17 | private async Task Delete() 18 | { 19 | ArgumentNullException.ThrowIfNull(SecondDB); 20 | ArgumentNullException.ThrowIfNull(TestDB); 21 | ArgumentNullException.ThrowIfNull(FriendsDB); 22 | 23 | await SecondDB.DexieNETFactory.Delete(); 24 | await TestDB.DexieNETFactory.Delete(); 25 | await FriendsDB.DexieNETFactory.Delete(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DexieCloudNET/README.md: -------------------------------------------------------------------------------- 1 | DexieCloudNET 2 | ======== 3 | 4 | DexieCloudNET is a .NET wrapper for dexie.js minimalist wrapper for IndexedDB see https://dexie.org with cloud support see https://dexie.org/cloud/ . 5 | 6 | *'DexieNET' used with permission of David Fahlander* 7 | 8 | **DexieCloudNET** aims to be a feature complete .NET wrapper for **Dexie.js** the famous Javascript IndexedDB wrapper from David Fahlander including support for **cloud sync**. 9 | 10 | I 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*, ... 11 | 12 | It's designed to work within a Blazor Webassembly application with minimal effort. 13 | 14 | The port of the **ToDoSample** to .NET is a good starting point for exploring all the cloud features. -------------------------------------------------------------------------------- /DexieNETTest/Server/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | // Add services to the container. 4 | builder.Services.AddRazorPages(); 5 | 6 | var app = builder.Build(); 7 | 8 | // Configure the HTTP request pipeline. 9 | if (app.Environment.IsDevelopment()) 10 | { 11 | app.UseWebAssemblyDebugging(); 12 | } 13 | else 14 | { 15 | app.UseExceptionHandler("/Error"); 16 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 17 | app.UseHsts(); 18 | } 19 | 20 | app.UseHttpsRedirection(); 21 | 22 | app.UseBlazorFrameworkFiles(); 23 | app.UseStaticFiles(); 24 | 25 | app.UseRouting(); 26 | 27 | 28 | app.MapRazorPages(); 29 | app.MapControllers(); 30 | app.MapFallbackToFile("index.html"); 31 | 32 | app.Run(); 33 | 34 | public partial class Program { } 35 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dexie/configure-app.ps1: -------------------------------------------------------------------------------- 1 | $jsonConfigFile = "..\Properties\launchSettings.json" 2 | $JSON = Get-Content $jsonConfigFile | Out-String | ConvertFrom-Json 3 | $urls = $JSON.profiles.https.applicationUrl 4 | $urlArray = $urls.Split(";") 5 | 6 | $dexieCloudFile = "dexie-cloud.json" 7 | $importFile = "importfile.json" 8 | 9 | if (-not(Test-Path -Path $dexieCloudFile -PathType Leaf)) { 10 | Write-Host " 11 | Please run: 12 | npx dexie-cloud create 13 | or: 14 | npx dexie-cloud connect 15 | ...to create a database in the cloud 16 | Then retry this script! 17 | " 18 | exit 19 | } 20 | 21 | Write-Host "Adding demo users to your application..." 22 | npx dexie-cloud import $importFile 23 | 24 | Write-Host "Whitelisting origin: "$url 25 | 26 | foreach ($url in $urlArray) 27 | { 28 | npx dexie-cloud whitelist $url 29 | } 30 | 31 | -------------------------------------------------------------------------------- /DexieNETTableGenerator/Helpers/RecordHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | RecordHelper.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | using System.ComponentModel; 20 | 21 | namespace System.Runtime.CompilerServices 22 | { 23 | [EditorBrowsable(EditorBrowsableState.Never)] 24 | internal class IsExternalInit { } 25 | } 26 | -------------------------------------------------------------------------------- /DexieNET/yarn/webpackDEV.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: ['./src/dexieNET.ts'], 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: ["ts-loader"], 11 | exclude: /node_modules/ 12 | }, 13 | ] 14 | }, 15 | experiments: { 16 | outputModule: true 17 | }, 18 | resolve: { 19 | extensions: ['.tsx', '.ts', '.js'], 20 | }, 21 | devtool: 'source-map', 22 | output: { 23 | module: true, 24 | library: { 25 | type: 'module' 26 | }, 27 | path: path.resolve(__dirname, '../wwwroot/js'), 28 | filename: 'dexieNET.js', 29 | publicPath: '_content/DexieNET/js/' 30 | }, 31 | optimization: { 32 | minimize: false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *bin* 2 | *obj* 3 | *bat* 4 | *map* 5 | *log* 6 | .vs 7 | *.user 8 | *-template 9 | template-* 10 | .DS_Store 11 | .idea 12 | *.lock 13 | DexieNETIntern.sln 14 | DexieNETTest/TableGeneratorTest/TestResults 15 | Publish 16 | GeneratorRunner 17 | payload.json 18 | docker-compose.yml 19 | *.tar 20 | Nuget 21 | **node_modules 22 | 23 | DexieNET/wwwroot 24 | DexieNET/yarn/src/*.js 25 | DexieNET/yarn/.eslintrc.js 26 | 27 | DexieCloudNET/wwwroot/ 28 | DexieCloudNET/yarn/src/*.js 29 | DexieCloudNET/yarn/.eslintrc.js 30 | 31 | DexieNETCloudSample/Dexie/dexie-cloud.json 32 | DexieNETCloudSample/Dexie/dexie-cloud.key 33 | DexieNETCloudSample/wwwroot/dexie-cloud.json 34 | DexieNETCloudSample/wwwroot/importfile.json 35 | DexieNETCloudSample/Properties/Settings.xml 36 | DexieNETCloudSample/wwwroot/.htaccess 37 | DexieNETCloudSample/wwwroot/appsettings* 38 | DexieNETCloudPushServer/database 39 | *.code-workspace 40 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/DBTests.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @if (_testsCompleted) 5 | { 6 |
All Tests Completed
7 | } 8 | 9 | @if (_db is not null) 10 | { 11 | _lastCategory = null; 12 | 13 | @foreach (var test in _testResults.OrderBy(t => t.Category)) 14 | { 15 | @if (_lastCategory != test.Category) 16 | { 17 | _lastCategory = test.Category; 18 |

19 |
@test.Category
20 | } 21 | 22 | @if (test.Error) 23 | { 24 |
@test.Result
25 | } 26 | else 27 | { 28 |
@test.Result
29 | } 30 | } 31 | } 32 | 33 | @if (_error is not null) 34 | { 35 |

@_error

36 | } -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexieServiceworker/webpackDEV.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: ['./dexieServiceWorker.ts'], 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: ["ts-loader"], 11 | exclude: /node_modules/ 12 | }, 13 | ] 14 | }, 15 | experiments: { 16 | outputModule: true 17 | }, 18 | resolve: { 19 | extensions: ['.tsx', '.ts', '.js'], 20 | }, 21 | devtool: 'source-map', 22 | output: { 23 | module: true, 24 | library: { 25 | type: 'module' 26 | }, 27 | path: path.resolve(__dirname, '../../wwwroot/js'), 28 | filename: 'dexieServiceWorker.js', 29 | publicPath: '_content/DexieCloudNET/js/' 30 | }, 31 | optimization: { 32 | minimize: false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzJobDetail.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzJobDetail 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string JobName { get; init; } = null!; 10 | 11 | public string JobGroup { get; init; } = null!; 12 | 13 | public string? Description { get; init; } 14 | 15 | public string JobClassName { get; init; } = null!; 16 | 17 | public bool IsDurable { get; init; } 18 | 19 | public bool IsNonconcurrent { get; init; } 20 | 21 | public bool IsUpdateData { get; init; } 22 | 23 | public bool RequestsRecovery { get; init; } 24 | 25 | public byte[]? JobData { get; init; } 26 | 27 | public virtual ICollection QrtzTriggers { get; init; } = new List(); 28 | } 29 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/DexieNETTest.TestBase.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | enable 5 | enable 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/webpackDEV.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: ['./dexieCloudNETServiceWorker.ts'], 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: ["ts-loader"], 11 | exclude: /node_modules/ 12 | }, 13 | ] 14 | }, 15 | experiments: { 16 | outputModule: true 17 | }, 18 | resolve: { 19 | extensions: ['.tsx', '.ts', '.js'], 20 | }, 21 | devtool: 'source-map', 22 | output: { 23 | module: true, 24 | library: { 25 | type: 'module' 26 | }, 27 | path: path.resolve(__dirname, '../../wwwroot/js'), 28 | filename: 'dexieCloudNETServiceWorker.js', 29 | publicPath: '_content/DexieCloudNET/js/' 30 | }, 31 | optimization: { 32 | minimize: false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DexieNET/yarn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dexienet", 3 | "version": "1.5.0", 4 | "description": "", 5 | "main": "dexieNET.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "buildDebug": "webpack --config webpackDEV.config.js", 9 | "buildRelease": "webpack --config webpackREL.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "Bernhard Straub", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "@microsoft/dotnet-js-interop": "^8.0.0", 16 | "dexie": "4.0.11", 17 | "rxjs": "^7.8.1" 18 | }, 19 | "devDependencies": { 20 | "css-loader": "^7.1.2", 21 | "file-loader": "^6.2.0", 22 | "remove-files-webpack-plugin": "^1.5.0", 23 | "source-map-loader": "^5.0.0", 24 | "style-loader": "^4.0.0", 25 | "terser": "^5.30.4", 26 | "ts-loader": "^9.5.1", 27 | "typescript": "^5.3.3", 28 | "webpack": "^5.91.0", 29 | "webpack-cli": "^6.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DexieNETTableGeneratorCodeFix/DexieNETTableGeneratorCodeFix.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | enable 6 | 13.0 7 | True 8 | True 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Administration/AdministrationService.State.cs: -------------------------------------------------------------------------------- 1 | using DexieCloudNET; 2 | using RxBlazorLightCore; 3 | 4 | namespace DexieNETCloudSample.Administration 5 | { 6 | public partial class AdministrationService 7 | { 8 | public Func GetUsers(CloudKeyData value) => async c => 9 | { 10 | await DoGetUsers(value, c.CancellationToken); 11 | }; 12 | 13 | public Func DeleteUser => async c => 14 | { 15 | await DoDeleteUser(c.CancellationToken); 16 | }; 17 | 18 | public bool IsLoggedIn() 19 | { 20 | return DBService.UserLogin.Value?.AccessToken is not null; 21 | } 22 | 23 | public async Task ExpireAllPushSubscriptions() 24 | { 25 | ArgumentNullException.ThrowIfNull(DBService.DB); 26 | await DBService.DB.ExpireAllPushSubscriptions(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexieServiceworker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dexieserviceworker", 3 | "version": "1.5.0", 4 | "description": "", 5 | "main": "dexieServiceWorker.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "buildDebug": "webpack --config webpackDEV.config.js", 9 | "buildRelease": "webpack --config webpackREL.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "Bernhard Straub", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "@microsoft/dotnet-js-interop": "^8.0.0", 16 | "dexie": "4.0.11", 17 | "dexie-cloud-addon": "4.0.11" 18 | }, 19 | "devDependencies": { 20 | "file-loader": "^6.2.0", 21 | "js-md5": "^0.8.3", 22 | "remove-files-webpack-plugin": "^1.5.0", 23 | "source-map-loader": "^5.0.0", 24 | "style-loader": "^4.0.0", 25 | "terser": "^5.30.4", 26 | "ts-loader": "^9.5.1", 27 | "typescript": "^5.4.5", 28 | "webpack": "^5.90.1", 29 | "webpack-cli": "^6.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzFiredTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzFiredTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string EntryId { get; init; } = null!; 10 | 11 | public string TriggerName { get; init; } = null!; 12 | 13 | public string TriggerGroup { get; init; } = null!; 14 | 15 | public string InstanceName { get; init; } = null!; 16 | 17 | public long FiredTime { get; init; } 18 | 19 | public long SchedTime { get; init; } 20 | 21 | public int Priority { get; init; } 22 | 23 | public string State { get; init; } = null!; 24 | 25 | public string? JobName { get; init; } 26 | 27 | public string? JobGroup { get; init; } 28 | 29 | public bool IsNonconcurrent { get; init; } 30 | 31 | public bool? RequestsRecovery { get; init; } 32 | } 33 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dexiecloudnetserviceworker", 3 | "version": "1.5.0", 4 | "description": "", 5 | "main": "dexieCloudNETServiceWorker.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "buildDebug": "webpack --config webpackDEV.config.js", 9 | "buildRelease": "webpack --config webpackREL.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "Bernhard Straub", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "@microsoft/dotnet-js-interop": "^8.0.0", 16 | "dexie": "4.0.11", 17 | "dexie-cloud-addon": "4.0.11" 18 | }, 19 | "devDependencies": { 20 | "file-loader": "^6.2.0", 21 | "js-md5": "^0.8.3", 22 | "remove-files-webpack-plugin": "^1.5.0", 23 | "source-map-loader": "^5.0.0", 24 | "style-loader": "^4.0.0", 25 | "terser": "^5.30.4", 26 | "ts-loader": "^9.5.1", 27 | "typescript": "^5.4.5", 28 | "webpack": "^5.90.1", 29 | "webpack-cli": "^6.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Components/Share.razor: -------------------------------------------------------------------------------- 1 | @inherits RxBLServiceSubscriber 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Share with 12 | 13 | 14 | 15 | Service.CreateMemberScope())> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @code { 25 | [Parameter, EditorRequired] 26 | public required ToDoDBList List { get; init; } 27 | } 28 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Components/MemberForm.razor: -------------------------------------------------------------------------------- 1 | @if (Scope.Members.Any()) 2 | { 3 | 4 | 5 | Member 6 | Role 7 | 8 | 9 | Scope.CreateRowScope())> 11 | 12 | 13 | 14 | 15 | } 16 | 17 | @code { 18 | [CascadingParameter] 19 | public required ToDoListMemberService.MemberScope Scope { get; init; } 20 | 21 | [Parameter, EditorRequired] 22 | public required ToDoDBList List { get; init; } 23 | 24 | protected override async Task OnParametersSetAsync() 25 | { 26 | Scope.SetList(List); 27 | await base.OnParametersSetAsync(); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Generell/Persistance.cs: -------------------------------------------------------------------------------- 1 | namespace DexieNETTest.TestBase.Test 2 | { 3 | internal class PersistanceTest(TestDB db) : DexieTest(db) 4 | { 5 | public override string Name => "PersistanceTest"; 6 | 7 | public override async ValueTask RunTest() 8 | { 9 | var persistance = DB.Persistance(); 10 | 11 | var persistanceType1 = await persistance.GetPersistanceType(); 12 | var storageEstimate = await persistance.GetStorageEstimate(); 13 | var persist = await persistance.RequestPersistance(); 14 | var persistanceType2 = await persistance.GetPersistanceType(); 15 | 16 | return $"PersistanceTest: OK, PersistanceType before: {persistanceType1}, Persistence: {persist}, PersistanceType after: {persistanceType2}, " + 17 | $"Quota: {Math.Round(storageEstimate.Quota / 1000000.0, 2)} MB, Used: {Math.Round(storageEstimate.Usage / 1000000.0, 2)} MB"; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dexiercloudnet", 3 | "version": "1.5.0", 4 | "description": "", 5 | "main": "dexieCloudNET.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "buildDebug": "webpack --config webpackDEV.config.js", 9 | "buildRelease": "webpack --config webpackREL.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "Bernhard Straub", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "@microsoft/dotnet-js-interop": "^8.0.0", 16 | "dexie": "4.0.11", 17 | "dexie-cloud-addon": "^4.0.11", 18 | "dexie-cloud-common": "^1.0.52" 19 | }, 20 | "devDependencies": { 21 | "file-loader": "^6.2.0", 22 | "js-md5": "^0.8.3", 23 | "remove-files-webpack-plugin": "^1.5.0", 24 | "source-map-loader": "^5.0.0", 25 | "style-loader": "^4.0.0", 26 | "terser": "^5.30.4", 27 | "ts-loader": "^9.5.1", 28 | "typescript": "^5.4.5", 29 | "webpack": "^5.90.1", 30 | "webpack-cli": "^6.0.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexieServiceworker/webpackREL.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: ['./dexieServiceWorker.ts'], 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.tsx?$/, 11 | use: ["ts-loader"], 12 | exclude: /node_modules/ 13 | }, 14 | ] 15 | }, 16 | experiments: { 17 | outputModule: true 18 | }, 19 | resolve: { 20 | extensions: ['.tsx', '.ts', '.js'], 21 | }, 22 | devtool: 'source-map', 23 | output: { 24 | module: true, 25 | library: { 26 | type: 'module' 27 | }, 28 | path: path.resolve(__dirname, '../../wwwroot/js'), 29 | filename: 'dexieServiceWorker.js', 30 | publicPath: '_content/DexieCloudNET/js/' 31 | }, 32 | optimization: { 33 | minimize: true, 34 | minimizer: [new TerserPlugin()], 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | true 8 | false 9 | true 10 | Release 11 | Any CPU 12 | FileSystem 13 | ..\..\publish\ 14 | FileSystem 15 | <_TargetId>Folder 16 | 17 | net7.0 18 | browser-wasm 19 | 087f446f-2117-4f60-a28d-2f7bca1ea29b 20 | true 21 | false 22 | 23 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/serviceworker/webpackREL.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: ['./dexieCloudNETServiceWorker.ts'], 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.tsx?$/, 11 | use: ["ts-loader"], 12 | exclude: /node_modules/ 13 | }, 14 | ] 15 | }, 16 | experiments: { 17 | outputModule: true 18 | }, 19 | resolve: { 20 | extensions: ['.tsx', '.ts', '.js'], 21 | }, 22 | devtool: 'source-map', 23 | output: { 24 | module: true, 25 | library: { 26 | type: 'module' 27 | }, 28 | path: path.resolve(__dirname, '../../wwwroot/js'), 29 | filename: 'dexieCloudNETServiceWorker.js', 30 | publicPath: '_content/DexieCloudNET/js/*.*' 31 | }, 32 | optimization: { 33 | minimize: true, 34 | minimizer: [new TerserPlugin()], 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dexie/importfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "demoUsers": { 3 | "alice@demo.local": {}, 4 | "bob@demo.local": {}, 5 | "foo@demo.local": {}, 6 | "bar@demo.local": {} 7 | }, 8 | "roles": { 9 | "admin": { 10 | "displayName": "Admin", 11 | "description": "Members with this role gains full permissions within the realm pointed out by the member entry", 12 | "sortOrder": 1, 13 | "permissions": { "manage": "*" } 14 | }, 15 | "user": { 16 | "displayName": "User", 17 | "description": "Members with this role can add toDoDBItems, manage own toDoDBItems and mark other toDoDBItems as done or undone", 18 | "sortOrder": 2, 19 | "permissions": { 20 | "add": [ "toDoDBItems" ], 21 | "update": { 22 | "toDoDBItems": [ "completed" ] 23 | } 24 | } 25 | }, 26 | "guest": { 27 | "displayName": "Guest", 28 | "description": "Members with this role have no permissions to change any data", 29 | "sortOrder": 3, 30 | "permissions": {} 31 | } 32 | }, 33 | "data": {} 34 | } 35 | -------------------------------------------------------------------------------- /DexieNETTest/Client/DexieNETTest.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 7c146606-e186-4e86-bbc1-223866b7f3c4 8 | Debug;Release 9 | AnyCPU 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DexieNETTest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | An unhandled error has occurred. 26 | Reload 27 | 🗙 28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzSimpropTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzSimpropTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerName { get; init; } = null!; 10 | 11 | public string TriggerGroup { get; init; } = null!; 12 | 13 | public string? StrProp1 { get; init; } 14 | 15 | public string? StrProp2 { get; init; } 16 | 17 | public string? StrProp3 { get; init; } 18 | 19 | public int? IntProp1 { get; init; } 20 | 21 | public int? IntProp2 { get; init; } 22 | 23 | public long? LongProp1 { get; init; } 24 | 25 | public long? LongProp2 { get; init; } 26 | 27 | public decimal? DecProp1 { get; init; } 28 | 29 | public decimal? DecProp2 { get; init; } 30 | 31 | public bool? BoolProp1 { get; init; } 32 | 33 | public bool? BoolProp2 { get; init; } 34 | 35 | public string? TimeZoneId { get; init; } 36 | 37 | public virtual QrtzTrigger QrtzTrigger { get; init; } = null!; 38 | } 39 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Generell/OpenClose.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class OpenClose(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "OpenClose"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); // open implicitly 13 | 14 | var isOpen = DB.IsOpen(); 15 | 16 | if (!isOpen) 17 | { 18 | throw new InvalidOperationException("IsOpen failed."); 19 | } 20 | 21 | DB.Close(); 22 | 23 | isOpen = DB.IsOpen(); 24 | 25 | if (isOpen) 26 | { 27 | throw new InvalidOperationException("Close failed."); 28 | } 29 | 30 | var newDBInstance = await DB.Open(); 31 | 32 | isOpen = newDBInstance.IsOpen(); 33 | 34 | if (!isOpen) 35 | { 36 | throw new InvalidOperationException("Open failed."); 37 | } 38 | 39 | return "OK"; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/PublishNuget.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish Nuget 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | env: 8 | NuGetDirectory: ${{ github.workspace}}/.nuget 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup .NET 9.0 19 | uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: 9.0.x 22 | 23 | - name: Build DexieNET and DexieCloudNET 24 | run: dotnet build DexieCloudNET/DexieCloudNET.csproj -c Release 25 | 26 | - name: Pack DexieNET 27 | run: dotnet pack DexieNET/DexieNET.csproj -c Release 28 | 29 | - name: Pack DexieCloudNET 30 | run: dotnet pack DexieCloudNET/DexieCloudNET.csproj -c Release 31 | 32 | - name: TestSG 33 | run: dotnet test DexieNETTest/TableGeneratorTest/DexieNETTest.TableGeneratorTest.csproj -c Release 34 | 35 | - name: TestDB 36 | run: dotnet test DexieNETTest/Tests/DexieNETTest.Tests.csproj -c Release 37 | 38 | - name: Publish 39 | run: dotnet nuget push ${{ env.NuGetDirectory }}/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /DexieNETTest/Client/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | name: .NET Build, Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | pull_request: 8 | branches: 9 | - "**" 10 | 11 | jobs: 12 | 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup .NET 9.0 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: 9.0.x 24 | 25 | - name: Build DexieNET and DexieCloudNET 26 | run: dotnet build DexieCloudNET/DexieCloudNET.csproj -c Release 27 | 28 | - name: Pack DexieNET 29 | run: dotnet pack DexieNET/DexieNET.csproj -c Release 30 | 31 | - name: Pack DexieCloudNET 32 | run: dotnet pack DexieCloudNET/DexieCloudNET.csproj -c Release 33 | 34 | - name: UploadNuget 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: Nugets 38 | path: .nuget/ 39 | retention-days: 2 40 | 41 | - name: TestSG 42 | run: dotnet test DexieNETTest/TableGeneratorTest/DexieNETTest.TableGeneratorTest.csproj -c Release 43 | 44 | - name: TestDB 45 | run: dotnet test DexieNETTest/Tests/DexieNETTest.Tests.csproj -c Release -------------------------------------------------------------------------------- /DexieNET/Component/DexieNET.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace DexieNET.Component 5 | { 6 | public class DexieNET() : OwningComponentBase where T : DBBase, IDBBase 7 | { 8 | [Inject] 9 | protected IDexieNETService? DexieNETService { get; set; } 10 | 11 | [NotNull] // OnInitializedAsync will throw 12 | protected T? Dexie { get; private set; } 13 | 14 | protected override async Task OnInitializedAsync() 15 | { 16 | if (DexieNETService is not null) 17 | { 18 | Dexie = await DexieNETService.DexieNETFactory.Create(); 19 | 20 | if (Dexie is null) 21 | { 22 | throw new InvalidOperationException("Can not create database."); 23 | } 24 | } 25 | 26 | await base.OnInitializedAsync(); 27 | } 28 | 29 | protected override void Dispose(bool disposing) 30 | { 31 | if (disposing) 32 | { 33 | DexieNETService?.DexieNETFactory.Dispose(); 34 | } 35 | base.Dispose(disposing); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/Delete.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Delete(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Delete"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var person = DataGenerator.GetPerson1(); 15 | 16 | var key = await table.Add(person); 17 | await table.Delete(key); 18 | 19 | var res = await table.Count(); 20 | 21 | if (res != 0) 22 | { 23 | throw new InvalidOperationException("Delete failed."); 24 | } 25 | 26 | await DB.Transaction(async _ => 27 | { 28 | await table.Clear(); 29 | key = await table.Add(person); 30 | await table.Delete(key); 31 | }); 32 | 33 | res = await table.Count(); 34 | 35 | if (res != 0) 36 | { 37 | throw new InvalidOperationException("Delete failed."); 38 | } 39 | 40 | return "OK"; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DexieNETCloudSample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Diagnostics.CodeAnalysis 2 | @using System.Net.Http 3 | @using System.Net.Http.Json 4 | @using R3 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using Microsoft.AspNetCore.Components.Web.Virtualization 9 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 10 | @using Microsoft.Extensions.Options 11 | @using Microsoft.JSInterop 12 | @using DexieNETCloudSample 13 | @using DexieNETCloudSample.Administration 14 | @using DexieNETCloudSample.Components 15 | @using DexieNETCloudSample.Dialogs 16 | @using DexieNETCloudSample.Extensions; 17 | @using DexieNETCloudSample.Logic 18 | @using DexieNETCloudSample.Dexie.Services 19 | @using DexieNETCloudSample.Shared 20 | @using DexieNET 21 | @using DexieCloudNET 22 | @using MudBlazor 23 | @using RxBlazorLightCore 24 | @using RxMudBlazorLight.Buttons 25 | @using RxMudBlazorLight.Dialogs 26 | @using RxMudBlazorLight.Extensions 27 | @using RxMudBlazorLight.IconButtons 28 | @using RxMudBlazorLight.FabButtons 29 | @using RxMudBlazorLight.Inputs 30 | @using RxMudBlazorLight.Inputs.Select 31 | @using RxMudBlazorLight.Menus 32 | @using RxMudBlazorLight.ToggleIconButtons 33 | @using cmdwtf -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/ConfirmDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | @Message 4 | 5 | 6 | Cancel 7 | @ConfirmButton 8 | 9 | 10 | 11 | @code { 12 | [CascadingParameter] 13 | public required IMudDialogInstance MudDialog { get; init; } 14 | 15 | [Parameter, EditorRequired] 16 | public required string Message { get; init; } 17 | 18 | [Parameter] 19 | public string ConfirmButton { get; set; } = "Delete"; 20 | 21 | [Parameter] 22 | public bool SuccessOnConfirm { get; set; } 23 | 24 | void Submit() => MudDialog.Close(DialogResult.Ok(true)); 25 | void Cancel() => MudDialog.Cancel(); 26 | 27 | protected override async Task OnInitializedAsync() 28 | { 29 | var options = MudDialog.Options with 30 | { 31 | CloseButton = false 32 | }; 33 | 34 | await MudDialog.SetOptionsAsync(options); 35 | await base.OnInitializedAsync(); 36 | } 37 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/AddSecond.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class AddSecond(SecondDB db) : DexieTest(db) 6 | { 7 | public override string Name => "AddSecond"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.TestStore(); 12 | await table.Clear(); 13 | 14 | var key = await table.Add(new TestStore("Test"), 1); 15 | var testAdded = (await table.ToArray()).FirstOrDefault(); 16 | 17 | if (key != 1 || testAdded?.Name != "Test") 18 | { 19 | throw new InvalidOperationException("Item not identical."); 20 | } 21 | 22 | await DB.Transaction(async _ => 23 | { 24 | await table.Clear(); 25 | key = await table.Add(new TestStore("Test"), 1); 26 | }); 27 | 28 | testAdded = (await table.ToArray()).FirstOrDefault(); 29 | 30 | if (key != 1 || testAdded?.Name != "Test") 31 | { 32 | throw new InvalidOperationException("Item not identical."); 33 | } 34 | 35 | return "OK"; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/dexieCloudNETBase.ts: -------------------------------------------------------------------------------- 1 | /* 2 | dexieCloudNETBase.ts 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | import DexieCloudDB from 'dexie'; 22 | import {Dexie} from 'dexie'; 23 | import DexieCloud from "dexie-cloud-addon"; 24 | 25 | export let CurrentDB: CloudDB | undefined = undefined; 26 | 27 | 28 | Dexie.debug = 'dexie'; 29 | // @ts-ignore 30 | export class CloudDB extends DexieCloudDB { 31 | constructor(name: string) { 32 | super(name, {addons: [DexieCloud]}); 33 | } 34 | } 35 | 36 | export function CreateCloud(name: string): CloudDB { 37 | CurrentDB = new CloudDB(name); 38 | return CurrentDB; 39 | } -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/webpackDEV.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const RemovePlugin = require('remove-files-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: ['./dexieCloudNET.ts'], 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.tsx?$/, 11 | use: ["ts-loader"], 12 | exclude: /node_modules/ 13 | }, 14 | ] 15 | }, 16 | experiments: { 17 | outputModule: true 18 | }, 19 | resolve: { 20 | extensions: ['.tsx', '.ts', '.js'], 21 | }, 22 | devtool: 'source-map', 23 | output: { 24 | module: true, 25 | library: { 26 | type: 'module' 27 | }, 28 | path: path.resolve(__dirname, '../../wwwroot/js'), 29 | filename: 'dexieCloudNET.js', 30 | publicPath: '_content/DexieCloudNET/js/' 31 | }, 32 | optimization: { 33 | minimize: false 34 | }, 35 | plugins: [ 36 | new RemovePlugin({ 37 | before: { 38 | allowRootAndOutside: true, 39 | include: [ 40 | path.resolve(__dirname, '../../wwwroot/js') 41 | ] 42 | } 43 | }) 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /DexieNET/yarn/src/dexieNETBase.ts: -------------------------------------------------------------------------------- 1 | /* 2 | dexieNET.ts 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | import Dexie from 'dexie'; 22 | 23 | // @ts-ignore 24 | export class DB extends Dexie { 25 | constructor(name: string) { 26 | super(name); 27 | } 28 | } 29 | 30 | export function Create(name: string): DB { 31 | let db = new DB(name); 32 | return db; 33 | } 34 | 35 | export function Delete(name: string): Promise { 36 | return Dexie.delete(name); 37 | } 38 | 39 | export function Name(db: DB): string { 40 | return db.name; 41 | } 42 | 43 | export function Version(db: DB): number { 44 | return db.verno; 45 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/ToCollection.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class ToCollection(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "ToCollection"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | await table.BulkAdd(persons); 16 | var count = await table.ToCollection().Count(); 17 | 18 | if (count != persons.Count()) 19 | { 20 | throw new InvalidOperationException("Items count not identical."); 21 | } 22 | 23 | await DB.Transaction(async _ => 24 | { 25 | await table.Clear(); 26 | await table.BulkAdd(persons); 27 | var collection = table.ToCollection(); 28 | count = await collection.Count(); 29 | }); 30 | 31 | if (count != persons.Count()) 32 | { 33 | throw new InvalidOperationException("Items count not identical."); 34 | } 35 | 36 | return "OK"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/Add.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Add(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Add"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var person = DataGenerator.GetPerson1(); 15 | 16 | var key = await table.Add(person); 17 | var personAdded = (await table.ToArray()).FirstOrDefault(); 18 | 19 | PersonComparer comparer = new(true); 20 | 21 | if (!comparer.Equals(person, personAdded)) 22 | { 23 | throw new InvalidOperationException("Item not identical."); 24 | } 25 | 26 | await DB.Transaction(async _ => 27 | { 28 | await table.Clear(); 29 | await table.Add(person); 30 | }); 31 | 32 | personAdded = (await table.ToArray()).FirstOrDefault(); 33 | 34 | if (!comparer.Equals(person, personAdded)) 35 | { 36 | throw new InvalidOperationException("Item not identical from Transaction."); 37 | } 38 | 39 | return "OK"; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DexieNET/yarn/webpackREL.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const RemovePlugin = require('remove-files-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: ['./src/dexieNET.ts'], 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | use: ["ts-loader"], 13 | exclude: /node_modules/ 14 | }, 15 | ] 16 | }, 17 | experiments: { 18 | outputModule: true 19 | }, 20 | resolve: { 21 | extensions: ['.tsx', '.ts', '.js'], 22 | }, 23 | devtool: 'source-map', 24 | output: { 25 | module: true, 26 | library: { 27 | type: 'module' 28 | }, 29 | path: path.resolve(__dirname, '../wwwroot/js'), 30 | filename: 'dexieNET.js', 31 | publicPath: '_content/DexieNET/js/' 32 | }, 33 | optimization: { 34 | minimize: true, 35 | minimizer: [new TerserPlugin()], 36 | }, 37 | plugins: [ 38 | new RemovePlugin({ 39 | before: { 40 | allowRootAndOutside: true, 41 | include: [ 42 | path.resolve(__dirname, '../wwwroot/js') 43 | ] 44 | } 45 | }) 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /DexieCloudNET/yarn/dexiecloud/webpackREL.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const RemovePlugin = require('remove-files-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: ['./dexieCloudNET.ts'], 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | use: ["ts-loader"], 13 | exclude: /node_modules/ 14 | }, 15 | ] 16 | }, 17 | experiments: { 18 | outputModule: true 19 | }, 20 | resolve: { 21 | extensions: ['.tsx', '.ts', '.js'], 22 | }, 23 | devtool: 'source-map', 24 | output: { 25 | module: true, 26 | library: { 27 | type: 'module' 28 | }, 29 | path: path.resolve(__dirname, '../../wwwroot/js'), 30 | filename: 'dexieCloudNET.js', 31 | publicPath: '_content/DexieCloudNET/js/*.*' 32 | }, 33 | optimization: { 34 | minimize: true, 35 | minimizer: [new TerserPlugin()], 36 | }, 37 | plugins: [ 38 | new RemovePlugin({ 39 | before: { 40 | allowRootAndOutside: true, 41 | include: [ 42 | path.resolve(__dirname, '../../wwwroot/js') 43 | ] 44 | } 45 | }) 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/AboutDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DexieNETCloudSample 7 | 8 | 9 | 10 | Build at @_buildTime 11 | © Bernhard Straub 2024 12 | 13 | 14 | 15 | 16 | Ok 17 | 18 | 19 | 20 | @code { 21 | [CascadingParameter] 22 | public required IMudDialogInstance MudDialog { get; init; } 23 | 24 | void Submit() => MudDialog.Close(DialogResult.Ok(true)); 25 | 26 | private readonly string _buildTime = BuildTimestamp.BuildTime.ToLongDateString() + " - " + BuildTimestamp.BuildTime.ToLongTimeString(); 27 | 28 | protected override async Task OnInitializedAsync() 29 | { 30 | var options = MudDialog.Options with 31 | { 32 | CloseButton = false 33 | }; 34 | 35 | await MudDialog.SetOptionsAsync(options); 36 | await base.OnInitializedAsync(); 37 | } 38 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/Program.cs: -------------------------------------------------------------------------------- 1 | using DexieCloudNET; 2 | using DexieNETCloudSample; 3 | using DexieNETCloudSample.Administration; 4 | using DexieNETCloudSample.Dexie.Services; 5 | using DexieNETCloudSample.Extensions; 6 | using DexieNETCloudSample.Logic; 7 | using Microsoft.AspNetCore.Components.Web; 8 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 9 | using MudBlazor; 10 | using MudBlazor.Services; 11 | 12 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 13 | builder.RootComponents.Add("#app"); 14 | builder.RootComponents.Add("head::after"); 15 | 16 | builder.Services.AddMudServices(config => 17 | { 18 | config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.TopCenter; 19 | }); 20 | 21 | builder.Services.AddDexieCloudNET(); 22 | 23 | builder.Services.AddSingleton(sp => new DexieCloudService(sp)); 24 | builder.Services.AddSingleton(sp => new AdministrationService(sp)); 25 | builder.Services.AddScoped(sp => new ToDoItemService(sp)); 26 | builder.Services.AddSingleton(sp => new ToDoListMemberService(sp)); 27 | builder.Services.AddSingleton(sp => new ToDoListService(sp)); 28 | 29 | builder.Logging.ClearProviders(); 30 | #if !DEBUG 31 | builder.Services.AddLogging(l => l.SetMinimumLevel(LogLevel.Warning)); 32 | #endif 33 | 34 | await builder.LoadConfigurationAsync(); // also adds HttpClient 35 | 36 | await builder.Build().RunAsync(); 37 | -------------------------------------------------------------------------------- /DexieCloudNET/Component/DexieCloudNET.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System.Diagnostics.CodeAnalysis; 3 | using DexieNET; 4 | 5 | namespace DexieCloudNET.Component 6 | { 7 | public class DexieCloudNET(string cloudURL) : OwningComponentBase where T : DBBase, IDBBase 8 | { 9 | [Inject] 10 | protected IDexieNETService? DexieNETService { get; set; } 11 | 12 | [NotNull] // OnInitializedAsync will throw 13 | protected T? Dexie { get; set; } 14 | 15 | protected override async Task OnInitializedAsync() 16 | { 17 | if (DexieNETService is not null) 18 | { 19 | DexieCloudOptions cloudOptions = new(cloudURL); 20 | 21 | Dexie = await DexieNETService.DexieNETFactory.Create(); 22 | 23 | if (Dexie is null) 24 | { 25 | throw new InvalidOperationException("Can not create database."); 26 | } 27 | 28 | await Dexie.ConfigureCloud(cloudOptions); 29 | } 30 | 31 | await base.OnInitializedAsync(); 32 | } 33 | 34 | protected override void Dispose(bool disposing) 35 | { 36 | if (disposing) 37 | { 38 | DexieNETService?.DexieNETFactory.Dispose(); 39 | } 40 | base.Dispose(disposing); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/BulkGet.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class BulkGet(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "BulkGet"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | var keys = await table.BulkAdd(persons, true); 16 | 17 | var personsAdded = await table.BulkGet(keys); 18 | 19 | PersonComparer pComparer = new(true); 20 | 21 | if (!persons.SequenceEqual(personsAdded, new PersonComparer(true))) 22 | { 23 | throw new InvalidOperationException("Items not identical."); 24 | } 25 | 26 | await DB.Transaction(async _ => 27 | { 28 | await table.Clear(); 29 | keys = await table.BulkAdd(persons, true); 30 | personsAdded = await table.BulkGet(keys); 31 | }); 32 | 33 | if (personsAdded is null || !persons.SequenceEqual(personsAdded, new PersonComparer(true))) 34 | { 35 | throw new InvalidOperationException("Items not identical."); 36 | } 37 | 38 | return "OK"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:12360", 7 | "sslPort": 44366 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "http://localhost:5223", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 26 | "applicationUrl": "https://localhost:7293;http://localhost:5223", 27 | "environmentVariables": { 28 | "ASPNETCORE_ENVIRONMENT": "Development" 29 | } 30 | }, 31 | "IIS Express": { 32 | "commandName": "IISExpress", 33 | "launchBrowser": true, 34 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 35 | "environmentVariables": { 36 | "ASPNETCORE_ENVIRONMENT": "Development" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/AuthenticateEMail.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Item.Email) /> 8 | 9 | 10 | Submit 11 | 12 | 13 | 14 | 15 | 16 | 17 | @code { 18 | 19 | [CascadingParameter] 20 | public required IMudDialogInstance MudDialog { get; init; } 21 | 22 | [Parameter, EditorRequired] 23 | public required EmailData Item { get; init; } 24 | 25 | protected override async Task OnInitializedAsync() 26 | { 27 | var options = MudDialog.Options with 28 | { 29 | CloseButton = true 30 | }; 31 | 32 | await MudDialog.SetOptionsAsync(options); 33 | await base.OnInitializedAsync(); 34 | } 35 | 36 | private void OnValidSubmit(EditContext context) 37 | { 38 | MudDialog.Close(DialogResult.Ok(Item)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DexieNETCloudSample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DexieNETCloudSample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 | An unhandled error has occurred. 29 | Reload 30 | 🗙 31 |
32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/AddToDoList.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @List.Title) /> 8 | 9 | 10 | Save 11 | 12 | 13 | 14 | 15 | 16 | 17 | @code { 18 | 19 | [CascadingParameter] 20 | public required IMudDialogInstance MudDialog { get; init; } 21 | 22 | [Parameter, EditorRequired] 23 | public required ToDoListData List { get; init; } 24 | 25 | [Parameter] 26 | public bool CanUpdateTitle { get; set; } 27 | 28 | protected override async Task OnInitializedAsync() 29 | { 30 | var options = MudDialog.Options with 31 | { 32 | CloseButton = true 33 | }; 34 | 35 | await MudDialog.SetOptionsAsync(options); 36 | await base.OnInitializedAsync(); 37 | } 38 | 39 | private void OnValidSubmit(EditContext context) 40 | { 41 | MudDialog.Close(DialogResult.Ok(List)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Components/ToDoDBItemAddOrUpdate.razor: -------------------------------------------------------------------------------- 1 | @inherits RxBLStateSubscriber 2 | 3 | @if (AddMode) 4 | { 5 | 6 | } 7 | else 8 | { 9 | 10 | } 11 | 12 | @code { 13 | [Parameter, EditorRequired] 14 | public required bool AddMode { get; init; } 15 | 16 | [Parameter, EditorRequired] 17 | public required ToDoDBItem Item { get; init; } 18 | 19 | [Inject] 20 | public required IDialogService DialogService { get; init; } 21 | 22 | private async Task ShowAddUpdateDialog(IStateCommandAsync _) 23 | { 24 | using var itemInput = Owner.CreateItemInput(Item); 25 | var parameters = new DialogParameters { ["Item"] = Item, ["Owner"] = itemInput }; 26 | var dialog = await DialogService.ShowAsync(AddMode ? "Add ToDo" : "Edit ToDo", parameters); 27 | var result = await dialog.Result; 28 | 29 | if (result.OK()) 30 | { 31 | var scope = (ToDoItemService.ToDoItemItemInput?)result.Data; 32 | if (scope is not null) 33 | { 34 | await scope.SubmitAsync(); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/QuartzDBEntities/QrtzTrigger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength 2 | 3 | namespace DexieNETCloudPushServer.QuartzDBEntities; 4 | 5 | public partial class QrtzTrigger 6 | { 7 | public string SchedName { get; init; } = null!; 8 | 9 | public string TriggerName { get; init; } = null!; 10 | 11 | public string TriggerGroup { get; init; } = null!; 12 | 13 | public string JobName { get; init; } = null!; 14 | 15 | public string JobGroup { get; init; } = null!; 16 | 17 | public string? Description { get; init; } 18 | 19 | public long? NextFireTime { get; init; } 20 | 21 | public long? PrevFireTime { get; init; } 22 | 23 | public int? Priority { get; init; } 24 | 25 | public string TriggerState { get; init; } = null!; 26 | 27 | public string TriggerType { get; init; } = null!; 28 | 29 | public long StartTime { get; init; } 30 | 31 | public long? EndTime { get; init; } 32 | 33 | public string? CalendarName { get; init; } 34 | 35 | public short? MisfireInstr { get; init; } 36 | 37 | public byte[]? JobData { get; init; } 38 | 39 | public virtual QrtzBlobTrigger? QrtzBlobTrigger { get; init; } 40 | 41 | public virtual QrtzCronTrigger? QrtzCronTrigger { get; init; } 42 | 43 | public virtual QrtzJobDetail QrtzJobDetail { get; init; } = null!; 44 | 45 | public virtual QrtzSimpleTrigger? QrtzSimpleTrigger { get; init; } 46 | 47 | public virtual QrtzSimpropTrigger? QrtzSimpropTrigger { get; init; } 48 | } 49 | -------------------------------------------------------------------------------- /DexieNETTableGenerator/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | ; Shipped analyzer releases 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | ## Release 1.0 5 | 6 | ### New Rules 7 | 8 | Rule ID | Category | Severity | Notes 9 | --------|----------|----------|------- 10 | DNTGG000 | DNTGenerator | Error | GeneratorDiagnostic 11 | DNTGG001 | DNTGenerator | Info | GeneratorDiagnostic 12 | DNTGG002 | DNTGenerator | Error | GeneratorDiagnostic 13 | DNTGG003 | DNTGenerator | Error | GeneratorDiagnostic 14 | DNTGG100 | DNTGenerator | Error | GeneratorDiagnostic 15 | DNTGG101 | DNTGenerator | Error | GeneratorDiagnostic 16 | DNTGG102 | DNTGenerator | Error | GeneratorDiagnostic 17 | DNTGG110 | DNTGenerator | Error | GeneratorDiagnostic 18 | DNTGG200 | DNTGenerator | Error | GeneratorDiagnostic 19 | DNTGG201 | DNTGenerator | Error | GeneratorDiagnostic 20 | DNTGG202 | DNTGenerator | Error | GeneratorDiagnostic 21 | DNTGG203 | DNTGenerator | Error | GeneratorDiagnostic 22 | DNTGG210 | DNTGenerator | Error | GeneratorDiagnostic 23 | DNTGG211 | DNTGenerator | Error | GeneratorDiagnostic 24 | DNTGG220 | DNTGenerator | Error | GeneratorDiagnostic 25 | DNTGG221 | DNTGenerator | Error | GeneratorDiagnostic 26 | DNTGG222 | DNTGenerator | Error | GeneratorDiagnostic 27 | DNTGG223 | DNTGenerator | Error | GeneratorDiagnostic 28 | DNTGG230 | DNTGenerator | Error | GeneratorDiagnostic 29 | DNTGG231 | DNTGenerator | Error | GeneratorDiagnostic 30 | DNTGG240 | DNTGenerator | Error | GeneratorDiagnostic 31 | DNTGG300 | DNTGenerator | Error | GeneratorDiagnostic -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/DexieNETTest.TableGeneratorTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | enable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Where/NoneOf.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class NoneOf(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "NoneOf"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var comparer = new PersonComparer(true); 12 | 13 | var table = DB.Persons; 14 | await table.Clear(); 15 | 16 | var persons = DataGenerator.GetPersons(); 17 | await table.BulkAdd(persons); 18 | 19 | var personsDataAge = persons.Where(p => p.Age is not 11 and not 75).OrderBy(p => p.Age); 20 | var personsAge = await table.Where(p => p.Age).NoneOf(11, 75).ToArray(); 21 | 22 | if (!personsAge.OrderBy(p => p.Age).SequenceEqual(personsDataAge, comparer)) 23 | { 24 | throw new InvalidOperationException("Items not identical."); 25 | } 26 | 27 | await DB.Transaction(async _ => 28 | { 29 | await table.Clear(); 30 | await table.BulkAdd(persons); 31 | 32 | var whereClauseAge = table.Where(p => p.Age); 33 | var collectionAge = whereClauseAge.NoneOf(11, 75); 34 | personsAge = await collectionAge.ToArray(); 35 | }); 36 | 37 | if (!personsAge.OrderBy(p => p.Age).SequenceEqual(personsDataAge, comparer)) 38 | { 39 | throw new InvalidOperationException("Items not identical."); 40 | } 41 | 42 | return "OK"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Logic/DataHelper.cs: -------------------------------------------------------------------------------- 1 | using DexieCloudNET; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DexieNETCloudSample.Logic 5 | { 6 | public class ToDoListData(string title) 7 | { 8 | [Required(AllowEmptyStrings = false)] 9 | public string Title { get; set; } = title; 10 | 11 | [Required] 12 | public DateTime DueDate { get; set; } 13 | } 14 | 15 | public class ToDoItemData(string text, DateTime date) 16 | { 17 | [Required(AllowEmptyStrings = false)] 18 | public string Text { get; set; } = text; 19 | 20 | [Required] 21 | public DateTime DueDate { get; set; } = date; 22 | } 23 | 24 | public class EmailData(string? placeholder) 25 | { 26 | [Required(AllowEmptyStrings = false)] 27 | [RegularExpression(@"^[\w\-+.]+@([\w-]+\.)+[\w-]{2,10}(\sas\s[\w\-+.]+@([\w-]+\.)+[\w-]{2,10})?$", 28 | ErrorMessage = "Enter a valid eMail address or eMail1 as eMail2!")] 29 | // https://github.com/dexie/Dexie.js/blob/81ce60736455470742e6b4c12a9e4f10e73f7c60/addons/dexie-cloud/src/authentication/interactWithUser.ts#L100 30 | public string Email { get; set; } = string.Empty; 31 | 32 | public string Placeholder { get; set; } = placeholder ?? string.Empty; 33 | } 34 | 35 | public class OTPData(IEnumerable alerts) 36 | { 37 | [StringLength(8, MinimumLength = 8)] 38 | [RegularExpression(@"([a-zA-Z0-9]+)", ErrorMessage = "Only letters and numbers allowed")] 39 | public string OTP { get; set; } = string.Empty; 40 | 41 | public IEnumerable Alerts { get; set; } = alerts; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DexieNETTest/Server/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DexieNETTest.Server.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DexieNET/DexieNET/Service/DexieNETService.cs: -------------------------------------------------------------------------------- 1 | /* 2 | DexieNETService.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | using Microsoft.Extensions.DependencyInjection; 22 | using Microsoft.JSInterop; 23 | 24 | namespace DexieNET 25 | { 26 | public interface IDexieNETFactory : IDisposable 27 | { 28 | public ValueTask Create(); 29 | public ValueTask Delete(); 30 | } 31 | 32 | public interface IDexieNETService where T : DBBase, IDBBase 33 | { 34 | public IDexieNETFactory DexieNETFactory { get; } 35 | } 36 | 37 | public sealed class DexieNETService(IJSRuntime jsRuntime) : IDexieNETService where T : DBBase, IDBBase 38 | { 39 | public IDexieNETFactory DexieNETFactory { get; } = new DexieNETFactory(jsRuntime); 40 | } 41 | 42 | public static class ServiceCollectionExtensions 43 | { 44 | public static IServiceCollection AddDexieNET(this IServiceCollection services) where T : DBBase, IDBBase 45 | { 46 | return services.AddSingleton, DexieNETService>(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/Put.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Put(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Put"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | 12 | var table = DB.Persons; 13 | await table.Clear(); 14 | 15 | var person = DataGenerator.GetPerson1(); 16 | 17 | var res = await table.Put(person); 18 | 19 | var personAdded = (await table.ToArray()).FirstOrDefault(); 20 | 21 | PersonComparer comparerNoID = new(true); 22 | 23 | if (!comparerNoID.Equals(person, personAdded)) 24 | { 25 | throw new InvalidOperationException("Item not identical."); 26 | } 27 | 28 | PersonComparer comparer = new(); 29 | 30 | person = personAdded with { Name = "Updated" }; 31 | res = await table.Put(person); 32 | 33 | personAdded = (await table.ToArray()).FirstOrDefault(); 34 | 35 | if (personAdded?.Name != "Updated") 36 | { 37 | throw new InvalidOperationException("Item not updated."); 38 | } 39 | 40 | await DB.Transaction(async _ => 41 | { 42 | await table.Clear(); 43 | res = await table.Put(person); 44 | }); 45 | 46 | personAdded = (await table.ToArray()).FirstOrDefault(); 47 | 48 | if (personAdded?.Name != "Updated") 49 | { 50 | throw new InvalidOperationException("Item not updated Transaction."); 51 | } 52 | 53 | return "OK"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DexieNETTest/Tests/Tests/DexieNETTests.cs: -------------------------------------------------------------------------------- 1 | using DexieNETTest.Tests.Infrastructure; 2 | using Xunit; 3 | 4 | namespace DexieNETTest.Tests.Tests 5 | { 6 | [CollectionDefinition("Chromium", DisableParallelization = true)] 7 | public class ChromiumCollection : ICollectionFixture 8 | { 9 | // This class has no code, and is never created. Its purpose is simply 10 | // to be the place to apply [CollectionDefinition] and all the 11 | // ICollectionFixture<> interfaces. 12 | } 13 | 14 | 15 | [Collection("Chromium")] 16 | public class ChromiumTest(ChromiumFixture fixture) : DexieNETTestBase(fixture) 17 | { 18 | } 19 | 20 | #if DEBUG 21 | [CollectionDefinition("Firefox", DisableParallelization = true)] 22 | public class FirefoxCollection : ICollectionFixture 23 | { 24 | // This class has no code, and is never created. Its purpose is simply 25 | // to be the place to apply [CollectionDefinition] and all the 26 | // ICollectionFixture<> interfaces. 27 | } 28 | 29 | [Collection("Firefox")] 30 | public class FirefoxTest(FirefoxFixture fixture) : DexieNETTestBase(fixture) 31 | { 32 | } 33 | 34 | #if !Windows 35 | [CollectionDefinition("Webkit", DisableParallelization = true)] 36 | public class WebkitCollection : ICollectionFixture 37 | { 38 | // This class has no code, and is never created. Its purpose is simply 39 | // to be the place to apply [CollectionDefinition] and all the 40 | // ICollectionFixture<> interfaces. 41 | } 42 | 43 | [Collection("Webkit")] 44 | public class WebkitTest : DexieNETTestBase 45 | { 46 | public WebkitTest(WebkitFixture fixture) : base(fixture) { } 47 | } 48 | #endif 49 | #endif 50 | } 51 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 30 |
31 | 32 | @code { 33 | private bool collapseNavMenu = true; 34 | 35 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 36 | 37 | private void ToggleNavMenu() 38 | { 39 | collapseNavMenu = !collapseNavMenu; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DexieNET/yarn/src/dexieNETLiveQuery.ts: -------------------------------------------------------------------------------- 1 | /* 2 | dexieNETLiveQuery.ts 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | import {liveQuery} from 'dexie'; 22 | import {from, Observable, Subscription} from 'rxjs'; 23 | 24 | const liveQueryObservables: { [id: number]: Observable; } = {}; 25 | const liveQuerySubscription: { [id: number]: Subscription; } = {}; 26 | 27 | // LiveQuery 28 | export function LiveQuerySubscribe(dotnetRef: any): number { 29 | 30 | const id = Date.now(); 31 | liveQueryObservables[id] = from(liveQuery(async () => { 32 | return await dotnetRef.invokeMethodAsync('LiveQueryCallback'); 33 | })); 34 | 35 | liveQuerySubscription[id] = liveQueryObservables[id].subscribe({ 36 | next: (v) => dotnetRef.invokeMethod('OnNextJS', v), 37 | error: (e) => dotnetRef.invokeMethod('OnErrorJS', e.message), 38 | complete: () => dotnetRef.invokeMethod('OnCompletedJS') 39 | }); 40 | 41 | return id; 42 | } 43 | 44 | export function LiveQueryUnsubscribe(id: number): void { 45 | 46 | if (id in liveQuerySubscription) { 47 | liveQuerySubscription[id].unsubscribe(); 48 | delete liveQuerySubscription[id]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DexieNETTest/Tests/Infrastructure/BrowserFixtures.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace DexieNETTest.Tests.Infrastructure 4 | { 5 | public class ChromiumFixture : WAFixtureBase, IWAFixture 6 | { 7 | public IWAFixture.BrowserType Type => IWAFixture.BrowserType.Chromium; 8 | public int Port => PortNumber; 9 | public bool OnePass => true; 10 | public bool Headless => true; 11 | 12 | private static int PortNumber => 7048; 13 | 14 | public ChromiumFixture() : base(PortNumber) { } 15 | 16 | public async Task InitializeAsync() 17 | { 18 | await InitializeAsync(Type, OnePass, Headless); 19 | } 20 | } 21 | 22 | public class FirefoxFixture : WAFixtureBase, IWAFixture 23 | { 24 | public IWAFixture.BrowserType Type => IWAFixture.BrowserType.Firefox; 25 | public int Port => PortNumber; 26 | public bool OnePass => true; 27 | public bool Headless => true; 28 | 29 | private static int PortNumber => 7049; 30 | 31 | public FirefoxFixture() : base(PortNumber) { } 32 | 33 | public async Task InitializeAsync() 34 | { 35 | await InitializeAsync(Type, OnePass, Headless); 36 | } 37 | } 38 | 39 | public class WebkitFixture : WAFixtureBase, IWAFixture 40 | { 41 | public IWAFixture.BrowserType Type => IWAFixture.BrowserType.Webkit; 42 | public int Port => PortNumber; 43 | public bool OnePass => true; 44 | public bool Headless => true; 45 | 46 | private static int PortNumber => 7050; 47 | 48 | public WebkitFixture() : base(PortNumber) { } 49 | 50 | public async Task InitializeAsync() 51 | { 52 | await InitializeAsync(Type, OnePass, Headless); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DexieNETTest/Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 11 | "applicationUrl": "http://localhost:5136" 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "launchBrowser": true, 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "dotnetRunMessages": true, 20 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 21 | "applicationUrl": "https://localhost:7293;http://localhost:5136" 22 | }, 23 | "IIS Express": { 24 | "commandName": "IISExpress", 25 | "launchBrowser": true, 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | }, 29 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 30 | }, 31 | "WSL": { 32 | "commandName": "WSL2", 33 | "launchBrowser": true, 34 | "launchUrl": "https://localhost:7293", 35 | "environmentVariables": { 36 | "ASPNETCORE_ENVIRONMENT": "Development", 37 | "ASPNETCORE_URLS": "https://localhost:7293;http://localhost:5136" 38 | }, 39 | "distributionName": "" 40 | } 41 | }, 42 | "iisSettings": { 43 | "windowsAuthentication": false, 44 | "anonymousAuthentication": true, 45 | "iisExpress": { 46 | "applicationUrl": "http://localhost:65220", 47 | "sslPort": 44350 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /DexieNETTest/Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 11 | "applicationUrl": "http://localhost:5136" 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "launchBrowser": true, 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "dotnetRunMessages": true, 20 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 21 | "applicationUrl": "https://localhost:7293;http://localhost:5136" 22 | }, 23 | "IIS Express": { 24 | "commandName": "IISExpress", 25 | "launchBrowser": true, 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | }, 29 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" 30 | }, 31 | "WSL": { 32 | "commandName": "WSL2", 33 | "launchBrowser": true, 34 | "launchUrl": "https://localhost:7293", 35 | "environmentVariables": { 36 | "ASPNETCORE_ENVIRONMENT": "Development", 37 | "ASPNETCORE_URLS": "https://localhost:7293;http://localhost:5136" 38 | }, 39 | "distributionName": "" 40 | } 41 | }, 42 | "iisSettings": { 43 | "windowsAuthentication": false, 44 | "anonymousAuthentication": true, 45 | "iisExpress": { 46 | "applicationUrl": "http://localhost:65220", 47 | "sslPort": 44350 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /DexieNETTest/Client/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /DexieCloudNET/DexieCloudNET/Cloud/DexieCloudNETUser.cs: -------------------------------------------------------------------------------- 1 | /* 2 | DexieNETCloudUser.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | namespace DexieCloudNET 22 | { 23 | public enum GrantType 24 | { 25 | DEMO, 26 | OTP 27 | } 28 | 29 | public record LoginInformation 30 | ( 31 | string EMail, 32 | string? UserId, 33 | GrantType GrantType 34 | ); 35 | 36 | public enum LicenseType 37 | { 38 | DEMO, 39 | EVAL, 40 | PROD, 41 | CLIENT 42 | } 43 | 44 | public enum LicenseStatus 45 | { 46 | OK, 47 | EXPIRED, 48 | DEACTIVATED 49 | } 50 | 51 | public record License 52 | ( 53 | LicenseType Type, 54 | LicenseStatus Status, 55 | DateTime? ValidUntil, 56 | int? EvalDaysLeft 57 | ); 58 | 59 | public record UserLogin(string UserId, string? Name, string? EMail, Dictionary Claims, 60 | License? License, DateTime LastLogin, 61 | string? AccessToken, DateTime? AccessTokenExpiration, string? RefreshToken, DateTime? RefreshTokenExpiration, 62 | string? NonExportablePrivateKey, string? PublicKey, bool IsLoggedIn); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/Services/FileSecretsConfigurationService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Text.Json; 3 | 4 | namespace DexieNETCloudPushServer.Services; 5 | 6 | using Microsoft.Extensions.Configuration; 7 | 8 | public class FilesSecretsConfigurationService(IServiceProvider serviceProvider) : ISecretsConfigurationService 9 | { 10 | private readonly IConfiguration _configuration = serviceProvider.GetRequiredService(); 11 | 12 | private record Secrets(string Key, string Value); 13 | public Dictionary GetSecrets() 14 | { 15 | Dictionary secretsDict = []; 16 | 17 | var databases = _configuration.GetSection("Databases").Get(); 18 | ArgumentNullException.ThrowIfNull(databases); 19 | var databasesJson = JsonSerializer.Serialize(databases, 20 | DatabaseConfigContext.Default.DatabaseConfigArray); 21 | secretsDict.Add("Databases", databasesJson); 22 | 23 | var vapidKeys = _configuration.GetSection("VapidKeys").Get(); 24 | ArgumentNullException.ThrowIfNull(vapidKeys); 25 | var vapidKeysJson = JsonSerializer.Serialize(vapidKeys, 26 | VapidKeysConfigContext.Default.VapidKeysConfig); 27 | secretsDict.Add("VapidKeys", vapidKeysJson); 28 | 29 | return secretsDict; 30 | } 31 | } 32 | 33 | public static class FilesSecretsConfigurationServiceExtensions 34 | { 35 | public static void AddFilesSecretsConfigurationService(this IServiceCollection services, ConfigurationManager configurationManager, string secretFileName) 36 | { 37 | configurationManager.AddJsonFile(secretFileName); 38 | services.AddSingleton(sp => new FilesSecretsConfigurationService(sp)); 39 | } 40 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits RxBLLayoutSubscriber 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | DexieNETCloudSample 20 | 21 | 22 | 23 | 24 | 25 | @Body 26 | 27 | 28 | 29 | 30 | @code { 31 | [Inject] 32 | public required IDialogService DialogService { get; init; } 33 | 34 | private bool _drawerOpen = true; 35 | private readonly MudTheme _theme = new(); 36 | 37 | void DrawerToggle() 38 | { 39 | _drawerOpen = !_drawerOpen; 40 | } 41 | 42 | private async Task OpenAboutDialog() 43 | { 44 | await DialogService.ShowAsync("About DexieNETCloudSample"); 45 | } 46 | 47 | private string GetColorThemeIcon() 48 | { 49 | return Service.LightMode.Value ? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode; 50 | } 51 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/ClearCount.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class ClearCount(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "ClearCount"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | var persons = DataGenerator.GetPersons(); 13 | await table.Clear(); 14 | 15 | await table.BulkAdd(persons); 16 | 17 | var countS = await table.Count(c => c.ToString()); 18 | if (countS != persons.Count().ToString()) 19 | { 20 | throw new InvalidOperationException("Count failed."); 21 | } 22 | 23 | var countAS = await table.Where(p => p.Age).Above(40).Count(c => c.ToString()); 24 | if (countAS != persons.Where(p => p.Age > 40).Count().ToString()) 25 | { 26 | throw new InvalidOperationException("Count failed."); 27 | } 28 | 29 | await table.Clear(); 30 | 31 | var res = await table.Count(); 32 | 33 | if (res != 0) 34 | { 35 | throw new InvalidOperationException("Clear failed."); 36 | } 37 | 38 | await DB.Transaction(async _ => 39 | { 40 | await table.Clear(); 41 | await table.BulkAdd(persons); 42 | await table.Clear(); // for yet unknown reasons when using Playwright with Webkit clear will not work here 43 | res = await table.Count(); 44 | }); 45 | 46 | if (res != 0) 47 | { 48 | throw new InvalidOperationException($"Transaction Clear failed E: 0, A: {res}."); 49 | } 50 | 51 | return "OK"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/Tests/ReservedStoreName.cs: -------------------------------------------------------------------------------- 1 | using DNTGenerator.Diagnostics; 2 | using Xunit; 3 | using Fixer = DNTGeneratorTest.Helpers.CSharpCodeFixVerifier; 4 | 5 | namespace DNTGeneratorTest.Tests 6 | { 7 | public class ResrvedStoreNameTests 8 | { 9 | //No diagnostics expected to show up 10 | [Fact] 11 | public async Task RecordEmpty() 12 | { 13 | var test = @""; 14 | 15 | await Fixer.VerifyAnalyzerAsync(test); 16 | } 17 | 18 | //No diagnostics expected to show up 19 | [Fact] 20 | public async Task RecordValidMembers() 21 | { 22 | var test = @$" 23 | using DexieNET; 24 | 25 | namespace Test 26 | {{ 27 | public partial record Members 28 | ( 29 | string NotIndexed 30 | ) : IDBStore; 31 | }} 32 | "; 33 | await Fixer.VerifyAnalyzerAsync(test); 34 | } 35 | 36 | [Fact] 37 | public async Task RecordInvalidMembers() 38 | { 39 | var test = @$" 40 | using DexieNET; 41 | 42 | namespace Test 43 | {{ 44 | [Schema(CloudSync = true)] 45 | public partial record {{|{GeneratorDiagnostic.ReservedStoreName.Id}:Members|}} 46 | ( 47 | string NotIndexed 48 | ) : IDBStore; 49 | }} 50 | "; 51 | await Fixer.VerifyAnalyzerAsync(test); 52 | } 53 | 54 | [Fact] 55 | public async Task RecordInvalidMembersAttribute() 56 | { 57 | var test = @$" 58 | using DexieNET; 59 | 60 | namespace Test 61 | {{ 62 | [Schema({{|{GeneratorDiagnostic.ReservedStoreName.Id}:StoreName = ""Members""|}}, CloudSync = true)] 63 | public partial record TestStore 64 | ( 65 | string NotIndexed 66 | ) : IDBStore; 67 | }} 68 | "; 69 | await Fixer.VerifyAnalyzerAsync(test); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/AddClass.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class AddClass(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "AddClass"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | DB.Close(); 12 | DB.Version(1).Stores(); 13 | await DB.Open(); 14 | 15 | var table = DB.PersonWithProperties; 16 | await table.Clear(); 17 | 18 | var person = new PersonWithProperties("FirstName", "LastName"); 19 | 20 | var key = await table.Add(person); 21 | var (firstName, lastName, id) = (await table.ToArray()).First(); 22 | 23 | if (firstName != person.FirstName || lastName != person.LastName || id != key) 24 | { 25 | throw new InvalidOperationException("Item invalid."); 26 | } 27 | 28 | await table.Clear(); 29 | var person1 = new PersonWithProperties("FirstName", "LastName"); 30 | 31 | var key1 = await table.Put(person1); 32 | var (firstName1, lastName1, id1) = (await table.ToArray()).First(); 33 | 34 | if (firstName1 != person1.FirstName || lastName1 != person1.LastName || id1 != key1) 35 | { 36 | throw new InvalidOperationException("Item invalid."); 37 | } 38 | 39 | person1.FirstName = "Update"; 40 | 41 | var key2 = await table.Put(person1); 42 | var (firstName2, lastName2, id2) = (await table.ToArray()).First(); 43 | 44 | if (firstName2 != person1.FirstName || lastName2 != person1.LastName || key1 != key2 || id2 != key2) 45 | { 46 | throw new InvalidOperationException("Item invalid."); 47 | } 48 | 49 | return "OK"; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/BulkDelete.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class BulkDelete(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "BulkDelete"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | var keys = await table.BulkAdd(persons, true); 16 | 17 | var evenKeys = keys.Where(i => i % 2 == 0); 18 | var oddKeys = keys.Where(i => i % 2 != 0); 19 | 20 | await table.BulkDelete(evenKeys); 21 | 22 | var keysRemain = (await table.ToArray()) 23 | .Select(p => p.Id) 24 | .Where(i => i is not null) 25 | .Select(i => (ulong)i!); 26 | 27 | if (!Enumerable.SequenceEqual(oddKeys, keysRemain)) 28 | { 29 | throw new InvalidOperationException("Items not identical."); 30 | } 31 | 32 | await DB.Transaction(async _ => 33 | { 34 | await table.Clear(); 35 | keys = await table.BulkAdd(persons, true); 36 | 37 | evenKeys = keys.Where(i => i % 2 == 0); 38 | await table.BulkDelete(evenKeys); 39 | 40 | keysRemain = (await table.ToArray()) 41 | .Select(p => p.Id) 42 | .Where(i => i is not null) 43 | .Select(i => (ulong)i!); 44 | }); 45 | 46 | oddKeys = keys.Where(i => i % 2 != 0); 47 | 48 | if (!Enumerable.SequenceEqual(oddKeys, keysRemain)) 49 | { 50 | throw new InvalidOperationException("Items not identical."); 51 | } 52 | 53 | return "OK"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Generell/LiveQueryWriteTest.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | using R3; 3 | 4 | namespace DexieNETTest.TestBase.Test 5 | { 6 | internal class LiveQueryWriteTest(TestDB db) : DexieTest(db) 7 | { 8 | public override string Name => "LiveQueryWriteTest"; 9 | 10 | public override async ValueTask RunTest() 11 | { 12 | var finished = false; 13 | var tablePersons = DB.Persons; 14 | await tablePersons.Clear(); 15 | var tableStudents = DB.Students; 16 | await tableStudents.Clear(); 17 | 18 | var student = new Student("Physics", -1); 19 | var keyStudent = await DB.Students.Add(student); 20 | 21 | var lqP = DB.LiveQuery(async () => 22 | { 23 | var values = await tablePersons.Where(p => p.Age).AboveOrEqual(50).ToArray(); 24 | return values; 25 | }); 26 | 27 | var disposableP = lqP.AsObservable.SubscribeAwait(async (values, ct) => 28 | { 29 | Console.WriteLine($"Count: {values.Count()}"); 30 | await tableStudents.Delete(keyStudent); 31 | finished = true; 32 | }); 33 | 34 | var persons = DataGenerator.GetPersons(); 35 | await DB.Persons.BulkAdd(persons); 36 | 37 | do 38 | { 39 | await Task.Delay(100); 40 | } while (!finished); 41 | 42 | var studentsCount = await tableStudents.Count(); 43 | 44 | if (studentsCount != 0) 45 | { 46 | throw new InvalidOperationException($"studentsCount : {studentsCount}-> LiveQueryWriteTest failed."); 47 | } 48 | 49 | disposableP.Dispose(); 50 | return "OK"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/TableFilter.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class TableFilter(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "TableFilter"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | await table.BulkAdd(persons); 16 | 17 | var oldPersonsData = persons.Where(p => p.Age > 30); 18 | var col = table.Filter(p => p.Age > 30); 19 | var oldPersons = await col.ToArray(); 20 | var oldPersonsCount = await col.Count(); 21 | 22 | if (!oldPersons.SequenceEqual(oldPersonsData, new PersonComparer(true))) 23 | { 24 | throw new InvalidOperationException("Items not identical."); 25 | } 26 | 27 | if (oldPersonsCount != oldPersonsData.Count()) 28 | { 29 | throw new InvalidOperationException("Items not identical."); 30 | } 31 | 32 | await DB.Transaction(async _ => 33 | { 34 | await table.Clear(); 35 | await table.BulkAdd(persons); 36 | var collection = table.Filter(p => p.Age > 30); 37 | oldPersons = await collection.ToArray(); 38 | oldPersonsCount = await collection.Count(); 39 | }); 40 | 41 | if (!oldPersons.SequenceEqual(oldPersonsData, new PersonComparer(true))) 42 | { 43 | throw new InvalidOperationException("Items not identical."); 44 | } 45 | 46 | if (oldPersonsCount != oldPersonsData.Count()) 47 | { 48 | throw new InvalidOperationException("Items not identical."); 49 | } 50 | 51 | return "OK"; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/Tests/AutoIncrementWithoutPrimary.cs: -------------------------------------------------------------------------------- 1 | using DNTGenerator.Diagnostics; 2 | using Xunit; 3 | using Fixer = DNTGeneratorTest.Helpers.CSharpCodeFixVerifier; 4 | 5 | namespace DNTGeneratorTest.Tests 6 | { 7 | public class IndexAutoWithoutPrimaryTests 8 | { 9 | //No diagnostics expected to show up 10 | [Fact] 11 | public async Task AutoWithoutPrimaryEmpty() 12 | { 13 | var test = @""; 14 | 15 | await Fixer.VerifyAnalyzerAsync(test); 16 | } 17 | 18 | [Fact] 19 | public async Task AutoWithoutPrimaryClassFix() 20 | { 21 | var test = @$" 22 | using DexieNET; 23 | 24 | namespace Test 25 | {{ 26 | public class Person : IDBStore 27 | {{ 28 | [Index({{|{GeneratorDiagnostic.AutoWithoutPrimaryKeyArgument.Id}:IsAuto = true|}})] ulong? ID {{ get; set; }} 29 | }} 30 | }} 31 | "; 32 | 33 | var fix = @" 34 | using DexieNET; 35 | 36 | namespace Test 37 | { 38 | public class Person : IDBStore 39 | { 40 | [Index(IsPrimary = true, IsAuto = true)] ulong? ID { get; set; } 41 | } 42 | } 43 | "; 44 | await Fixer.VerifyCodeFixAsync(test, fix, 0); 45 | } 46 | 47 | [Fact] 48 | public async Task AutoWithoutPrimaryRecordFix() 49 | { 50 | var test = @$" 51 | using DexieNET; 52 | 53 | namespace Test 54 | {{ 55 | public record Person 56 | ( 57 | [property: Index({{|{GeneratorDiagnostic.AutoWithoutPrimaryKeyArgument.Id}:IsAuto = true|}})] ulong? ID 58 | ) : IDBStore; 59 | }} 60 | "; 61 | 62 | var fix = @" 63 | using DexieNET; 64 | 65 | namespace Test 66 | { 67 | public record Person 68 | ( 69 | [property: Index(IsPrimary = true, IsAuto = true)] ulong? ID 70 | ) : IDBStore; 71 | } 72 | "; 73 | await Fixer.VerifyCodeFixAsync(test, fix); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/AddToDoItem.razor: -------------------------------------------------------------------------------- 1 | @inherits RxBLStateSubscriber 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Save 16 | 17 | 18 | 19 | 20 | 21 | 22 | @code { 23 | 24 | [CascadingParameter] 25 | public required IMudDialogInstance MudDialog { get; init; } 26 | 27 | [Parameter] 28 | public required ToDoDBItem? Item { get; init; } 29 | 30 | protected override async Task OnInitializedAsync() 31 | { 32 | var options = MudDialog.Options with 33 | { 34 | CloseButton = true 35 | }; 36 | 37 | await MudDialog.SetOptionsAsync(options); 38 | await base.OnInitializedAsync(); 39 | } 40 | 41 | private void OnValidSubmit(EditContext context) 42 | { 43 | MudDialog.Close(Owner); 44 | } 45 | } -------------------------------------------------------------------------------- /DexieCloudNET/DexieCloudNET/Cloud/DexieCloudNETShared.cs: -------------------------------------------------------------------------------- 1 | /* 2 | DexieNETCloudShared.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | using DexieNET; 22 | 23 | namespace DexieCloudNET 24 | { 25 | public interface IDBCloudEntity 26 | { 27 | string? Owner { get; } 28 | string? RealmId { get; } 29 | 30 | public string? EntityKey => GetEntityKey(); 31 | 32 | private string? GetEntityKey() 33 | { 34 | if (Owner is null || RealmId is null) 35 | { 36 | return null; 37 | } 38 | 39 | return Owner + RealmId; 40 | } 41 | } 42 | 43 | public record PushTrigger( 44 | string Message, 45 | string PushPayloadBase64, 46 | string Icon, 47 | DateTime? PushTimeUtc = null, 48 | bool RequireInteraction = false, 49 | int? IntervalMinutes = null, 50 | int? RepeatCount = null 51 | ); 52 | 53 | [Schema(CloudSync = true)] 54 | public record PushNotification( 55 | [property: Index(IsPrimary = true)] string ID, 56 | string Title, 57 | string NotifierRealm, 58 | PushTrigger[] Triggers, 59 | string? Tag = null, 60 | long? AppBadge = null, 61 | bool Expired = false, 62 | string? RealmId = null, 63 | string? Owner = null 64 | ) : IDBStore, IDBCloudEntity; 65 | } 66 | -------------------------------------------------------------------------------- /DexieNET/yarn/src/dexieNETTable.ts: -------------------------------------------------------------------------------- 1 | /* 2 | dexieNETTable.ts 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | import { IndexableType, IndexableTypeArrayReadonly, Table } from 'dexie'; 22 | 23 | // Wrappers 24 | export async function AddByteArray(table: Table, item: any, key?: IndexableType): Promise { 25 | let buffer: any = await table.add(item, key); 26 | return new Uint8Array(buffer); 27 | } 28 | 29 | export async function BulkAddByteArray(table: Table, item: IndexableTypeArrayReadonly, keys?: Uint8Array[], allKeys?: boolean): Promise { 30 | let options = allKeys ? { allKeys: allKeys } : undefined 31 | let buffer = await table.bulkAdd(item, keys, options); 32 | return allKeys == true ? (buffer as any[]).map(x => new Uint8Array(x)) : new Array(new Uint8Array(buffer as any)); 33 | } 34 | 35 | export async function PutByteArray(table: Table, item: any, key?: IndexableType): Promise { 36 | let buffer: any = await table.put(item, key); 37 | return new Uint8Array(buffer); 38 | } 39 | 40 | export async function BulkPutByteArray(table: Table, item: IndexableTypeArrayReadonly, keys?: Uint8Array[], allKeys?: boolean): Promise { 41 | let options = allKeys ? { allKeys: allKeys } : undefined 42 | let buffer = await table.bulkPut(item, keys, options); 43 | return allKeys == true ? (buffer as any[]).map(x => new Uint8Array(x)) : new Array(new Uint8Array(buffer as any)); 44 | } -------------------------------------------------------------------------------- /DexieNET/yarn/src/dexieNETTransactions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | dexieNETTransactions.ts 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | import Dexie, { Transaction, TransactionMode, Version } from 'dexie'; 22 | import { DB } from "./dexieNETBase"; 23 | 24 | // Version upgrade 25 | export function Upgrade(version: Version, dotnetRef: any): Version { 26 | return version.upgrade(_ => { 27 | dotnetRef.invokeMethod('UpgradeCallback'); 28 | }); 29 | } 30 | 31 | export function AbortCurrentTransaction(): void { 32 | Dexie.currentTransaction?.abort(); 33 | } 34 | 35 | export function AbortTransaction(transaction: Transaction | null): void { 36 | transaction?.abort(); 37 | } 38 | 39 | export function CurrentTransaction(): any | null { 40 | return Dexie.currentTransaction; 41 | } 42 | 43 | export function TopLevelTransaction(db: DB, tables: string[], mode: TransactionMode, dotnetRef: any): void { 44 | // @ts-ignore 45 | db.transaction(mode, tables, _ => dotnetRef.invokeMethod('TransactionCallback')); 46 | } 47 | 48 | export async function TopLevelTransactionAsync(db: DB, tables: string[], mode: TransactionMode, dotnetRef: any): Promise { 49 | await db.transaction(mode, tables, _ => dotnetRef.invokeMethod('TransactionCallback')); 50 | } 51 | 52 | export async function TransactioWaitFor(dotnetRef: any): Promise { 53 | await Dexie.waitFor(async () => await dotnetRef.invokeMethodAsync('TransactionWaitForCallback')); 54 | } -------------------------------------------------------------------------------- /DexieCloudNET/DexieCloudNET/Cloud/DexieCloudNETSync.cs: -------------------------------------------------------------------------------- 1 | /* 2 | DexieNETCloudSync.cs 3 | 4 | Copyright(c) 2024 Bernhard Straub 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | 'DexieNET' used with permission of David Fahlander 19 | */ 20 | 21 | namespace DexieCloudNET 22 | { 23 | public record SyncState(SyncState.SyncStatus Status, SyncState.SyncStatePhase Phase, int Progress, string? Error) 24 | { 25 | public enum SyncStatus 26 | { 27 | NOT_STARTED, 28 | CONNECTING, 29 | CONNECTED, 30 | DISCONNECTED, 31 | ERROR, 32 | OFFLINE 33 | } 34 | 35 | public enum SyncStatePhase 36 | { 37 | INITIAL, 38 | NOT_IN_SYNC, 39 | PUSHING, 40 | PULLING, 41 | IN_SYNC, 42 | ERROR, 43 | OFFLINE 44 | } 45 | } 46 | 47 | public record PersistedSyncState( 48 | string? ServerRevision, 49 | Dictionary LatestRevisions, 50 | string[] Realms, 51 | string[] InviteRealms, 52 | string ClientIdentity, 53 | bool? InitiallySynced, 54 | string? RemoteDbId, 55 | string[] SyncedTables, 56 | DateTime? Timestamp, 57 | string? Error 58 | ); 59 | 60 | public record SyncOptions(SyncOptions.SyncPurpose Purpose = SyncOptions.SyncPurpose.PUSH, bool Wait = true) 61 | { 62 | public enum SyncPurpose 63 | { 64 | PUSH, 65 | PULL 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/Services/BWSSecretsConfigurationService.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace DexieNETCloudPushServer.Services; 5 | 6 | using Bitwarden.Sdk; 7 | using Microsoft.Extensions.Configuration; 8 | 9 | public class BWSSecretsConfigurationService(IServiceProvider serviceProvider, string projectName) : ISecretsConfigurationService 10 | { 11 | private readonly IConfiguration _configuration = serviceProvider.GetRequiredService(); 12 | 13 | public Dictionary GetSecrets() 14 | { 15 | var accessToken = _configuration.GetSection("BWS_ACCESS_TOKEN").Value; 16 | ArgumentNullException.ThrowIfNull(accessToken); 17 | var organizationId = _configuration.GetSection("BWS_ORGANIZATION_ID").Value; 18 | ArgumentNullException.ThrowIfNull(organizationId); 19 | 20 | using var client = new BitwardenClient(); 21 | client.AccessTokenLogin(accessToken); 22 | 23 | var organizationGuid = Guid.Parse(organizationId); 24 | 25 | var projects = client.Projects.List(organizationGuid); 26 | var projectId = projects.Data.Where(p => p.Name == projectName).Select(p => p.Id).First(); 27 | 28 | var secrets = client.Secrets.List(organizationGuid); 29 | var secretValues = secrets.Data.Select(x => client.Secrets.Get(x.Id)).Where(x => x.ProjectId == projectId); 30 | 31 | Dictionary secretsDict = []; 32 | 33 | foreach (var secretValue in secretValues) 34 | { 35 | secretsDict.Add(secretValue.Key, secretValue.Value); 36 | } 37 | 38 | return secretsDict; 39 | } 40 | } 41 | 42 | public static class BWSSecretsConfigurationServiceExtensions 43 | { 44 | public static void AddBWSSecretsConfigurationService(this IServiceCollection services, string projectName) 45 | { 46 | services.AddSingleton(sp => new BWSSecretsConfigurationService(sp, projectName)); 47 | } 48 | } 49 | #endif -------------------------------------------------------------------------------- /DexieNETTableGenerator/DexieNETTableGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | enable 5 | 13.0 6 | True 7 | True 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | $(GetTargetPathDependsOn);GetDependencyTargetPaths 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/Tests/NonMultiEntryNotArray.cs: -------------------------------------------------------------------------------- 1 | using DNTGenerator.Diagnostics; 2 | using Xunit; 3 | using Fixer = DNTGeneratorTest.Helpers.CSharpCodeFixVerifier; 4 | 5 | namespace DNTGeneratorTest.Tests 6 | { 7 | public class NonMultiEntryNotArrayTests 8 | { 9 | //No diagnostics expected to show up 10 | [Fact] 11 | public async Task NonMultiEntryNotArrayEmpty() 12 | { 13 | var test = @""; 14 | 15 | await Fixer.VerifyAnalyzerAsync(test); 16 | } 17 | 18 | [Fact] 19 | public async Task NonMultiEntryNotArrayClassFix() 20 | { 21 | var test = @$" 22 | using System.Collections.Generic; 23 | using DexieNET; 24 | 25 | namespace Test 26 | {{ 27 | public partial class NonMultiEntry : IDBStore 28 | {{ 29 | [Index] IEnumerable {{|{GeneratorDiagnostic.NonMultiEntryNotArray.Id}:NME|}} {{ get; set; }} 30 | }} 31 | }} 32 | "; 33 | 34 | var fix = @" 35 | using System.Collections.Generic; 36 | using DexieNET; 37 | 38 | namespace Test 39 | { 40 | public partial class NonMultiEntry : IDBStore 41 | { 42 | [Index] ulong[] NME { get; set; } 43 | } 44 | } 45 | "; 46 | await Fixer.VerifyCodeFixAsync(test, fix); 47 | } 48 | 49 | [Fact] 50 | public async Task NonMultiEntryNotArrayRecordFix() 51 | { 52 | var test = @$" 53 | using System.Collections.Generic; 54 | using DexieNET; 55 | 56 | namespace Test 57 | {{ 58 | public partial record NonMultiEntry 59 | ( 60 | [property: Index] IEnumerable {{|{GeneratorDiagnostic.NonMultiEntryNotArray.Id}:NME|}} 61 | ) : IDBStore; 62 | }} 63 | "; 64 | 65 | var fix = @" 66 | using System.Collections.Generic; 67 | using DexieNET; 68 | 69 | namespace Test 70 | { 71 | public partial record NonMultiEntry 72 | ( 73 | [property: Index] ulong[] NME 74 | ) : IDBStore; 75 | } 76 | "; 77 | await Fixer.VerifyCodeFixAsync(test, fix); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Collection/LimitOffset.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class LimitOffset(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "LimitOffset"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | const int limit = 5; 12 | 13 | PersonComparer comparer = new(true); 14 | 15 | var table = DB.Persons; 16 | await table.Clear(); 17 | 18 | var persons = DataGenerator.GetPersonsRandom(limit * 10); 19 | await table.BulkAdd(persons); 20 | 21 | var dataLimited = persons.Take(limit); 22 | var limited = await table.Limit(limit).ToArray(); 23 | 24 | if (!limited.SequenceEqual(dataLimited, comparer)) 25 | { 26 | throw new InvalidOperationException("Items not identical."); 27 | } 28 | 29 | var dataOffseted = persons.Skip(limit * 9); 30 | var offseted = await table.Offset(limit * 9).ToArray(); 31 | 32 | if (!offseted.SequenceEqual(dataOffseted, comparer)) 33 | { 34 | throw new InvalidOperationException("Items not identical."); 35 | } 36 | 37 | await DB.Transaction(async _ => 38 | { 39 | await table.Clear(); 40 | await table.BulkAdd(persons); 41 | 42 | var collection = table.Limit(limit); 43 | limited = await collection.ToArray(); 44 | 45 | collection = table.Offset(limit * 9); 46 | offseted = await collection.ToArray(); 47 | }); 48 | 49 | if (!limited.SequenceEqual(dataLimited, comparer)) 50 | { 51 | throw new InvalidOperationException("Items not identical."); 52 | } 53 | 54 | if (!offseted.SequenceEqual(dataOffseted, comparer)) 55 | { 56 | throw new InvalidOperationException("Items not identical."); 57 | } 58 | 59 | return "OK"; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Generell/Reverse.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Reverse(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Reverse"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | PersonComparer comparer = new(true); 12 | 13 | var table = DB.Persons; 14 | await table.Clear(); 15 | 16 | var persons = DataGenerator.GetPersons(); 17 | await table.BulkAdd(persons); 18 | 19 | var dataReversed = persons.Reverse(); 20 | var reversed = await table.Reverse().ToArray(); 21 | 22 | if (!reversed.SequenceEqual(dataReversed, comparer)) 23 | { 24 | throw new InvalidOperationException("Items not identical."); 25 | } 26 | 27 | var dataReversedO = persons.OrderBy(p => p.Age).Reverse(); 28 | var reversedO = await table.OrderBy(p => p.Age).Reverse().ToArray(); 29 | 30 | if (!reversedO.SequenceEqual(dataReversedO, comparer)) 31 | { 32 | throw new InvalidOperationException("Items not identical."); 33 | } 34 | 35 | await DB.Transaction(async _ => 36 | { 37 | await table.Clear(); 38 | await table.BulkAdd(persons); 39 | 40 | var collection = table.Reverse(); 41 | reversed = await collection.ToArray(); 42 | 43 | var collectionAge = table.OrderBy(p => p.Age); 44 | collectionAge.Reverse(); 45 | reversedO = await collectionAge.ToArray(); 46 | }); 47 | 48 | if (!reversed.SequenceEqual(dataReversed, comparer)) 49 | { 50 | throw new InvalidOperationException("Items not identical."); 51 | } 52 | 53 | if (!reversedO.SequenceEqual(dataReversedO, comparer)) 54 | { 55 | throw new InvalidOperationException("Items not identical."); 56 | } 57 | 58 | return "OK"; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Collection/CollectionFilter.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class CollectionFilter(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "CollectionFilter"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | await table.BulkAdd(persons); 16 | 17 | var oldBuddysData = persons.Where(p => p.Tags.Contains("Buddy") && p.Age > 30); 18 | var col = table.ToCollection().Filter(p => p.Tags.Contains("Buddy")).Filter(p => p.Age > 30); 19 | var oldBuddys = await col.ToArray(); 20 | var oldBuddysCount = await col.Count(); 21 | 22 | if (!oldBuddys.SequenceEqual(oldBuddysData, new PersonComparer(true))) 23 | { 24 | throw new InvalidOperationException("Items not identical."); 25 | } 26 | 27 | if (oldBuddysCount != oldBuddysData.Count()) 28 | { 29 | throw new InvalidOperationException("Items not identical."); 30 | } 31 | 32 | await DB.Transaction(async _ => 33 | { 34 | await table.Clear(); 35 | await table.BulkAdd(persons); 36 | var collection = table.ToCollection(); 37 | collection.Filter(p => p.Tags.Contains("Buddy")); 38 | collection.Filter(p => p.Age > 30); 39 | oldBuddysData = await collection.ToArray(); 40 | oldBuddysCount = await collection.Count(); 41 | }); 42 | 43 | if (!oldBuddysData.SequenceEqual(oldBuddysData, new PersonComparer(true))) 44 | { 45 | throw new InvalidOperationException("Items not identical."); 46 | } 47 | 48 | if (oldBuddysCount != oldBuddysData.Count()) 49 | { 50 | throw new InvalidOperationException("Items not identical."); 51 | } 52 | 53 | return "OK"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/Tests/RecordCompoundIndexNotFound.cs: -------------------------------------------------------------------------------- 1 | using DNTGenerator.Diagnostics; 2 | using Xunit; 3 | using Fixer = DNTGeneratorTest.Helpers.CSharpCodeFixVerifier; 4 | 5 | namespace DNTGeneratorTest.Tests 6 | { 7 | public class RecordCompoundIndexNotFoundTests 8 | { 9 | //No diagnostics expected to show up 10 | [Fact] 11 | public async Task RecordEmpty() 12 | { 13 | var test = @""; 14 | 15 | await Fixer.VerifyAnalyzerAsync(test); 16 | } 17 | 18 | //No diagnostics expected to show up 19 | [Fact] 20 | public async Task RecordCloud() 21 | { 22 | var test = @$" 23 | using DexieNET; 24 | 25 | namespace Test 26 | {{ 27 | [Schema(CloudSync = true)] 28 | [CompoundIndex(""FirstName"", ""RealmId"")] 29 | public partial class Person : IDBStore 30 | {{ 31 | string LastName1 {{ get; set; }} 32 | string FirstName {{ get; set; }} 33 | }} 34 | }} 35 | "; 36 | await Fixer.VerifyAnalyzerAsync(test); 37 | } 38 | 39 | [Fact] 40 | public async Task ClassCompoundIndexNotFound() 41 | { 42 | var test = @$" 43 | using DexieNET; 44 | 45 | namespace Test 46 | {{ 47 | [CompoundIndex(""FirstName"", {{|{GeneratorDiagnostic.CompoundIndexNotFound.Id}:""LastName""|}}, IsPrimary = true)] 48 | public partial class Person : IDBStore 49 | {{ 50 | string LastName1 {{ get; set; }} 51 | string FirstName {{ get; set; }} 52 | }} 53 | }} 54 | "; 55 | await Fixer.VerifyAnalyzerAsync(test); 56 | } 57 | 58 | [Fact] 59 | public async Task RecordCompoundIndexNotFound() 60 | { 61 | var test = @$" 62 | using DexieNET; 63 | 64 | namespace Test 65 | {{ 66 | [CompoundIndex({{|{GeneratorDiagnostic.CompoundIndexNotFound.Id}:""FirstName""|}}, ""LastName"", IsPrimary = true)] 67 | public partial record Person 68 | ( 69 | string LastName, 70 | string FirstName1 71 | ) : IDBStore; 72 | }} 73 | "; 74 | await Fixer.VerifyAnalyzerAsync(test); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/AddComplex.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class AddComplex(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "AddComplex"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var tablePerson = DB.Persons; 12 | var tableStudents = DB.Students; 13 | 14 | await tablePerson.Clear(); 15 | await tableStudents.Clear(); 16 | 17 | var person = DataGenerator.GetPerson1(); 18 | 19 | var keyP = await tablePerson.Add(person); 20 | var student = new Student("Physics", (int)keyP); 21 | var keyS = await tableStudents.Add(student); 22 | 23 | var personAdded = await tablePerson.Get(keyP); 24 | var studentAdded = await tableStudents.Get(keyS); 25 | 26 | PersonComparer pComparer = new(true); 27 | 28 | if (!pComparer.Equals(person, personAdded)) 29 | { 30 | throw new InvalidOperationException("Item not identical."); 31 | } 32 | 33 | StudentComparer sComparer = new(true); 34 | 35 | if (!sComparer.Equals(student, studentAdded)) 36 | { 37 | throw new InvalidOperationException("Item not identical."); 38 | } 39 | 40 | await DB.Transaction(async _ => 41 | { 42 | await tablePerson.Clear(); 43 | await tableStudents.Clear(); 44 | 45 | keyP = await tablePerson.Add(person); 46 | 47 | student = new Student("Physics", (int)keyP); 48 | var keyS = await tableStudents.Add(student); 49 | 50 | personAdded = await tablePerson.Get(keyP); 51 | studentAdded = await tableStudents.Get(keyS); 52 | }); 53 | 54 | if (!pComparer.Equals(person, personAdded)) 55 | { 56 | throw new InvalidOperationException("Item not identical"); 57 | } 58 | 59 | if (!sComparer.Equals(student, studentAdded)) 60 | { 61 | throw new InvalidOperationException("Item not identical."); 62 | } 63 | 64 | return "OK"; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using DexieNETCloudPushServer.Quartz; 4 | using DexieNETCloudPushServer.Services; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | using Quartz; 9 | using LogLevel = Microsoft.Extensions.Logging.LogLevel; 10 | 11 | var builder = Host.CreateApplicationBuilder(args); 12 | 13 | builder.Logging.ClearProviders(); 14 | builder.Logging.AddSimpleConsole(o => o.TimestampFormat = "G"); 15 | #if !DEBUG 16 | builder.Logging.AddFilter("DexieNETCloudPushServer", LogLevel.Information); 17 | builder.Logging.AddFilter("Microsoft", LogLevel.Warning); 18 | builder.Logging.AddFilter("System", LogLevel.Warning); 19 | builder.Logging.AddFilter("Polly", LogLevel.Warning); 20 | builder.Logging.AddFilter("Quartz", LogLevel.Warning); 21 | #else 22 | builder.Logging.AddFilter("DexieNETCloudPushServer", LogLevel.Debug); 23 | builder.Logging.AddFilter("Microsoft", LogLevel.Information); 24 | builder.Logging.AddFilter("System", LogLevel.Information); 25 | builder.Logging.AddFilter("Polly", LogLevel.Information); 26 | builder.Logging.AddFilter("Quartz", LogLevel.Information); 27 | #endif 28 | 29 | var dbFullName = Path.Combine(QuartzDBContext.DbPath, QuartzDBContext.DbName); 30 | var sqlite = $"Data Source={dbFullName};"; 31 | builder.Services.AddDbContext(ServiceLifetime.Singleton, ServiceLifetime.Singleton); 32 | 33 | #if DEBUG 34 | builder.Services.AddBWSSecretsConfigurationService("DexieNETCloud"); 35 | #else 36 | var secretsFullName = Path.Combine(QuartzDBContext.DbPath, "secrets.json"); 37 | builder.Services.AddFilesSecretsConfigurationService(builder.Configuration, secretsFullName); 38 | #endif 39 | builder.Services.AddHttpClient("PushClient"); 40 | //.AddStandardResilienceHandler(); 41 | 42 | builder.Services.AddSingleton(); 43 | builder.Services.AddHostedService(sp => sp.GetRequiredService()); 44 | builder.Services.AddQuartz(q => 45 | { 46 | q.UsePersistentStore(o => 47 | { 48 | o.PerformSchemaValidation = true; 49 | o.UseMicrosoftSQLite(sqlite); 50 | o.UseNewtonsoftJsonSerializer(); 51 | }); 52 | }); 53 | 54 | builder.Services.AddQuartzHostedService(opt => { opt.WaitForJobsToComplete = true; }); 55 | 56 | using var host = builder.Build(); 57 | await host.RunAsync(); -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Components/DBComponentTest.razor: -------------------------------------------------------------------------------- 1 | @inherits DexieNET 2 | @using Microsoft.AspNetCore.Components.Forms 3 | 4 |

A very simple liveQuery() example

5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | 40 | 41 | 44 | 45 | 48 | 49 | 52 |
53 | 54 | 55 |
56 | 57 |

Friends

58 | 59 |
60 | 61 | 62 |
63 | 64 |
65 | 66 |

Friends sorted by name

67 | 68 |
69 | 70 | 72 | 73 | 74 |
75 |

76 | 77 |

-------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/BulkAdd.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class BulkAdd(TestDB db, bool allKeys = false) : DexieTest(db) 6 | { 7 | public override string Name => AllKeys ? "BulkAddAllKeys" : "BulkAdd"; 8 | 9 | public bool AllKeys { get; private set; } = allKeys; 10 | 11 | public override async ValueTask RunTest() 12 | { 13 | var table = DB.Persons; 14 | await table.Clear(); 15 | 16 | var persons = DataGenerator.GetPersons(); 17 | var keys = await table.BulkAdd(persons, AllKeys); 18 | 19 | if (persons.Count() != DataGenerator.GetPersons().Count()) 20 | { 21 | throw new InvalidOperationException("Count mismatch."); 22 | } 23 | 24 | if (AllKeys) 25 | { 26 | if (keys.Count() != DataGenerator.GetPersons().Count()) 27 | { 28 | throw new InvalidOperationException("Keys mismatch."); 29 | } 30 | } 31 | 32 | var personsAdded = (await table.ToArray()); 33 | 34 | if (!persons.SequenceEqual(personsAdded, new PersonComparer(true))) 35 | { 36 | throw new InvalidOperationException("Items not identical."); 37 | } 38 | 39 | await DB.Transaction(async _ => 40 | { 41 | await table.Clear(); 42 | await table.BulkAdd(persons, AllKeys); 43 | }); 44 | 45 | if (persons.Count() != DataGenerator.GetPersons().Count()) 46 | { 47 | throw new InvalidOperationException("Count mismatch Transaction."); 48 | } 49 | 50 | if (AllKeys) 51 | { 52 | if (keys?.Count() != DataGenerator.GetPersons().Count()) 53 | { 54 | throw new InvalidOperationException("Keys mismatch Transaction."); 55 | } 56 | } 57 | 58 | personsAdded = (await table.ToArray()); 59 | 60 | if (!persons.SequenceEqual(personsAdded, new PersonComparer(true))) 61 | { 62 | throw new InvalidOperationException("Items not identical Transaction."); 63 | } 64 | 65 | return "OK"; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/Update.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Update(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Update"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var person = DataGenerator.GetPerson1(); 15 | 16 | var res = await table.Add(person); 17 | var personAdded = (await table.ToArray()).FirstOrDefault(); 18 | 19 | PersonComparer comparer = new(true); 20 | if (!comparer.Equals(person, personAdded)) 21 | { 22 | throw new InvalidOperationException("Item not identical."); 23 | } 24 | 25 | if (personAdded?.Id is null || personAdded.Id != res) 26 | { 27 | throw new InvalidOperationException("Updated preparation failed."); 28 | } 29 | 30 | var updateRes = await table.Update(res, p => p.Name, "Updated", p => p.Address.Street, "Updated"); 31 | 32 | if (!updateRes) 33 | { 34 | throw new InvalidOperationException("Updated failed."); 35 | } 36 | 37 | var personUpdated = (await table.ToArray()).FirstOrDefault(); 38 | 39 | if (personUpdated?.Name != "Updated" || personUpdated?.Address.Street != "Updated") 40 | { 41 | throw new InvalidOperationException("Item not updated."); 42 | } 43 | 44 | await DB.Transaction(async _ => 45 | { 46 | await table.Clear(); 47 | res = await table.Add(person); 48 | updateRes = await table.Update(res, p => p.Name, "Updated", p => p.Address.Street, "Updated"); 49 | }); 50 | 51 | if (!updateRes) 52 | { 53 | throw new InvalidOperationException("Updated failed."); 54 | } 55 | 56 | personUpdated = (await table.ToArray()).FirstOrDefault(); 57 | 58 | if (personUpdated?.Name != "Updated" || personUpdated?.Address.Street != "Updated") 59 | { 60 | throw new InvalidOperationException("Item not updated."); 61 | } 62 | 63 | return "OK"; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Collection/CollectionPrimaryKeys.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class CollectionPrimaryKeys(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "CollectionPrimaryKeys"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | await table.Clear(); 13 | 14 | var persons = DataGenerator.GetPersons(); 15 | await table.BulkAdd(persons); 16 | 17 | var personsData = await table.ToArray(); 18 | 19 | var primaryKeysData = personsData 20 | .Where(p => p.Id is not null) 21 | .Select(p => (ulong)p.Id!); 22 | 23 | var primaryKeys = await table.ToCollection().PrimaryKeys(); 24 | 25 | if (!primaryKeysData.SequenceEqual(primaryKeys)) 26 | { 27 | throw new InvalidOperationException("Items not identical."); 28 | } 29 | 30 | var primaryKeysDataAge = personsData 31 | .Where(p => p.Age > 30 && p.Id is not null) 32 | .Select(p => (ulong)p.Id!) 33 | .OrderBy(p => p); 34 | 35 | var primaryKeysAge = await table.Where(p => p.Age).Above(30).PrimaryKeys(); 36 | 37 | if (!primaryKeysDataAge.SequenceEqual(primaryKeysAge.OrderBy(p => p))) 38 | { 39 | throw new InvalidOperationException("Items not identical."); 40 | } 41 | 42 | await DB.Transaction(async _ => 43 | { 44 | var collection = table.ToCollection(); 45 | primaryKeys = await collection.PrimaryKeys(); 46 | 47 | var whereClause = table.Where(p => p.Age); 48 | var collectionAbove = whereClause.Above(30); 49 | primaryKeysAge = await collectionAbove.PrimaryKeys(); 50 | }); 51 | 52 | if (!primaryKeysData.SequenceEqual(primaryKeys)) 53 | { 54 | throw new InvalidOperationException("Items not identical."); 55 | } 56 | 57 | if (!primaryKeysDataAge.SequenceEqual(primaryKeysAge.OrderBy(p => p))) 58 | { 59 | throw new InvalidOperationException("Items not identical."); 60 | } 61 | 62 | return "OK"; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DexieNETTest/TableGeneratorTest/Helpers/GeneratorFactory.cs: -------------------------------------------------------------------------------- 1 | using DNTGenerator; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.Text; 5 | using System.Collections.Immutable; 6 | using System.Text; 7 | 8 | namespace DexieNETTableGeneratorTest.Helpers 9 | { 10 | internal class GeneratorFactory 11 | { 12 | public static IEnumerable<(string Name, SourceText Source)> RunGenerator(params string[] sources) 13 | { 14 | List syntaxTrees = new(); 15 | 16 | foreach (string? source in sources) 17 | { 18 | string? st = source; 19 | SyntaxTree? syntaxTree = CSharpSyntaxTree.ParseText(SourceText.From(st, Encoding.UTF8), CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp11)); 20 | syntaxTrees.Add(syntaxTree); 21 | } 22 | 23 | CSharpCompilationOptions? compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) 24 | .WithNullableContextOptions(NullableContextOptions.Enable) 25 | .WithOptimizationLevel(OptimizationLevel.Debug) 26 | .WithGeneralDiagnosticOption(ReportDiagnostic.Default); 27 | 28 | var references = AppDomain.CurrentDomain.GetAssemblies() 29 | .Where(assembly => !assembly.IsDynamic) 30 | .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) 31 | .Cast(); 32 | 33 | Compilation compilation = CSharpCompilation.Create("testgenerator", syntaxTrees, references, compilationOptions); 34 | CSharpParseOptions? parseOptions = syntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions; 35 | 36 | Generator? generator = new(); 37 | 38 | GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(generator.AsSourceGenerator()), parseOptions: parseOptions); 39 | driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation? generatorCompilation, out ImmutableArray generatorDiagnostics); 40 | 41 | var t = generatorCompilation.SyntaxTrees.FirstOrDefault()?.ToString(); 42 | return generatorCompilation.SyntaxTrees.Skip(1).Select((s, i) => ($"Generated{i}", SourceText.From(s.ToString()))); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Administration/Administration.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using MudBlazor; 3 | using DexieNETCloudSample.Dialogs; 4 | using DexieNETCloudSample.Logic; 5 | using RxBlazorLightCore; 6 | using RxMudBlazorLight.Extensions; 7 | 8 | namespace DexieNETCloudSample.Administration 9 | { 10 | public partial class Administration 11 | { 12 | [Inject] 13 | public required IDialogService DialogService { get; init; } 14 | 15 | private string _notification = "Important message to all users! An update is available!"; 16 | 17 | private Func GetUsers => async stateCommandAsync => 18 | { 19 | CloudKeyData data = new("clientId", "clientSecret"); 20 | 21 | var parameters = new DialogParameters { ["Item"] = data }; 22 | var dialog = await DialogService.ShowAsync("Cloud Client Keys", parameters); 23 | 24 | var result = await dialog.Result; 25 | if (result.OK()) 26 | { 27 | stateCommandAsync.NotifyChanging(); 28 | await Task.Delay(2000, stateCommandAsync.CancellationToken); 29 | var cloudKeyData = (CloudKeyData?)result.Data; 30 | if (cloudKeyData is not null) 31 | { 32 | await stateCommandAsync.ExecuteAsync(Service1.GetUsers(data)); 33 | } 34 | } 35 | }; 36 | 37 | private string GetExceptions() 38 | { 39 | return Service1.Exceptions.Aggregate("", (p, n) => p + n.Exception.Message + ", ").TrimEnd([' ', ',']); 40 | } 41 | 42 | private async Task ExpirePushSubscriptions() 43 | { 44 | var parameters = new DialogParameters 45 | { ["Message"] = $"Expire PushSubscriptions?", ["ConfirmButton"] = "Expire", ["SuccessOnConfirm"] = false }; 46 | var dialog = await DialogService.ShowAsync("PushSubscriptions", parameters); 47 | 48 | var res = await dialog.Result; 49 | 50 | if (!res.OK()) 51 | { 52 | return; 53 | } 54 | 55 | await Service1.ExpireAllPushSubscriptions(); 56 | } 57 | 58 | private async Task SendPushNotification() 59 | { 60 | await Service1.DBService.SendPushNotification(_notification.MakeLines()); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/AuthenticateOTP.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @if (Item.Alerts.Any()) 8 | { 9 | 10 | 11 | @foreach (var alert in Item.Alerts.OrderByDescending(a => a.Type)) 12 | { 13 | Color color = alert.Type switch 14 | { 15 | UIAlert.AlertType.INFO => Color.Info, 16 | UIAlert.AlertType.WARNING => Color.Warning, 17 | UIAlert.AlertType.ERROR => Color.Error, 18 | _ => throw new ArgumentOutOfRangeException(alert.Type.ToString()) 19 | }; 20 | 21 | @alert.GetMessage() 22 | } 23 | 24 | 25 | } 26 | 27 | 28 | Item.OTP) /> 29 | 30 | 31 | Submit 32 | 33 | 34 | 35 | 36 | 37 | 38 | @code { 39 | 40 | [CascadingParameter] 41 | public required IMudDialogInstance MudDialog { get; init; } 42 | 43 | [Parameter, EditorRequired] 44 | public required OTPData Item { get; init; } 45 | 46 | protected override async Task OnInitializedAsync() 47 | { 48 | var options = MudDialog.Options with 49 | { 50 | CloseButton = true 51 | }; 52 | 53 | await MudDialog.SetOptionsAsync(options); 54 | await base.OnInitializedAsync(); 55 | } 56 | 57 | private void OnValidSubmit(EditContext context) 58 | { 59 | MudDialog.Close(DialogResult.Ok(Item)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DexieNETCloudPushServer/README.md: -------------------------------------------------------------------------------- 1 | DexieCloudNETPushServer 2 | ======== 3 | 4 | DexieCloudNETPushServer is a helper application that supports 5 | WebPush messages in a DexieCloudNET based solution. 6 | 7 | The PushServer will use the [Dexie Cloud REST API](https://dexie.org/cloud/docs/rest-api) to communicate with any database in a solution agnostic way. 8 | The best way to set up your own PushServer is with Docker. 9 | Create a Docker image using the [buildPushServer.sh](../buildPushServer.sh) script. 10 | Make sure *--platform* matches your desired architecture. 11 | 12 | **For new releases please expire all subscriptions, and if push still doesn't work, delete and reinstall your PWA!** 13 | 14 | Setting up the app's secrets is a two-step process: 15 | * map any local directory to */pushserver/database/* 16 | * Place a *secrets.json* file inside the mounted directory with the following content 17 | ```json 18 | { 19 | "Databases": [ 20 | { 21 | "url": "your dexie cloud database url", 22 | "clientId": "your dexie cloud client id", 23 | "clientSecret": "your dexie cloud client secret" 24 | } 25 | ], 26 | "VapidKeys": { 27 | "publicKey": "Your Vapid public key", 28 | "privateKey": "your Vapid private key" 29 | } 30 | } 31 | ``` 32 | * create a settings file (rootFolder may be different for *development* and *production*) with the vapid public key and the root folder of the pwa, e.g. 33 | ```json 34 | { 35 | "applicationServerKey": "your Vapid public key", 36 | "rootFolder": "your folder for the pwa" 37 | } 38 | ``` 39 | In any case, the pushURL and vapid public key must be added when configuring the cloud database. 40 | Make sure that the resulting push URL will be correctly decoded on one of your pages. 41 | 42 | ```csharp 43 | public static async Task ConfigureCloud(this DBBase dexie, DexieCloudOptions cloudOptions, 44 | string pushURL, string? applicationServerKey = null) 45 | ``` 46 | 47 | See the [DexieNETCloudSample](../DexieNETCloudSample) for more information. 48 | 49 | To enable push support, do the following: 50 | * add the [DBAddPushSupport](https://github.com/b-straub/DexieNET/blob/9e0915b38995bce0660229c2b77cc86bc7b6a058/DexieNETCloudSample/Dexie/Services/DexieCloudService.cs#L15) attribute to your *IDBStore 51 | * Store push information in your database like this [AddPushNotification](https://github.com/b-straub/DexieNET/blob/9e0915b38995bce0660229c2b77cc86bc7b6a058/DexieNETCloudSample/Dexie/Services/ToDoItemService.cs#L187) -------------------------------------------------------------------------------- /DexieNETCloudPushServer/Services/CloudRecords.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DexieCloudNET; 3 | 4 | namespace DexieNETCloudPushServer.Services; 5 | 6 | public record PushEntity( 7 | string ID 8 | ); 9 | 10 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.Never)] 11 | [JsonSerializable(typeof(PushEntity[]))] 12 | public partial class PushEntityContext : JsonSerializerContext 13 | { 14 | } 15 | 16 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.Never)] 17 | [JsonSerializable(typeof(PushNotification[]))] 18 | public partial class PushNotificationContext : JsonSerializerContext 19 | { 20 | } 21 | 22 | public record PushMember(string? UserId); 23 | 24 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.Never)] 25 | [JsonSerializable(typeof(PushMember[]))] 26 | public partial class PushMemberContext : JsonSerializerContext 27 | { 28 | } 29 | 30 | // ReSharper disable once InconsistentNaming 31 | public record WebPushNotification(string Title, string Body, string Navigate, string? Tag = null, long? App_badge = null, string? Icon = null, bool? RequireInteraction = null); 32 | 33 | // ReSharper disable once InconsistentNaming 34 | public record DeclarativeWebPushNotification(int? Web_push, WebPushNotification Notification) 35 | { 36 | public static int DeclarativeWebPushMagicNumber = 8030; 37 | } 38 | 39 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] 40 | [JsonSerializable(typeof(DeclarativeWebPushNotification))] 41 | public partial class DeclarativeWebPushNotificationContext : JsonSerializerContext 42 | { 43 | } 44 | 45 | public record DatabaseConfig(string Url, string ClientId, string ClientSecret); 46 | 47 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] 48 | [JsonSerializable(typeof(DatabaseConfig[]))] 49 | public partial class DatabaseConfigContext : JsonSerializerContext 50 | { 51 | } 52 | public record VapidKeysConfig(string PublicKey, string PrivateKey); 53 | [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] 54 | [JsonSerializable(typeof(VapidKeysConfig[]))] 55 | public partial class VapidKeysConfigContext : JsonSerializerContext 56 | { 57 | } -------------------------------------------------------------------------------- /DexieNETCloudSample/Components/InviteForm.razor: -------------------------------------------------------------------------------- 1 | 2 | @if (Scope.CanAddMember()) 3 | { 4 | 5 | 6 | Invite User 7 | 8 | 9 | 10 | 11 | 12 | @context 13 | 14 | 16 | 17 | 18 | 19 | } 20 | 21 | @code { 22 | [Inject] 23 | public required DexieCloudService DBService { get; init; } 24 | 25 | [Inject] 26 | public required IDialogService DialogService { get; init; } 27 | 28 | [CascadingParameter] 29 | public required ToDoListMemberService.MemberScope Scope { get; init; } 30 | 31 | private IEnumerable _users = []; 32 | 33 | private Func AddMember(string user) => async _ => 34 | { 35 | if (user == Scope.Users.Last()) 36 | { 37 | ArgumentNullException.ThrowIfNull(DialogService); 38 | 39 | EmailData data = new("email@mydomain.com"); 40 | 41 | var parameters = new DialogParameters { ["Item"] = data }; 42 | var dialog = await DialogService.ShowAsync("Email for Invite", parameters); 43 | 44 | var result = await dialog.Result; 45 | if (!result.OK()) 46 | { 47 | return; 48 | } 49 | 50 | user = data.Email; 51 | } 52 | else 53 | { 54 | var parameters = new DialogParameters 55 | { ["Message"] = $"Invite {user}", ["ConfirmButton"] = "Invite", ["SuccessOnConfirm"] = true }; 56 | var dialog = await DialogService.ShowAsync("Invite USER", parameters); 57 | 58 | var res = await dialog.Result; 59 | 60 | if (!res.OK()) 61 | { 62 | return; 63 | } 64 | } 65 | 66 | await Scope.AddMember(user); 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Where/NotEqual.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class NotEqual(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "NotEqual"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var comparer = new PersonComparer(true); 12 | 13 | var table = DB.Persons; 14 | await table.Clear(); 15 | 16 | var persons = DataGenerator.GetPersons(); 17 | await table.BulkAdd(persons); 18 | 19 | var personsDataAge = persons.Where(p => p.Age != 11).OrderBy(p => p.Age); 20 | var personsAge = await table.Where(p => p.Age).NotEqual(11).ToArray(); 21 | 22 | if (!personsAge.OrderBy(p => p.Age).SequenceEqual(personsDataAge, comparer)) 23 | { 24 | throw new InvalidOperationException("Items not identical."); 25 | } 26 | 27 | var personsDataName = persons.Where(p => p.Name != "Person1").OrderBy(p => p.Name); 28 | var personsName = await table.Where(p => p.Name).NotEqual("Person1").ToArray(); 29 | 30 | if (!personsName.OrderBy(p => p.Name).SequenceEqual(personsDataName, comparer)) 31 | { 32 | throw new InvalidOperationException("Items not identical."); 33 | } 34 | 35 | await DB.Transaction(async _ => 36 | { 37 | await table.Clear(); 38 | await table.BulkAdd(persons); 39 | 40 | var whereClauseAge = table.Where(p => p.Age); 41 | var whereClauseName = table.Where(p => p.Name); 42 | 43 | var collectionAge = whereClauseAge.NotEqual(11); 44 | var collectionName = whereClauseName.NotEqual("Person1"); 45 | 46 | personsAge = await collectionAge.ToArray(); 47 | personsName = await collectionName.ToArray(); 48 | }); 49 | 50 | if (!personsAge.OrderBy(p => p.Age).SequenceEqual(personsDataAge, comparer)) 51 | { 52 | throw new InvalidOperationException("Items not identical."); 53 | } 54 | 55 | if (!personsName.OrderBy(p => p.Name).SequenceEqual(personsDataName, comparer)) 56 | { 57 | throw new InvalidOperationException("Items not identical."); 58 | } 59 | 60 | return "OK"; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dexie/Services/ToDoItemService.State.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | using DexieNETCloudSample.Logic; 3 | using RxBlazorLightCore; 4 | 5 | namespace DexieNETCloudSample.Dexie.Services 6 | { 7 | public partial class ToDoItemService 8 | { 9 | public Func ToggledItemCompleted(ToDoDBItem item) => async _ => 10 | { 11 | ArgumentNullException.ThrowIfNull(_db); 12 | ArgumentNullException.ThrowIfNull(item); 13 | 14 | var completedItem = item with { Completed = !item.Completed }; 15 | 16 | await _db.Transaction(async t => 17 | { 18 | var id = await _db.ToDoDBItems.Put(completedItem); 19 | 20 | if (_db.PushNotifications.TransactionCollecting) 21 | { 22 | await _db.PushNotifications.Put(null); 23 | return; 24 | } 25 | await AddPushNotification(id, completedItem.Completed ? PnReason.COMPLETED : PnReason.REMINDER); 26 | }); 27 | }; 28 | 29 | public Func CanToggledItemCompleted(ToDoDBItem? item) => () => 30 | { 31 | return CanUpdate(item, i => i.Completed); 32 | }; 33 | 34 | public Func DeleteItems(bool completed) => async _ => 35 | { 36 | ArgumentNullException.ThrowIfNull(_db); 37 | ArgumentNullException.ThrowIfNull(CurrentList.Value); 38 | 39 | var itemsToDelete = (completed ? await _db.ToDoDBItems 40 | .Where(i => i.ListID, CurrentList.Value.ID, i => i.Completed, true) 41 | .ToArray() : await _db.ToDoDBItems 42 | .Where(i => i.ListID, CurrentList.Value.ID) 43 | .ToArray()).ToArray(); 44 | 45 | foreach (var item in itemsToDelete) 46 | { 47 | ArgumentNullException.ThrowIfNull(item.ID); 48 | 49 | await _db.Transaction(async t => 50 | { 51 | await PreDeleteAction(item.ID); 52 | await GetTable().Delete(item.ID); 53 | await PostDeleteAction(item.ID); 54 | }); 55 | }; 56 | }; 57 | 58 | public Func CanDeleteCompletedItems => () => 59 | { 60 | var item = ItemsState.Value.Where(i => i.Completed).FirstOrDefault(); 61 | return CanDelete(item); 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /DexieNETTest/Tests/DexieNETTest.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | false 7 | true 8 | true 9 | true 10 | 11 | 12 | 13 | Windows 14 | 15 | 16 | OSX 17 | 18 | 19 | Linux 20 | 21 | 22 | 23 | 24 | 25 | 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | all 28 | 29 | 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dialogs/GetClientKeys.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Item.ClientId) /> 8 | 9 | 10 | Item.ClientSecret) InputType="@PasswordInput" Adornment="Adornment.End" AdornmentIcon="@PasswordInputIcon" OnAdornmentClick="ClientSecretToggle" AdornmentAriaLabel="Show Client Secret" /> 11 | 12 | 13 | Submit 14 | 15 | 16 | 17 | 18 | 19 | 20 | @code { 21 | 22 | [CascadingParameter] 23 | public required IMudDialogInstance MudDialog { get; init; } 24 | 25 | [Parameter, EditorRequired] 26 | public required CloudKeyData Item { get; init; } 27 | 28 | void Cancel() => MudDialog?.Cancel(); 29 | 30 | bool showSecret; 31 | InputType PasswordInput = InputType.Password; 32 | string PasswordInputIcon = Icons.Material.Filled.VisibilityOff; 33 | 34 | protected override async Task OnInitializedAsync() 35 | { 36 | var options = MudDialog.Options with 37 | { 38 | CloseButton = true 39 | }; 40 | 41 | await MudDialog.SetOptionsAsync(options); 42 | await base.OnInitializedAsync(); 43 | } 44 | 45 | private void OnValidSubmit(EditContext context) 46 | { 47 | MudDialog.Close(DialogResult.Ok(Item)); 48 | } 49 | 50 | void ClientSecretToggle() 51 | { 52 | if (showSecret) 53 | { 54 | showSecret = false; 55 | PasswordInputIcon = Icons.Material.Filled.VisibilityOff; 56 | PasswordInput = InputType.Password; 57 | } 58 | else 59 | { 60 | showSecret = true; 61 | PasswordInputIcon = Icons.Material.Filled.Visibility; 62 | PasswordInput = InputType.Text; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Table/Get.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class Get(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "Get"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Persons; 12 | 13 | await table.Clear(); 14 | 15 | var person = DataGenerator.GetPerson1(); 16 | var keyP = await table.Add(person); 17 | 18 | var personAdded = await table.Get(keyP); 19 | 20 | PersonComparer pComparer = new(true); 21 | 22 | if (!pComparer.Equals(person, personAdded)) 23 | { 24 | throw new InvalidOperationException("Item not identical."); 25 | } 26 | 27 | await table.Clear(); 28 | var persons = DataGenerator.GetPersons(); 29 | await table.BulkAdd(persons); 30 | 31 | var person6 = await table.Get(p => p.Name, "Person6"); 32 | 33 | if (person6?.Name != "Person6") 34 | { 35 | throw new InvalidOperationException("Item not identical."); 36 | } 37 | 38 | person6 = await table.Get(p => p.Name, "Person6", p => p.Age, 75); 39 | 40 | if (person6?.Name != "Person6") 41 | { 42 | throw new InvalidOperationException("Item not identical."); 43 | } 44 | 45 | person6 = await table.Get(p => p.Name, "Person6", p => p.Age, 75, p => p.Phone.Number.Number, "22222"); 46 | 47 | if (person6?.Name != "Person6") 48 | { 49 | throw new InvalidOperationException("Item not identical."); 50 | } 51 | 52 | await DB.Transaction(async _ => 53 | { 54 | await table.Clear(); 55 | keyP = await table.Add(person); 56 | personAdded = await table.Get(keyP); 57 | 58 | await table.Clear(); 59 | await table.BulkAdd(persons); 60 | person6 = await table.Get(p => p.Name, "Person6"); 61 | }); 62 | 63 | if (!pComparer.Equals(person, personAdded)) 64 | { 65 | throw new InvalidOperationException("Item not identical."); 66 | } 67 | 68 | if (person6.Name != "Person6") 69 | { 70 | throw new InvalidOperationException("Item not identical."); 71 | } 72 | 73 | return "OK"; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Generell/VersionUpdate.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class VersionUpdate(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "VersionUpdate"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | var table = DB.Friends; 12 | await table.Clear(); 13 | await table.BulkAdd(DataGenerator.GetFriend()); 14 | var fs = await table.ToArray(); 15 | 16 | try 17 | { 18 | table.OrderBy(f => f.ShoeSize); 19 | } 20 | catch (Exception ex) 21 | { 22 | if (!ex.Message.StartsWith("Can not create WhereClause")) 23 | { 24 | throw new InvalidOperationException("Test object not suitable."); 25 | } 26 | } 27 | 28 | DB.Close(); 29 | DB.Version(1.5).Stores(); 30 | await DB.Open(); 31 | 32 | var version = DB.Version(); 33 | if (version != 1.5) 34 | { 35 | throw new InvalidOperationException("Version not upgraded"); 36 | } 37 | 38 | var table2 = DB.Friends2; 39 | 40 | var f2s = await table2.ToArray(); 41 | var col = table2.Where(f => f.ShoeSize).Above(41); 42 | var friends2S = await col.ToArray(); 43 | var friends2 = await table2.OrderBy(f => f.ShoeSize).ToArray(); 44 | 45 | DB.Close(); 46 | DB.Version(3).Stores().Upgrade(async tx => 47 | { 48 | var table = tx.Friends(); 49 | 50 | await table.ToCollection().Modify(f => 51 | { 52 | var names = f.Name.Split(' '); 53 | var ageInfo = new AgeInfo2(DateTime.Now.AddYears(f.AgeInfo.Age)); 54 | 55 | var f3 = new Friend3(names[0], names[1], ageInfo); 56 | return f3; 57 | }); 58 | }); 59 | await DB.Open(); 60 | 61 | var table3 = DB.Friends3; 62 | var friends3 = await table3.ToArray(); 63 | 64 | friends3 = await table3.OrderBy(f => f.LastName).Reverse().ToArray(); 65 | 66 | if (friends3.First().LastName != "LLL") 67 | { 68 | throw new InvalidOperationException("Version not upgraded"); 69 | } 70 | 71 | return "OK"; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DexieNETTest/TestBase/Test/TestCases/Collection/CollectionFirstLast.cs: -------------------------------------------------------------------------------- 1 | using DexieNET; 2 | 3 | namespace DexieNETTest.TestBase.Test 4 | { 5 | internal class CollectionFirstLast(TestDB db) : DexieTest(db) 6 | { 7 | public override string Name => "CollectionFirstLast"; 8 | 9 | public override async ValueTask RunTest() 10 | { 11 | PersonComparer comparer = new(true); 12 | 13 | var table = DB.Persons; 14 | await table.Clear(); 15 | 16 | var first = await table.ToCollection().First(); 17 | var last = await table.ToCollection().Last(); 18 | 19 | if (first is not null || last is not null) 20 | { 21 | throw new InvalidOperationException("Items not null."); 22 | } 23 | 24 | var persons = DataGenerator.GetPersons(); 25 | await table.BulkAdd(persons); 26 | 27 | var personsData = await table.ToArray(); 28 | 29 | first = await table.ToCollection().First(); 30 | last = await table.ToCollection().Last(); 31 | 32 | if (!comparer.Equals(first, personsData.First()) || !comparer.Equals(last, personsData.Last())) 33 | { 34 | throw new InvalidOperationException("Items not identical."); 35 | } 36 | 37 | var firstA = await table.ToCollection().First(t => t?.Age); 38 | var lastA = await table.ToCollection().Last(t => t?.Age); 39 | 40 | if (firstA != personsData.First().Age || lastA != personsData.Last().Age) 41 | { 42 | throw new InvalidOperationException("Items not identical."); 43 | } 44 | 45 | await DB.Transaction(async _ => 46 | { 47 | await table.Clear(); 48 | 49 | var collection = table.ToCollection(); 50 | 51 | first = await collection.First(); 52 | last = await collection.Last(); 53 | 54 | await table.BulkAdd(persons); 55 | personsData = await table.ToArray(); 56 | 57 | collection = table.ToCollection(); 58 | first = await collection.First(); 59 | last = await collection.Last(); 60 | }); 61 | 62 | 63 | if (!comparer.Equals(first, personsData.First()) || !comparer.Equals(last, personsData.Last())) 64 | { 65 | throw new InvalidOperationException("Items not identical."); 66 | } 67 | 68 | return "OK"; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DexieNETCloudSample/Dexie/Services/ToDoListMemberService.MemberRowScope.cs: -------------------------------------------------------------------------------- 1 | using DexieCloudNET; 2 | using RxBlazorLightCore; 3 | 4 | namespace DexieNETCloudSample.Dexie.Services 5 | { 6 | public partial class ToDoListMemberService 7 | { 8 | public partial class MemberScope 9 | { 10 | public class MemberRowScope(ToDoListMemberService service, MemberScope memberScope) 11 | : RxBLStateScope(service) 12 | { 13 | public MemberScope MemberScope => memberScope; 14 | 15 | private static readonly MemberRoleSelection[] _roleSelections = 16 | [ 17 | new(MemberRole.OWNER), 18 | new(MemberRole.ADMIN), 19 | new(MemberRole.USER), 20 | new(MemberRole.GUEST), 21 | ]; 22 | 23 | public IStateGroupAsync MemberRoleSelection = 24 | service.CreateStateGroupAsync(_roleSelections); 25 | 26 | public Func GetInitialMemberRole(Member member) => () => 27 | { 28 | var role = memberScope.GetMemberRole(member); 29 | 30 | MemberRoleSelection? initialSelection = null; 31 | 32 | foreach (var roleSelection in _roleSelections) 33 | { 34 | var displayName = Service.GetRoleDisplayName(roleSelection.Role); 35 | roleSelection.RoleName = displayName; 36 | if (roleSelection.Role == role) 37 | { 38 | initialSelection = roleSelection; 39 | } 40 | } 41 | 42 | ArgumentNullException.ThrowIfNull(initialSelection); 43 | return initialSelection; 44 | }; 45 | 46 | public Func MemberRoleChangingAsync(Member member) => 47 | async (or, nr) => { await memberScope.ChangeAccess(member, or.Role, nr.Role); }; 48 | 49 | public Func CanChangeMemberRole(Member member) => () => memberScope.CanUpdateRole(member); 50 | 51 | public Func MemberRoleDisabledCallback(Member member) => index => 52 | { 53 | var role = _roleSelections[index].Role; 54 | return !memberScope.CanChangeToRole(member, role); 55 | }; 56 | } 57 | } 58 | } 59 | } --------------------------------------------------------------------------------