├── .github └── workflows │ ├── dotnet-core-pr.yml │ └── dotnet-core.yml ├── .gitignore ├── BlazorDB.sln ├── LICENSE ├── README.md ├── example └── BlazorDB.Example │ ├── App.razor │ ├── BlazorDB.Example.csproj │ ├── Pages │ ├── Counter.razor │ ├── FetchData.razor │ └── Index.razor │ ├── Program.cs │ ├── Shared │ ├── MainLayout.razor │ ├── NavMenu.razor │ └── SurveyPrompt.razor │ ├── _Imports.razor │ └── wwwroot │ ├── css │ ├── app.css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── dexie.min.js │ ├── favicon.ico │ └── index.html ├── global.json └── src └── BlazorDB ├── BlazorDB.csproj ├── BlazorDbEvent.cs ├── BlazorDbFactory.cs ├── DbStore.cs ├── IBlazorDbFactory.cs ├── IndexDbManager.cs ├── IndexFilterValue.cs ├── IndexedDbFunctions.cs ├── ServiceCollectionExtensions.cs ├── StoreRecord.cs ├── StoreSchema.cs ├── UpdateRecord.cs └── wwwroot ├── blazorDB.js └── dexie.min.js /.github/workflows/dotnet-core-pr.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET Core 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: 3.1.101 18 | - name: Install dependencies 19 | run: dotnet restore src/BlazorDB/BlazorDB.csproj 20 | - name: Build 21 | run: dotnet build --configuration Release --no-restore src/BlazorDB/BlazorDB.csproj 22 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Get the version 21 | id: get_version 22 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/\v/} 23 | - name: Install dependencies 24 | run: dotnet restore src/BlazorDB/BlazorDB.csproj 25 | - name: Build 26 | run: dotnet build /p:Version="${{ steps.get_version.outputs.VERSION }}" --configuration Release --no-restore src/BlazorDB/BlazorDB.csproj 27 | - name: Package 28 | run: dotnet pack /p:Version="${{ steps.get_version.outputs.VERSION }}" --configuration Release --no-restore src/BlazorDB/BlazorDB.csproj 29 | - name: Publish NuGet 30 | run: dotnet nuget push ${{ github.workspace }}/src/BlazorDB/bin/Release/BlazorIndexedDB.${{ steps.get_version.outputs.VERSION }}.nupkg -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json 31 | - name: Create Release 32 | id: create_release 33 | uses: actions/create-release@latest 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 36 | with: 37 | tag_name: ${{ github.ref }} 38 | release_name: Release ${{ github.ref }} 39 | draft: false 40 | prerelease: false 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /BlazorDB.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31213.239 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDB", "src\BlazorDB\BlazorDB.csproj", "{D0180843-D92A-44B7-9893-F2450415F208}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDB.Example", "example\BlazorDB.Example\BlazorDB.Example.csproj", "{155CD782-6CEF-4115-9A04-1864A2CDFC90}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D0180843-D92A-44B7-9893-F2450415F208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D0180843-D92A-44B7-9893-F2450415F208}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D0180843-D92A-44B7-9893-F2450415F208}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D0180843-D92A-44B7-9893-F2450415F208}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {155CD782-6CEF-4115-9A04-1864A2CDFC90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {155CD782-6CEF-4115-9A04-1864A2CDFC90}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {155CD782-6CEF-4115-9A04-1864A2CDFC90}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {155CD782-6CEF-4115-9A04-1864A2CDFC90}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C885CF82-0E03-4E96-BB65-8BC40C579CE4} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nathan Westfall 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Forked from [https://github.com/Reshiru/Blazor.IndexedDB.Framework](https://github.com/Reshiru/Blazor.IndexedDB.Framework) as inspiration. A lot has changed since original fork and a lot is planned to get it closer. 2 | 3 | # BlazorDB 4 | 5 | An easy, fast way to use IndexedDB in a Blazor application. 6 | 7 | ### How to Use (WASM) 8 | - Install `dotnet add package BlazorIndexedDB` 9 | - Add `.js` files to `index.html` 10 | ```js 11 | 12 | 13 | ``` 14 | - Add `@using BlazorDB` to `_Imports.razor` 15 | - Update `Program.cs` `ServiceCollection` with new databases (can add as many databases as you want) 16 | ```c# 17 | builder.Services.AddBlazorDB(options => 18 | { 19 | options.Name = "Test"; 20 | options.Version = 1; 21 | options.StoreSchemas = new List() 22 | { 23 | new StoreSchema() 24 | { 25 | Name = "Person", 26 | PrimaryKey = "id", 27 | PrimaryKeyAuto = true, 28 | UniqueIndexes = new List { "name" } 29 | } 30 | }; 31 | }); 32 | ``` 33 | - Inject `IBlazorDbFactory` in your Razor pages 34 | ```c# 35 | @inject IBlazorDbFactory _dbFactory 36 | ``` 37 | - Start using! 38 | ```c# 39 | var manager = await _dbFactory.GetDbManager(dbName) 40 | await manager.AddRecord(new StoreRecord() 41 | { 42 | StoreName = storeName, 43 | Record = new { Name = "MyName", Age = 20 } 44 | }); 45 | ``` 46 | 47 | ### How it works 48 | You defined your databases and store schemes in the `ServicesCollection`. We add a helper called `AddBlazorDB` where you can build ` DbStore`. From there, we ensure that an instance of `IBlazorDbFactory` is available in your service provider and will automatically add all `DbStores` and `IndexedDbManagers` based on what is defined with `AddBlazorDb`. This allows you to have multiple databases if needed. 49 | 50 | To access any database/store, all you need to do is inject the `IBlazorDbFactory` in your page and call `GetDbManager(string dbName)`. We will pull your `IndexedDbManager` from the factory and make sure it's created and ready. 51 | 52 | Most calls will either be true `Async` or let you pass an `Action` in. If it lets you pass in an `Action`, it is optional. This `Action` will get called when the method is complete. This way, it isn't blocking on `WASMs` single thread. All of these calls return a `Guid` which is the "transaction" identifier to IndexeDB (it's not a true database transaction, just the call between C# and javascript that gets tracked). 53 | 54 | If the call is flagged as `Async`, we will wait for the JS callback that it is complete and then return the data. The library takes care of this connection between javascript and C# for you. 55 | 56 | ### Available Calls 57 | - `Task OpenDb(Action action)` - Open the IndexedDb and make sure it is created, with an optional callback when complete 58 | - `Task DeleteDb(string dbName, Action action)` - Delete the database, with an optional callback when complete 59 | - `Task DeleteDbAsync(string dbName)` - Delete the database and wait for it to complete 60 | - `Task AddRecord(StoreRecord recordToAdd, Action action)` - Add a record to the store, with an optional callback when complete 61 | - `Task AddRecordAsync(StoreRecord recordToAdd)` - Add a record to the store and wait for it to complete 62 | - `Task BulkAddRecord(string storeName, IEnumerable recordsToBulkAdd, Action action)` - Adds records/objects to the specified store in bulk 63 | - `Task BulkAddRecordAsync(string storeName, IEnumerable recordsToBulkAdd)` - Adds records/objects to the specified store in bulk and waits for it to complete 64 | - `Task UpdateRecord(UpdateRecord recordToUpdate, Action action)` - Update a record in the store, with an optional callback when c complete 65 | - `Task UpdateRecordAsync(UpdateRecord recordToUpdate)` - Update a record in the store and wait for it to complete 66 | - `Task GetRecordByIdAsync(string storeName, TInput key)` - Get a record by the 'id' and wait for it to return 67 | - `Task DeleteRecord(string storeName, TInput key, Action action)` - Delete a record in the store by 'id', with an optional callback when complete 68 | - `Task DeleteRecordAsync(string storeName, TInput key)` - Delete a record in the store by 'id' and wait for it to complete 69 | - `Task ClearTable(string storeName, Action action)` - Clears all data from a Table but keeps the table 70 | - `Task ClearTableAsync(string storeName)` - Clears all data from a Table but keeps the table and wait for it to complete 71 | 72 | ### More to Come 73 | - Real queries 74 | - Automated testing 75 | - Dynamically add/remove from `IBlazorDbFactory` 76 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/BlazorDB.Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | 3.0 6 | 7 | 8 | 9 | 10 | Always 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 |

Counter

4 | 5 |

Current count: @currentCount

6 | 7 | 8 | 9 | @code { 10 | private int currentCount = 0; 11 | 12 | private void IncrementCount() 13 | { 14 | currentCount++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @inject HttpClient Http 3 | 4 |

Weather forecast

5 | 6 |

This component demonstrates fetching data from the server.

7 | 8 | @if (forecasts == null) 9 | { 10 |

Loading...

11 | } 12 | else 13 | { 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @foreach (var forecast in forecasts) 25 | { 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | 34 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
35 | } 36 | 37 | @code { 38 | private WeatherForecast[] forecasts; 39 | 40 | protected override async Task OnInitializedAsync() 41 | { 42 | forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); 43 | } 44 | 45 | public class WeatherForecast 46 | { 47 | public DateTime Date { get; set; } 48 | 49 | public int TemperatureC { get; set; } 50 | 51 | public string Summary { get; set; } 52 | 53 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject IJSRuntime JSRuntime; 3 | @inject IBlazorDbFactory _dbFactory; 4 | 5 |

Person

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |

Item

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | @code { 51 | IndexedDbManager manager; 52 | 53 | string dbName = "Test"; 54 | string storeName = "Person"; 55 | 56 | protected override async Task OnInitializedAsync() 57 | { 58 | manager = await _dbFactory.GetDbManager(dbName); 59 | manager.ActionCompleted += (_, __) => 60 | { 61 | Console.WriteLine(__.Message); 62 | }; 63 | } 64 | 65 | protected async void SwitchToItem() 66 | { 67 | dbName = "Test2"; 68 | storeName = "Item"; 69 | manager = await _dbFactory.GetDbManager(dbName); 70 | } 71 | 72 | protected async void SwitchToPerson() 73 | { 74 | dbName = "Test"; 75 | storeName = "Person"; 76 | manager = await _dbFactory.GetDbManager(dbName); 77 | } 78 | 79 | protected async Task Create() 80 | { 81 | Console.WriteLine("Create"); 82 | await manager.OpenDb(); 83 | } 84 | 85 | protected async Task Add() 86 | { 87 | await manager.AddRecord(new StoreRecord() 88 | { 89 | StoreName = storeName, 90 | Record = new { Name = "MyName", Age = 20, Action ="Add", Guid = System.Guid.NewGuid() } 91 | }); 92 | } 93 | 94 | protected async Task Put() 95 | { 96 | await manager.PutRecord(new StoreRecord() 97 | { 98 | StoreName = storeName, 99 | Record = new { Id = 1, Name = "MyName, modified", Action = "Put", Age = 55, Guid = System.Guid.NewGuid() } 100 | }); 101 | } 102 | 103 | protected async Task Add100() 104 | { 105 | for(var i = 0; i < 100; i++) 106 | { 107 | await manager.AddRecord(new StoreRecord() 108 | { 109 | StoreName = storeName, 110 | Record = new { Name = "MyName", Age = 20, Guid = System.Guid.NewGuid() } 111 | }); 112 | } 113 | } 114 | 115 | protected async Task Add100Async() 116 | { 117 | for(var i = 0; i < 100; i++) 118 | { 119 | await manager.AddRecordAsync(new StoreRecord() 120 | { 121 | StoreName = storeName, 122 | Record = new { Name = "MyName", Age = 20, Guid = System.Guid.NewGuid() } 123 | }); 124 | } 125 | } 126 | 127 | protected async Task BulkAdd() 128 | { 129 | var records = Enumerable.Range(0, 10_000).Select(x => new { Name = "MyName", Age = x, Action = "BulkAdd", Guid = System.Guid.NewGuid() }); 130 | await manager.BulkAddRecord(storeName, records); 131 | } 132 | 133 | protected async Task BulkAddAsync() 134 | { 135 | var records = Enumerable.Range(0, 10_000).Select(x => new { Name = "MyName", Age = x, Action = "BulkAddAsync", Guid = System.Guid.NewGuid() }); 136 | await manager.BulkAddRecordAsync(storeName, records); 137 | } 138 | 139 | protected async Task Update() 140 | { 141 | await manager.UpdateRecord(new UpdateRecord() 142 | { 143 | StoreName = storeName, 144 | Record = new { Name = "YourName", Age = 21, Guid = System.Guid.NewGuid() }, 145 | Key = 1 146 | }); 147 | } 148 | 149 | protected async Task UpdateAsync() 150 | { 151 | await manager.UpdateRecordAsync(new UpdateRecord() 152 | { 153 | StoreName = storeName, 154 | Record = new { Name = "YourName", Age = 21, Guid = System.Guid.NewGuid() }, 155 | Key = 1 156 | }); 157 | } 158 | 159 | protected async Task AddWithCallback() 160 | { 161 | await manager.AddRecord(new StoreRecord() 162 | { 163 | StoreName = storeName, 164 | Record = new { Name = "MyName", Age = 20, Guid = System.Guid.NewGuid() } 165 | }, (_) => { 166 | Console.WriteLine($"{_.Transaction}-{_.Failed}-{_.Message}"); 167 | }); 168 | } 169 | 170 | protected async Task Delete() 171 | { 172 | await manager.DeleteRecord(storeName, 2); 173 | } 174 | 175 | protected async Task DeleteAsync() 176 | { 177 | await manager.DeleteRecordAsync(storeName, 2); 178 | } 179 | 180 | protected async Task GetItem() 181 | { 182 | var item = await manager.GetRecordByIdAsync(storeName, 3); 183 | Console.WriteLine(item); 184 | } 185 | 186 | protected async Task ToArray() 187 | { 188 | var items = await manager.ToArray(storeName); 189 | foreach (var item in items.Take(10)) { 190 | Console.WriteLine(item); 191 | } 192 | Console.WriteLine($"Got {items.Count()} items."); 193 | } 194 | 195 | protected async Task DeleteDb() 196 | { 197 | await manager.DeleteDb(dbName); 198 | } 199 | 200 | private async Task WhereName() 201 | { 202 | var items = await manager.Where(storeName, "name", "MyName"); 203 | foreach (var item in items) { 204 | Console.WriteLine(item); 205 | } 206 | } 207 | 208 | private async void WhereId() 209 | { 210 | var items = await manager.Where(storeName, "id", 1); 211 | foreach (var item in items) { 212 | Console.WriteLine(item); 213 | } 214 | } 215 | 216 | private async void WhereNameAndId() 217 | { 218 | var filters = new List(); 219 | filters.Add(new IndexFilterValue("id", 1)); 220 | filters.Add(new IndexFilterValue("name", "MyName")); 221 | var items = await manager.Where(storeName, filters); 222 | foreach (var item in items) { 223 | Console.WriteLine(item); 224 | } 225 | } 226 | 227 | protected async Task ViolateUniqueness() 228 | { 229 | var duplicatedGuid = System.Guid.NewGuid(); 230 | await manager.AddRecord(new StoreRecord() 231 | { 232 | StoreName = storeName, 233 | Record = new { Name = "MyName", Age = 20, Action = "Add", Guid = duplicatedGuid } 234 | }); 235 | await manager.AddRecord(new StoreRecord() 236 | { 237 | StoreName = storeName, 238 | Record = new { Name = "MyName", Age = 20, Action = "Add", Guid = duplicatedGuid } 239 | }); 240 | } 241 | } -------------------------------------------------------------------------------- /example/BlazorDB.Example/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using System.Text; 6 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace BlazorDB.Example 12 | { 13 | public class Program 14 | { 15 | public static async Task Main(string[] args) 16 | { 17 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 18 | builder.RootComponents.Add("app"); 19 | 20 | builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 21 | builder.Services.AddBlazorDB(options => 22 | { 23 | options.Name = "Test"; 24 | options.Version = 1; 25 | options.StoreSchemas = new List() 26 | { 27 | new StoreSchema() 28 | { 29 | Name = "Person", 30 | PrimaryKey = "id", 31 | PrimaryKeyAuto = true, 32 | UniqueIndexes = new List { "guid" }, 33 | Indexes = new List { "name" } 34 | } 35 | }; 36 | }); 37 | builder.Services.AddBlazorDB(options => 38 | { 39 | options.Name = "Test2"; 40 | options.Version = 1; 41 | options.StoreSchemas = new List() 42 | { 43 | new StoreSchema() 44 | { 45 | Name = "Item", 46 | PrimaryKey = "id", 47 | PrimaryKeyAuto = true, 48 | UniqueIndexes = new List { "guid" }, 49 | Indexes = new List { "name" } 50 | } 51 | }; 52 | }); 53 | 54 | await builder.Build().RunAsync(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 6 | 7 |
8 |
9 | About 10 |
11 | 12 |
13 | @Body 14 |
15 |
16 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  7 | 8 |
9 | 26 |
27 | 28 | @code { 29 | private bool collapseNavMenu = true; 30 | 31 | private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 32 | 33 | private void ToggleNavMenu() 34 | { 35 | collapseNavMenu = !collapseNavMenu; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 |  11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string Title { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/_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.WebAssembly.Http 7 | @using Microsoft.JSInterop 8 | @using BlazorDB.Example 9 | @using BlazorDB.Example.Shared 10 | @using BlazorDB 11 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | a, .btn-link { 8 | color: #0366d6; 9 | } 10 | 11 | .btn-primary { 12 | color: #fff; 13 | background-color: #1b6ec2; 14 | border-color: #1861ac; 15 | } 16 | 17 | app { 18 | position: relative; 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .top-row { 24 | height: 3.5rem; 25 | display: flex; 26 | align-items: center; 27 | } 28 | 29 | .main { 30 | flex: 1; 31 | } 32 | 33 | .main .top-row { 34 | background-color: #f7f7f7; 35 | border-bottom: 1px solid #d6d5d5; 36 | justify-content: flex-end; 37 | } 38 | 39 | .main .top-row > a, .main .top-row .btn-link { 40 | white-space: nowrap; 41 | margin-left: 1.5rem; 42 | } 43 | 44 | .main .top-row a:first-child { 45 | overflow: hidden; 46 | text-overflow: ellipsis; 47 | } 48 | 49 | .sidebar { 50 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 51 | } 52 | 53 | .sidebar .top-row { 54 | background-color: rgba(0,0,0,0.4); 55 | } 56 | 57 | .sidebar .navbar-brand { 58 | font-size: 1.1rem; 59 | } 60 | 61 | .sidebar .oi { 62 | width: 2rem; 63 | font-size: 1.1rem; 64 | vertical-align: text-top; 65 | top: -2px; 66 | } 67 | 68 | .sidebar .nav-item { 69 | font-size: 0.9rem; 70 | padding-bottom: 0.5rem; 71 | } 72 | 73 | .sidebar .nav-item:first-of-type { 74 | padding-top: 1rem; 75 | } 76 | 77 | .sidebar .nav-item:last-of-type { 78 | padding-bottom: 1rem; 79 | } 80 | 81 | .sidebar .nav-item a { 82 | color: #d7d7d7; 83 | border-radius: 4px; 84 | height: 3rem; 85 | display: flex; 86 | align-items: center; 87 | line-height: 3rem; 88 | } 89 | 90 | .sidebar .nav-item a.active { 91 | background-color: rgba(255,255,255,0.25); 92 | color: white; 93 | } 94 | 95 | .sidebar .nav-item a:hover { 96 | background-color: rgba(255,255,255,0.1); 97 | color: white; 98 | } 99 | 100 | .content { 101 | padding-top: 1.1rem; 102 | } 103 | 104 | .navbar-toggler { 105 | background-color: rgba(255, 255, 255, 0.1); 106 | } 107 | 108 | .valid.modified:not([type=checkbox]) { 109 | outline: 1px solid #26b050; 110 | } 111 | 112 | .invalid { 113 | outline: 1px solid red; 114 | } 115 | 116 | .validation-message { 117 | color: red; 118 | } 119 | 120 | #blazor-error-ui { 121 | background: lightyellow; 122 | bottom: 0; 123 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 124 | display: none; 125 | left: 0; 126 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 127 | position: fixed; 128 | width: 100%; 129 | z-index: 1000; 130 | } 131 | 132 | #blazor-error-ui .dismiss { 133 | cursor: pointer; 134 | position: absolute; 135 | right: 0.75rem; 136 | top: 0.5rem; 137 | } 138 | 139 | @media (max-width: 767.98px) { 140 | .main .top-row:not(.auth) { 141 | display: none; 142 | } 143 | 144 | .main .top-row.auth { 145 | justify-content: space-between; 146 | } 147 | 148 | .main .top-row a, .main .top-row .btn-link { 149 | margin-left: 0; 150 | } 151 | } 152 | 153 | @media (min-width: 768px) { 154 | app { 155 | flex-direction: row; 156 | } 157 | 158 | .sidebar { 159 | width: 250px; 160 | height: 100vh; 161 | position: sticky; 162 | top: 0; 163 | } 164 | 165 | .main .top-row { 166 | position: sticky; 167 | top: 0; 168 | } 169 | 170 | .main > div { 171 | padding-left: 2rem !important; 172 | padding-right: 1.5rem !important; 173 | } 174 | 175 | .navbar-toggler { 176 | display: none; 177 | } 178 | 179 | .sidebar .collapse { 180 | /* Never collapse the sidebar for wide screens */ 181 | display: block; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/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. -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwestfall/BlazorDB/44a5814756d44af9afe1033651287aef38d09907/example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwestfall/BlazorDB/44a5814756d44af9afe1033651287aef38d09907/example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 9 | By P.J. Onori 10 | Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 74 | 76 | 79 | 81 | 84 | 86 | 88 | 91 | 93 | 95 | 98 | 100 | 102 | 104 | 106 | 109 | 112 | 115 | 117 | 121 | 123 | 125 | 127 | 130 | 132 | 134 | 136 | 138 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 162 | 165 | 167 | 169 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 189 | 191 | 194 | 196 | 198 | 200 | 202 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 241 | 243 | 245 | 247 | 249 | 251 | 253 | 256 | 259 | 261 | 263 | 265 | 267 | 269 | 272 | 274 | 276 | 280 | 282 | 285 | 287 | 289 | 292 | 295 | 298 | 300 | 302 | 304 | 306 | 309 | 312 | 314 | 316 | 318 | 320 | 322 | 324 | 326 | 330 | 334 | 338 | 340 | 343 | 345 | 347 | 349 | 351 | 353 | 355 | 358 | 360 | 363 | 365 | 367 | 369 | 371 | 373 | 375 | 377 | 379 | 381 | 383 | 386 | 388 | 390 | 392 | 394 | 396 | 399 | 401 | 404 | 406 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 423 | 425 | 428 | 431 | 435 | 438 | 440 | 442 | 444 | 446 | 448 | 451 | 453 | 455 | 457 | 460 | 462 | 464 | 466 | 468 | 471 | 473 | 477 | 479 | 481 | 483 | 486 | 488 | 490 | 492 | 494 | 496 | 499 | 501 | 504 | 506 | 509 | 512 | 515 | 517 | 520 | 522 | 524 | 526 | 529 | 532 | 534 | 536 | 539 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwestfall/BlazorDB/44a5814756d44af9afe1033651287aef38d09907/example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwestfall/BlazorDB/44a5814756d44af9afe1033651287aef38d09907/example/BlazorDB.Example/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /example/BlazorDB.Example/wwwroot/dexie.min.js: -------------------------------------------------------------------------------- 1 | (function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Dexie=t()})(this,function(){"use strict";var m=function(){return(m=Object.assign||function(e){for(var t,n=1,r=arguments.length;n.",mt="String expected.",gt=[],bt="undefined"!=typeof navigator&&/(MSIE|Trident|Edge)/.test(navigator.userAgent),_t=bt,wt=bt,xt="__dbnames",kt="readonly",Et="readwrite";function Ot(e,t){return e?t?function(){return e.apply(this,arguments)&&t.apply(this,arguments)}:e:t}var Pt={type:3,lower:-1/0,lowerOpen:!1,upper:[[]],upperOpen:!1},jt=(At.prototype._trans=function(e,r,t){var n=this._tx||Ce.trans,i=this.name;function o(e,t,n){if(!n.schema[i])throw new Z.NotFound("Table "+i+" not part of transaction");return r(n.idbtrans,n)}var u=ze();try{return n&&n.db===this.db?n===Ce.trans?n._promise(e,o,t):tt(function(){return n._promise(e,o,t)},{trans:n,transless:Ce.transless||Ce}):function e(t,n,r,i){if(t._state.openComplete||Ce.letThrough){var o=t._createTransaction(n,r,t._dbSchema);try{o.create()}catch(e){return pt(e)}return o._promise(n,function(e,t){return tt(function(){return Ce.trans=o,i(e,t,o)})}).then(function(e){return o._completion.then(function(){return e})})}if(!t._state.isBeingOpened){if(!t._options.autoOpen)return pt(new Z.DatabaseClosed);t.open().catch(ne)}return t._state.dbReadyPromise.then(function(){return e(t,n,r,i)})}(this.db,e,[this.name],o)}finally{u&&Le()}},At.prototype.get=function(t,e){var n=this;return t&&t.constructor===Object?this.where(t).first(e):this._trans("readonly",function(e){return n.core.get({trans:e,key:t}).then(function(e){return n.hook.reading.fire(e)})}).then(e)},At.prototype.where=function(u){if("string"==typeof u)return new this.db.WhereClause(this,u);if(d(u))return new this.db.WhereClause(this,"["+u.join("+")+"]");var n=_(u);if(1===n.length)return this.where(n[0]).equals(u[n[0]]);var e=this.schema.indexes.concat(this.schema.primKey).filter(function(t){return t.compound&&n.every(function(e){return 0<=t.keyPath.indexOf(e)})&&t.keyPath.every(function(e){return 0<=n.indexOf(e)})})[0];if(e&&this.db._maxKey!==yt)return this.where(e.name).equals(e.keyPath.map(function(e){return u[e]}));!e&&q&&console.warn("The query "+JSON.stringify(u)+" on "+this.name+" would benefit of a compound index ["+n.join("+")+"]");var a=this.schema.idxByName,r=this.db._deps.indexedDB;function s(e,t){try{return 0===r.cmp(e,t)}catch(e){return!1}}var t=n.reduce(function(e,n){var t=e[0],r=e[1],i=a[n],o=u[n];return[t||i,t||!i?Ot(r,i&&i.multi?function(e){var t=w(e,n);return d(t)&&t.some(function(e){return s(o,e)})}:function(e){return s(o,w(e,n))}):r]},[null,null]),i=t[0],o=t[1];return i?this.where(i.name).equals(u[i.keyPath]).filter(o):e?this.filter(o):this.where(n).equals("")},At.prototype.filter=function(e){return this.toCollection().and(e)},At.prototype.count=function(e){return this.toCollection().count(e)},At.prototype.offset=function(e){return this.toCollection().offset(e)},At.prototype.limit=function(e){return this.toCollection().limit(e)},At.prototype.each=function(e){return this.toCollection().each(e)},At.prototype.toArray=function(e){return this.toCollection().toArray(e)},At.prototype.toCollection=function(){return new this.db.Collection(new this.db.WhereClause(this))},At.prototype.orderBy=function(e){return new this.db.Collection(new this.db.WhereClause(this,d(e)?"["+e.join("+")+"]":e))},At.prototype.reverse=function(){return this.toCollection().reverse()},At.prototype.mapToClass=function(r){function e(e){if(!e)return e;var t=Object.create(r.prototype);for(var n in e)if(c(e,n))try{t[n]=e[n]}catch(e){}return t}return this.schema.mappedClass=r,this.schema.readHook&&this.hook.reading.unsubscribe(this.schema.readHook),this.schema.readHook=e,this.hook("reading",e),r},At.prototype.defineClass=function(){return this.mapToClass(function(e){s(this,e)})},At.prototype.add=function(t,n){var r=this;return this._trans("readwrite",function(e){return r.core.mutate({trans:e,type:"add",keys:null!=n?[n]:null,values:[t]})}).then(function(e){return e.numFailures?De.reject(e.failures[0]):e.lastResult}).then(function(e){if(!r.core.schema.primaryKey.outbound)try{x(t,r.core.schema.primaryKey.keyPath,e)}catch(e){}return e})},At.prototype.update=function(t,n){if("object"!=typeof n||d(n))throw new Z.InvalidArgument("Modifications must be an object.");if("object"!=typeof t||d(t))return this.where(":id").equals(t).modify(n);_(n).forEach(function(e){x(t,e,n[e])});var e=w(t,this.schema.primKey.keyPath);return void 0===e?pt(new Z.InvalidArgument("Given object does not contain its primary key")):this.where(":id").equals(e).modify(n)},At.prototype.put=function(t,n){var r=this;return this._trans("readwrite",function(e){return r.core.mutate({trans:e,type:"put",values:[t],keys:null!=n?[n]:null})}).then(function(e){return e.numFailures?De.reject(e.failures[0]):e.lastResult}).then(function(e){if(!r.core.schema.primaryKey.outbound)try{x(t,r.core.schema.primaryKey.keyPath,e)}catch(e){}return e})},At.prototype.delete=function(t){var n=this;return this._trans("readwrite",function(e){return n.core.mutate({trans:e,type:"delete",keys:[t]})}).then(function(e){return e.numFailures?De.reject(e.failures[0]):void 0})},At.prototype.clear=function(){var t=this;return this._trans("readwrite",function(e){return t.core.mutate({trans:e,type:"deleteRange",range:Pt})}).then(function(e){return e.numFailures?De.reject(e.failures[0]):void 0})},At.prototype.bulkGet=function(t){var n=this;return this._trans("readonly",function(e){return n.core.getMany({keys:t,trans:e})})},At.prototype.bulkAdd=function(t,e,n){var u=this,r=Array.isArray(e)?e:void 0,a=(n=n||(r?void 0:e))?n.allKeys:void 0;return this._trans("readwrite",function(e){if(!u.core.schema.primaryKey.outbound&&r)throw new Z.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys");if(r&&r.length!==t.length)throw new Z.InvalidArgument("Arguments objects and keys must have the same length");var o=t.length;return u.core.mutate({trans:e,type:"add",keys:r,values:t,wantResults:a}).then(function(e){var t=e.numFailures,n=e.results,r=e.lastResult,i=e.failures;if(0===t)return a?n:r;throw new X(u.name+".bulkAdd(): "+t+" of "+o+" operations failed",Object.keys(i).map(function(e){return i[e]}))})})},At.prototype.bulkPut=function(t,e,n){var u=this,r=Array.isArray(e)?e:void 0,a=(n=n||(r?void 0:e))?n.allKeys:void 0;return this._trans("readwrite",function(e){if(!u.core.schema.primaryKey.outbound&&r)throw new Z.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys");if(r&&r.length!==t.length)throw new Z.InvalidArgument("Arguments objects and keys must have the same length");var o=t.length;return u.core.mutate({trans:e,type:"put",keys:r,values:t,wantResults:a}).then(function(e){var t=e.numFailures,n=e.results,r=e.lastResult,i=e.failures;if(0===t)return a?n:r;throw new X(u.name+".bulkPut(): "+t+" of "+o+" operations failed",Object.keys(i).map(function(e){return i[e]}))})})},At.prototype.bulkDelete=function(t){var i=this,o=t.length;return this._trans("readwrite",function(e){return i.core.mutate({trans:e,type:"delete",keys:t})}).then(function(e){var t=e.numFailures,n=e.lastResult,r=e.failures;if(0===t)return n;throw new X(i.name+".bulkDelete(): "+t+" of "+o+" operations failed",r)})},At);function At(){}function Kt(i){var u={},t=function(e,t){if(t){for(var n=arguments.length,r=new Array(n-1);--n;)r[n-1]=arguments[n];return u[e].subscribe.apply(null,r),i}if("string"==typeof e)return u[e]};t.addEventType=a;for(var e=1,n=arguments.length;es+c&&f(s+g)})})};return f(0).then(function(){if(0=l}).forEach(function(s){t.push(function(){var t=p,e=s._cfg.dbschema;wn(c,t,h),wn(c,e,h),p=c._dbSchema=e;var n=function(e,t){var n,r={del:[],add:[],change:[]};for(n in e)t[n]||r.del.push(n);for(n in t){var i=e[n],o=t[n];if(i){var u={name:n,def:o,recreate:!1,del:[],add:[],change:[]};if(i.primKey.src===o.primKey.src||bt){var a=i.idxByName,s=o.idxByName,c=void 0;for(c in a)s[c]||u.del.push(c);for(c in s){var l=a[c],f=s[c];l?l.src!==f.src&&u.change.push(f):u.add.push(f)}(0l){pn(c,h),d=!0;var i=k(e);n.del.forEach(function(e){i[e]=t[e]}),yn(c,[c.Transaction.prototype]),dn(c,[c.Transaction.prototype],_(i),i),f.schema=i;var o,u=F(r);u&&nt();var a=De.follow(function(){var e;(o=r(f))&&u&&(e=rt.bind(null,null),o.then(e,e))});return o&&"function"==typeof o.then?De.resolve(o):a.then(function(){return o})}}),t.push(function(e){d&&_t||function(e,t){for(var n=0;nMath.pow(2,62)?0:e.oldVersion,a.idbdb=u.result,mn(a,n/10,l,i))},i),u.onsuccess=He(function(){l=null;var e=a.idbdb=u.result,t=p(e.objectStoreNames);if(0 2 | 3 | 4 | 5 | 6 | 7 | BlazorDB.Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 |
17 | An unhandled error has occurred. 18 | Reload 19 | 🗙 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "3.1.x" 4 | } 5 | } -------------------------------------------------------------------------------- /src/BlazorDB/BlazorDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Library 6 | true 7 | 3.0 8 | Latest 9 | BlazorDB 10 | $(Version) 11 | Nathan Westfall 12 | A Blazor framework for interacting with IndexedDB 13 | https://github.com/nwestfall/BlazorDB/ 14 | git 15 | Blazor IndexedDB 16 | Nathan Westfall 17 | https://github.com/nwestfall/BlazorDB/blob/master/LICENSE 18 | MIT 19 | BlazorIndexedDB 20 | https://github.com/nwestfall/BlazorDB/blob/master/logo.png?raw=true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/BlazorDB/BlazorDbEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorDB 4 | { 5 | public class BlazorDbEvent 6 | { 7 | public Guid Transaction { get; set; } 8 | public bool Failed { get; set; } 9 | public string Message { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/BlazorDB/BlazorDbFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.JSInterop; 7 | 8 | namespace BlazorDB 9 | { 10 | public class BlazorDbFactory : IBlazorDbFactory 11 | { 12 | readonly IJSRuntime _jsRuntime; 13 | readonly IServiceProvider _serviceProvider; 14 | readonly IDictionary _dbs = new Dictionary(); 15 | 16 | public BlazorDbFactory(IServiceProvider serviceProvider, IJSRuntime jSRuntime) 17 | { 18 | _serviceProvider = serviceProvider; 19 | _jsRuntime = jSRuntime; 20 | } 21 | 22 | public async Task GetDbManager(string dbName) 23 | { 24 | if(!_dbs.Any()) 25 | await BuildFromServices(); 26 | if(_dbs.ContainsKey(dbName)) 27 | return _dbs[dbName]; 28 | 29 | return null; 30 | } 31 | 32 | public Task GetDbManager(DbStore dbStore) 33 | => GetDbManager(dbStore.Name); 34 | 35 | async Task BuildFromServices() 36 | { 37 | var dbStores = _serviceProvider.GetServices(); 38 | if(dbStores != null) 39 | { 40 | foreach(var dbStore in dbStores) 41 | { 42 | Console.WriteLine($"{dbStore.Name}{dbStore.Version}{dbStore.StoreSchemas.Count}"); 43 | var db = new IndexedDbManager(dbStore, _jsRuntime); 44 | await db.OpenDb(); 45 | _dbs.Add(dbStore.Name, db); 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/BlazorDB/DbStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorDB 4 | { 5 | public class DbStore 6 | { 7 | public string Name { get; set; } 8 | public int Version { get; set; } 9 | public List StoreSchemas { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/BlazorDB/IBlazorDbFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BlazorDB 4 | { 5 | public interface IBlazorDbFactory 6 | { 7 | Task GetDbManager(string dbName); 8 | 9 | Task GetDbManager(DbStore dbStore); 10 | } 11 | } -------------------------------------------------------------------------------- /src/BlazorDB/IndexDbManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.JSInterop; 5 | 6 | namespace BlazorDB 7 | { 8 | /// 9 | /// Provides functionality for accessing IndexedDB from Blazor application 10 | /// 11 | public class IndexedDbManager 12 | { 13 | readonly DbStore _dbStore; 14 | readonly IJSRuntime _jsRuntime; 15 | const string InteropPrefix = "window.blazorDB"; 16 | DotNetObjectReference _objReference; 17 | IDictionary>> _transactions = new Dictionary>>(); 18 | IDictionary> _taskTransactions = new Dictionary>(); 19 | 20 | /// 21 | /// A notification event that is raised when an action is completed 22 | /// 23 | public event EventHandler ActionCompleted; 24 | 25 | /// 26 | /// Ctor 27 | /// 28 | /// 29 | /// 30 | internal IndexedDbManager(DbStore dbStore, IJSRuntime jsRuntime) 31 | { 32 | _objReference = DotNetObjectReference.Create(this); 33 | _dbStore = dbStore; 34 | _jsRuntime = jsRuntime; 35 | } 36 | 37 | public List Stores => _dbStore.StoreSchemas; 38 | public int CurrentVersion => _dbStore.Version; 39 | public string DbName => _dbStore.Name; 40 | 41 | /// 42 | /// Opens the IndexedDB defined in the DbStore. Under the covers will create the database if it does not exist 43 | /// and create the stores defined in DbStore. 44 | /// 45 | /// 46 | public async Task OpenDb(Action action = null) 47 | { 48 | var trans = GenerateTransaction(action); 49 | await CallJavascriptVoid(IndexedDbFunctions.CREATE_DB, trans, _dbStore); 50 | return trans; 51 | } 52 | 53 | /// 54 | /// Deletes the database corresponding to the dbName passed in 55 | /// 56 | /// The name of database to delete 57 | /// 58 | public async Task DeleteDb(string dbName, Action action = null) 59 | { 60 | if (string.IsNullOrEmpty(dbName)) 61 | { 62 | throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); 63 | } 64 | var trans = GenerateTransaction(action); 65 | await CallJavascriptVoid(IndexedDbFunctions.DELETE_DB, trans, dbName); 66 | return trans; 67 | } 68 | 69 | /// 70 | /// Deletes the database corresponding to the dbName passed in 71 | /// Waits for response 72 | /// 73 | /// The name of database to delete 74 | /// 75 | public async Task DeleteDbAsync(string dbName) 76 | { 77 | if (string.IsNullOrEmpty(dbName)) 78 | { 79 | throw new ArgumentException("dbName cannot be null or empty", nameof(dbName)); 80 | } 81 | var trans = GenerateTransaction(); 82 | await CallJavascriptVoid(IndexedDbFunctions.DELETE_DB, trans.trans, dbName); 83 | return await trans.task; 84 | } 85 | 86 | /// 87 | /// Adds a new record/object to the specified store 88 | /// 89 | /// 90 | /// An instance of StoreRecord that provides the store name and the data to add 91 | /// 92 | public async Task AddRecord(StoreRecord recordToAdd, Action action = null) 93 | { 94 | var trans = GenerateTransaction(action); 95 | try 96 | { 97 | recordToAdd.DbName = DbName; 98 | await CallJavascriptVoid(IndexedDbFunctions.ADD_ITEM, trans, recordToAdd); 99 | } 100 | catch (JSException e) 101 | { 102 | RaiseEvent(trans, true, e.Message); 103 | } 104 | return trans; 105 | } 106 | 107 | /// 108 | /// Adds a new record/object to the specified store 109 | /// Waits for response 110 | /// 111 | /// 112 | /// An instance of StoreRecord that provides the store name and the data to add 113 | /// 114 | public async Task AddRecordAsync(StoreRecord recordToAdd) 115 | { 116 | var trans = GenerateTransaction(); 117 | try 118 | { 119 | recordToAdd.DbName = DbName; 120 | await CallJavascriptVoid(IndexedDbFunctions.ADD_ITEM, trans.trans, recordToAdd); 121 | } 122 | catch (JSException e) 123 | { 124 | RaiseEvent(trans.trans, true, e.Message); 125 | } 126 | return await trans.task; 127 | } 128 | 129 | /// 130 | /// Adds records/objects to the specified store in bulk 131 | /// 132 | /// 133 | /// The data to add 134 | /// 135 | public async Task BulkAddRecord(string storeName, IEnumerable recordsToBulkAdd, Action action = null) 136 | { 137 | var trans = GenerateTransaction(action); 138 | try 139 | { 140 | await CallJavascriptVoid(IndexedDbFunctions.BULKADD_ITEM, trans, DbName, storeName, recordsToBulkAdd); 141 | } 142 | catch (JSException e) 143 | { 144 | RaiseEvent(trans, true, e.Message); 145 | } 146 | return trans; 147 | } 148 | 149 | /// 150 | /// Adds records/objects to the specified store in bulk 151 | /// Waits for response 152 | /// 153 | /// 154 | /// An instance of StoreRecord that provides the store name and the data to add 155 | /// 156 | public async Task BulkAddRecordAsync(string storeName, IEnumerable recordsToBulkAdd) 157 | { 158 | var trans = GenerateTransaction(); 159 | try 160 | { 161 | await CallJavascriptVoid(IndexedDbFunctions.BULKADD_ITEM, trans.trans, DbName, storeName, recordsToBulkAdd); 162 | } 163 | catch (JSException e) 164 | { 165 | RaiseEvent(trans.trans, true, e.Message); 166 | } 167 | return await trans.task; 168 | } 169 | 170 | /// 171 | /// Puts a new record/object to the specified store 172 | /// 173 | /// 174 | /// An instance of StoreRecord that provides the store name and the data to put 175 | /// 176 | public async Task PutRecord(StoreRecord recordToPut, Action action = null) 177 | { 178 | var trans = GenerateTransaction(action); 179 | try 180 | { 181 | recordToPut.DbName = DbName; 182 | await CallJavascriptVoid(IndexedDbFunctions.PUT_ITEM, trans, recordToPut); 183 | } 184 | catch (JSException e) 185 | { 186 | RaiseEvent(trans, true, e.Message); 187 | } 188 | return trans; 189 | } 190 | 191 | /// 192 | /// Puts a new record/object to the specified store 193 | /// Waits for response 194 | /// 195 | /// 196 | /// An instance of StoreRecord that provides the store name and the data to put 197 | /// 198 | public async Task PutRecordAsync(StoreRecord recordToPut) 199 | { 200 | var trans = GenerateTransaction(); 201 | try 202 | { 203 | recordToPut.DbName = DbName; 204 | await CallJavascriptVoid(IndexedDbFunctions.PUT_ITEM, trans.trans, recordToPut); 205 | } 206 | catch (JSException e) 207 | { 208 | RaiseEvent(trans.trans, true, e.Message); 209 | } 210 | return await trans.task; 211 | } 212 | 213 | /// 214 | /// Updates and existing record 215 | /// 216 | /// 217 | /// An instance of UpdateRecord with the store name and the record to update 218 | /// 219 | public async Task UpdateRecord(UpdateRecord recordToUpdate, Action action = null) 220 | { 221 | var trans = GenerateTransaction(action); 222 | try 223 | { 224 | recordToUpdate.DbName = DbName; 225 | await CallJavascriptVoid(IndexedDbFunctions.UPDATE_ITEM, trans, recordToUpdate); 226 | } 227 | catch (JSException jse) 228 | { 229 | RaiseEvent(trans, true, jse.Message); 230 | } 231 | return trans; 232 | } 233 | 234 | /// 235 | /// Updates and existing record 236 | /// Waits for response 237 | /// 238 | /// 239 | /// An instance of UpdateRecord with the store name and the record to update 240 | /// 241 | public async Task UpdateRecordAsync(UpdateRecord recordToUpdate) 242 | { 243 | var trans = GenerateTransaction(); 244 | try 245 | { 246 | recordToUpdate.DbName = DbName; 247 | await CallJavascriptVoid(IndexedDbFunctions.UPDATE_ITEM, trans.trans, recordToUpdate); 248 | } 249 | catch (JSException jse) 250 | { 251 | RaiseEvent(trans.trans, true, jse.Message); 252 | } 253 | return await trans.task; 254 | } 255 | 256 | /// 257 | /// Retrieve a record by id 258 | /// 259 | /// 260 | /// 261 | /// The name of the store to retrieve the record from 262 | /// the id of the record 263 | /// 264 | public async Task GetRecordByIdAsync(string storeName, TInput key) 265 | { 266 | var trans = GenerateTransaction(null); 267 | 268 | var data = new { DbName = DbName, StoreName = storeName, Key = key }; 269 | try 270 | { 271 | return await CallJavascript(IndexedDbFunctions.FIND_ITEM, trans, data); 272 | } 273 | catch (JSException jse) 274 | { 275 | RaiseEvent(trans, true, jse.Message); 276 | } 277 | 278 | return default(TResult); 279 | } 280 | 281 | /// 282 | /// Filter a store on an indexed value 283 | /// 284 | /// The name of the store to retrieve the records from 285 | /// index field name to filter on 286 | /// filter's value 287 | /// 288 | public async Task> Where(string storeName, string indexName, object filterValue) 289 | { 290 | var filters = new List() { new IndexFilterValue(indexName, filterValue) }; 291 | return await Where(storeName, filters); 292 | } 293 | 294 | /// 295 | /// Filter a store on indexed values 296 | /// 297 | /// The name of the store to retrieve the records from 298 | /// A collection of index names and filters conditions 299 | /// 300 | public async Task> Where(string storeName, IEnumerable filters) 301 | { 302 | var trans = GenerateTransaction(null); 303 | 304 | try 305 | { 306 | 307 | return await CallJavascript>(IndexedDbFunctions.WHERE, trans, DbName, storeName, filters); 308 | } 309 | catch (JSException jse) 310 | { 311 | RaiseEvent(trans, true, jse.Message); 312 | } 313 | 314 | return default; 315 | } 316 | 317 | /// 318 | /// Retrieve all the records in a store 319 | /// 320 | /// 321 | /// The name of the store to retrieve the records from 322 | /// 323 | public async Task> ToArray(string storeName) 324 | { 325 | var trans = GenerateTransaction(null); 326 | 327 | try 328 | { 329 | return await CallJavascript>(IndexedDbFunctions.TOARRAY, trans, DbName, storeName); 330 | } 331 | catch (JSException jse) 332 | { 333 | RaiseEvent(trans, true, jse.Message); 334 | } 335 | 336 | return default; 337 | } 338 | 339 | /// 340 | /// Deletes a record from the store based on the id 341 | /// 342 | /// 343 | /// 344 | /// 345 | /// 346 | public async Task DeleteRecord(string storeName, TInput key, Action action = null) 347 | { 348 | var trans = GenerateTransaction(action); 349 | var data = new { DbName = DbName, StoreName = storeName, Key = key }; 350 | try 351 | { 352 | await CallJavascriptVoid(IndexedDbFunctions.DELETE_ITEM, trans, data); 353 | } 354 | catch (JSException jse) 355 | { 356 | RaiseEvent(trans, true, jse.Message); 357 | } 358 | return trans; 359 | } 360 | 361 | /// 362 | /// Deletes a record from the store based on the id 363 | /// Wait for response 364 | /// 365 | /// 366 | /// 367 | /// 368 | /// 369 | public async Task DeleteRecordAsync(string storeName, TInput key) 370 | { 371 | var trans = GenerateTransaction(); 372 | var data = new { DbName = DbName, StoreName = storeName, Key = key }; 373 | try 374 | { 375 | await CallJavascriptVoid(IndexedDbFunctions.DELETE_ITEM, trans.trans, data); 376 | } 377 | catch (JSException jse) 378 | { 379 | RaiseEvent(trans.trans, true, jse.Message); 380 | } 381 | return await trans.task; 382 | } 383 | 384 | /// 385 | /// Clears all data from a Table but keeps the table 386 | /// 387 | /// 388 | /// 389 | /// 390 | public async Task ClearTable(string storeName, Action action = null) 391 | { 392 | var trans = GenerateTransaction(action); 393 | try 394 | { 395 | await CallJavascriptVoid(IndexedDbFunctions.CLEAR_TABLE, trans, DbName, storeName); 396 | } 397 | catch (JSException jse) 398 | { 399 | RaiseEvent(trans, true, jse.Message); 400 | } 401 | return trans; 402 | } 403 | 404 | /// 405 | /// Clears all data from a Table but keeps the table 406 | /// Wait for response 407 | /// 408 | /// 409 | /// 410 | public async Task ClearTableAsync(string storeName) 411 | { 412 | var trans = GenerateTransaction(); 413 | try 414 | { 415 | await CallJavascriptVoid(IndexedDbFunctions.CLEAR_TABLE, trans.trans, DbName, storeName); 416 | } 417 | catch (JSException jse) 418 | { 419 | RaiseEvent(trans.trans, true, jse.Message); 420 | } 421 | return await trans.task; 422 | } 423 | 424 | [JSInvokable("BlazorDBCallback")] 425 | public void CalledFromJS(Guid transaction, bool failed, string message) 426 | { 427 | if(transaction != Guid.Empty) 428 | { 429 | WeakReference> r = null; 430 | _transactions.TryGetValue(transaction, out r); 431 | TaskCompletionSource t = null; 432 | _taskTransactions.TryGetValue(transaction, out t); 433 | if(r != null && r.TryGetTarget(out Action action)) 434 | { 435 | action?.Invoke(new BlazorDbEvent() 436 | { 437 | Transaction = transaction, 438 | Message = message, 439 | Failed = failed 440 | }); 441 | _transactions.Remove(transaction); 442 | } 443 | else if(t != null) 444 | { 445 | t.TrySetResult(new BlazorDbEvent() 446 | { 447 | Transaction = transaction, 448 | Message = message, 449 | Failed = failed 450 | }); 451 | _taskTransactions.Remove(transaction); 452 | } 453 | else 454 | RaiseEvent(transaction, failed, message); 455 | } 456 | } 457 | 458 | async Task CallJavascript(string functionName, Guid transaction, params object[] args) 459 | { 460 | var newArgs = GetNewArgs(transaction, args); 461 | return await _jsRuntime.InvokeAsync($"{InteropPrefix}.{functionName}", newArgs); 462 | } 463 | async Task CallJavascriptVoid(string functionName, Guid transaction, params object[] args) 464 | { 465 | var newArgs = GetNewArgs(transaction, args); 466 | await _jsRuntime.InvokeVoidAsync($"{InteropPrefix}.{functionName}", newArgs); 467 | } 468 | 469 | object[] GetNewArgs(Guid transaction, params object[] args) 470 | { 471 | var newArgs = new object[args.Length + 2]; 472 | newArgs[0] = _objReference; 473 | newArgs[1] = transaction; 474 | for(var i = 0; i < args.Length; i++) 475 | newArgs[i + 2] = args[i]; 476 | return newArgs; 477 | } 478 | 479 | (Guid trans, Task task) GenerateTransaction() 480 | { 481 | bool generated = false; 482 | var transaction = Guid.Empty; 483 | TaskCompletionSource tcs = new TaskCompletionSource(); 484 | do 485 | { 486 | transaction = Guid.NewGuid(); 487 | if(!_taskTransactions.ContainsKey(transaction)) 488 | { 489 | generated = true; 490 | _taskTransactions.Add(transaction, tcs); 491 | } 492 | } while(!generated); 493 | return (transaction, tcs.Task); 494 | } 495 | 496 | Guid GenerateTransaction(Action action) 497 | { 498 | bool generated = false; 499 | Guid transaction = Guid.Empty; 500 | do 501 | { 502 | transaction = Guid.NewGuid(); 503 | if(!_transactions.ContainsKey(transaction)) 504 | { 505 | generated = true; 506 | _transactions.Add(transaction, new WeakReference>(action)); 507 | } 508 | } while(!generated); 509 | return transaction; 510 | } 511 | 512 | void RaiseEvent(Guid transaction, bool failed, string message) 513 | => ActionCompleted?.Invoke(this, new BlazorDbEvent { Transaction = transaction, Failed = failed, Message = message }); 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/BlazorDB/IndexFilterValue.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDB 2 | { 3 | public class IndexFilterValue 4 | { 5 | public IndexFilterValue(string indexName, object filterValue) 6 | { 7 | IndexName = indexName; 8 | FilterValue = filterValue; 9 | } 10 | 11 | public string IndexName { get; set; } 12 | public object FilterValue { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/BlazorDB/IndexedDbFunctions.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDB 2 | { 3 | internal struct IndexedDbFunctions 4 | { 5 | public const string CREATE_DB = "createDb"; 6 | public const string DELETE_DB = "deleteDb"; 7 | public const string ADD_ITEM = "addItem"; 8 | public const string BULKADD_ITEM = "bulkAddItem"; 9 | public const string PUT_ITEM = "putItem"; 10 | public const string UPDATE_ITEM = "updateItem"; 11 | public const string DELETE_ITEM = "deleteItem"; 12 | public const string CLEAR_TABLE = "clear"; 13 | public const string FIND_ITEM = "findItem"; 14 | public const string TOARRAY = "toArray"; 15 | public const string WHERE = "where"; 16 | } 17 | } -------------------------------------------------------------------------------- /src/BlazorDB/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace BlazorDB 6 | { 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddBlazorDB(this IServiceCollection services, Action options) 10 | { 11 | var dbStore = new DbStore(); 12 | options(dbStore); 13 | 14 | services.AddTransient((_) => dbStore); 15 | services.TryAddSingleton(); 16 | 17 | return services; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/BlazorDB/StoreRecord.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDB 2 | { 3 | public class StoreRecord 4 | { 5 | public string DbName { get; set; } 6 | public string StoreName { get; set; } 7 | public T Record { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/BlazorDB/StoreSchema.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorDB 4 | { 5 | public class StoreSchema 6 | { 7 | public string Name { get; set; } 8 | public string PrimaryKey { get; set; } 9 | public bool PrimaryKeyAuto { get; set; } 10 | public List UniqueIndexes { get; set; } = new List(); 11 | public List Indexes { get; set; } = new List(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/BlazorDB/UpdateRecord.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDB 2 | { 3 | public class UpdateRecord : StoreRecord 4 | { 5 | public object Key { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/BlazorDB/wwwroot/blazorDB.js: -------------------------------------------------------------------------------- 1 | window.blazorDB = { 2 | databases: [], 3 | createDb: function(dotnetReference, transaction, dbStore) { 4 | if(window.blazorDB.databases.find(d => d.name == dbStore.name) !== undefined) 5 | console.warn("Blazor.IndexedDB.Framework - Database already exists"); 6 | 7 | var db = new Dexie(dbStore.name); 8 | 9 | var stores = {}; 10 | for(var i = 0; i < dbStore.storeSchemas.length; i++) { 11 | // build string 12 | var schema = dbStore.storeSchemas[i]; 13 | var def = ""; 14 | if(schema.primaryKeyAuto) 15 | def = def + "++"; 16 | if(schema.primaryKey !== null && schema.primaryKey !== "") 17 | def = def + schema.primaryKey; 18 | if(schema.uniqueIndexes !== undefined) { 19 | for(var j = 0; j < schema.uniqueIndexes.length; j++) { 20 | def = def + ","; 21 | var u = "&" + schema.uniqueIndexes[j]; 22 | def = def + u; 23 | } 24 | } 25 | if (schema.indexes !== undefined) { 26 | for (var j = 0; j < schema.indexes.length; j++) { 27 | def = def + ","; 28 | var u = schema.indexes[j]; 29 | def = def + u; 30 | } 31 | } 32 | 33 | stores[schema.name] = def; 34 | } 35 | db.version(dbStore.version).stores(stores); 36 | if(window.blazorDB.databases.find(d => d.name == dbStore.name) !== undefined) { 37 | window.blazorDB.databases.find(d => d.name == dbStore.name).db = db; 38 | } else { 39 | window.blazorDB.databases.push({ 40 | name: dbStore.name, 41 | db: db 42 | }); 43 | } 44 | db.open().then(_ => { 45 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Database opened'); 46 | }).catch(e => { 47 | console.error(e); 48 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Database could not be opened'); 49 | }); 50 | }, 51 | deleteDb: function(dotnetReference, transaction, dbName) { 52 | window.blazorDB.getDb(dbName).then(db => { 53 | var index = window.blazorDB.databases.findIndex(d => d.name == dbName); 54 | window.blazorDB.databases.splice(index, 1); 55 | db.delete().then(_ => { 56 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Database deleted'); 57 | }).catch(e => { 58 | console.error(e); 59 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Database could not be deleted'); 60 | }); 61 | }); 62 | }, 63 | addItem: function(dotnetReference, transaction, item) { 64 | window.blazorDB.getTable(item.dbName, item.storeName).then(table => { 65 | table.add(item.record).then(_ => { 66 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Item added'); 67 | }).catch(e => { 68 | console.error(e); 69 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Item could not be added'); 70 | }); 71 | }); 72 | }, 73 | bulkAddItem: function(dotnetReference, transaction, dbName, storeName, items) { 74 | window.blazorDB.getTable(dbName, storeName).then(table => { 75 | table.bulkAdd(items).then(_ => { 76 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Item(s) bulk added'); 77 | }).catch(e => { 78 | console.error(e); 79 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Item(s) could not be bulk added'); 80 | }); 81 | }); 82 | }, 83 | putItem: function(dotnetReference, transaction, item) { 84 | window.blazorDB.getTable(item.dbName, item.storeName).then(table => { 85 | table.put(item.record).then(_ => { 86 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Item put successful'); 87 | }).catch(e => { 88 | console.error(e); 89 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Item put failed'); 90 | }); 91 | }); 92 | }, 93 | updateItem: function(dotnetReference, transaction, item) { 94 | window.blazorDB.getTable(item.dbName, item.storeName).then(table => { 95 | table.update(item.key, item.record).then(_ => { 96 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Item updated'); 97 | }).catch(e => { 98 | console.error(e); 99 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Item could not be updated'); 100 | }); 101 | }); 102 | }, 103 | deleteItem: function(dotnetReference, transaction, item) { 104 | window.blazorDB.getTable(item.dbName, item.storeName).then(table => { 105 | table.delete(item.key).then(_ => { 106 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Item deleted'); 107 | }).catch(e => { 108 | console.error(e); 109 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Item could not be deleted'); 110 | }); 111 | }); 112 | }, 113 | clear: function(dotnetReference, transaction, dbName, storeName) { 114 | window.blazorDB.getTable(dbName, storeName).then(table => { 115 | table.clear().then(_ => { 116 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Table cleared'); 117 | }).catch(e => { 118 | console.error(e); 119 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Table could not be cleared'); 120 | }); 121 | }); 122 | }, 123 | findItem: function(dotnetReference, transaction, item) { 124 | var promise = new Promise((resolve, reject) => { 125 | window.blazorDB.getTable(item.dbName, item.storeName).then(table => { 126 | table.get(item.key).then(i => { 127 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'Found item'); 128 | resolve(i); 129 | }).catch(e => { 130 | console.error(e); 131 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'Could not find item'); 132 | reject(e); 133 | }); 134 | }); 135 | }); 136 | return promise; 137 | }, 138 | toArray: function(dotnetReference, transaction, dbName, storeName) { 139 | return new Promise((resolve, reject) => { 140 | window.blazorDB.getTable(dbName, storeName).then(table => { 141 | table.toArray(items => { 142 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'toArray succeeded'); 143 | resolve(items); 144 | }).catch(e => { 145 | console.error(e); 146 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'toArray failed'); 147 | reject(e); 148 | }); 149 | }); 150 | }); 151 | }, 152 | getDb: function(dbName) { 153 | return new Promise((resolve, reject) => { 154 | if(window.blazorDB.databases.find(d => d.name == dbName) === undefined) { 155 | console.warn("Blazor.IndexedDB.Framework - Database doesn't exist"); 156 | var db1 = new Dexie(dbName); 157 | db1.open().then(function (db) { 158 | if(window.blazorDB.databases.find(d => d.name == dbName) !== undefined) { 159 | window.blazorDB.databases.find(d => d.name == dbName).db = db1; 160 | } else { 161 | window.blazorDB.databases.push({ 162 | name: dbName, 163 | db: db1 164 | }); 165 | } 166 | resolve(db1); 167 | }).catch('NoSuchDatabaseError', function(e) { 168 | // Database with that name did not exist 169 | console.error ("Database not found"); 170 | reject("No database"); 171 | }); 172 | } else { 173 | var db = window.blazorDB.databases.find(d => d.name == dbName).db; 174 | resolve(db); 175 | } 176 | }); 177 | }, 178 | getTable: function(dbName, storeName) { 179 | return new Promise((resolve, reject) => { 180 | window.blazorDB.getDb(dbName).then(db => { 181 | var table = db.table(storeName); 182 | resolve(table); 183 | }); 184 | }); 185 | }, 186 | createFilterObject: function (filters) { 187 | const jsonFilter = {}; 188 | for (const filter in filters) { 189 | if (filters.hasOwnProperty(filter)) 190 | jsonFilter[filters[filter].indexName] = filters[filter].filterValue; 191 | } 192 | return jsonFilter; 193 | }, 194 | where: function(dotnetReference, transaction, dbName, storeName, filters) { 195 | const filterObject = this.createFilterObject(filters); 196 | return new Promise((resolve, reject) => { 197 | window.blazorDB.getTable(dbName, storeName).then(table => { 198 | table.where(filterObject).toArray(items => { 199 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, false, 'where succeeded'); 200 | resolve(items); 201 | }) 202 | }).catch(e => { 203 | console.error(e); 204 | dotnetReference.invokeMethodAsync('BlazorDBCallback', transaction, true, 'where failed'); 205 | reject(e); 206 | }); 207 | }); 208 | } 209 | } --------------------------------------------------------------------------------