diff --git a/.dockerignore b/.dockerignore index 415419ce..fe1152bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,4 +21,10 @@ **/obj **/secrets.dev.yaml **/values.dev.yaml -LICENSE \ No newline at end of file +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index c9a901a8..5b6c5012 100644 --- a/.editorconfig +++ b/.editorconfig @@ -98,4 +98,4 @@ csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent -file_header_template = Copyright © 2023-Present The Cloud Streams Authors\n\nLicensed under the Apache License, Version 2.0 (the "License"),\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License. \ No newline at end of file +file_header_template = Copyright © 2024-Present The Cloud Streams Authors\n\nLicensed under the Apache License, Version 2.0 (the "License"),\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License. \ No newline at end of file diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 3088574e..7019f886 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: ['7.0.x' ] + dotnet-version: ['8.0.x' ] steps: - name: Checkout diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0098ecc4..52e0fc16 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore "${{ env.SOLUTION }}" - name: Build @@ -25,8 +25,8 @@ jobs: - name: Push1 run: dotnet nuget push "./src/*/*/bin/Release/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate - publish-api-server-image: - name: Publish API Server image + publish-api-image: + name: Publish API image runs-on: ubuntu-latest steps: - name: Checkout repository @@ -41,7 +41,7 @@ jobs: id: meta uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: - images: ${{ env.REGISTRY }}/${{ github.repository }}-api-server + images: ${{ env.REGISTRY }}/${{ github.repository }}-api tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -49,7 +49,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: './src/core/CloudStreams.Core.Api.Server/Dockerfile' + file: './src/core/CloudStreams.Core.Api/Dockerfile' push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -78,7 +78,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: './src/broker/CloudStreams.Broker.Api.Server/Dockerfile' + file: './src/broker/CloudStreams.Broker.Api/Dockerfile' push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -107,13 +107,13 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: './src/gateway/CloudStreams.Gateway.Api.Server/Dockerfile' + file: './src/gateway/CloudStreams.Gateway.Api/Dockerfile' push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - publish-api-server-bin: - name: Publish API Server Binaries + publish-api-bin: + name: Publish API Binaries strategy: matrix: kind: ['linux', 'windows', 'macOS'] @@ -134,7 +134,7 @@ jobs: - name: Setup uses: actions/setup-dotnet@v2 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore run: dotnet restore - name: Build @@ -143,7 +143,7 @@ jobs: tag=$(git describe --tags --abbrev=0) release_name="cloud-streams-broker-${{ matrix.target }}" # Publish - dotnet publish src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" + dotnet publish src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" # Pack if [ "${{ matrix.target }}" == "win-x64" ]; then # Pack for Windows @@ -157,7 +157,7 @@ jobs: - name: Publish uses: softprops/action-gh-release@v1 with: - files: "cloud-streams-api-server*" + files: "cloud-streams-api*" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -192,7 +192,7 @@ jobs: tag=$(git describe --tags --abbrev=0) release_name="cloud-streams-broker-${{ matrix.target }}" # Publish - dotnet publish src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" + dotnet publish src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" # Pack if [ "${{ matrix.target }}" == "win-x64" ]; then # Pack for Windows @@ -241,7 +241,7 @@ jobs: tag=$(git describe --tags --abbrev=0) release_name="cloud-streams-gateway-${{ matrix.target }}" # Publish - dotnet publish src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" + dotnet publish src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj --runtime "${{ matrix.target }}" -c Release -o "$release_name" # Pack if [ "${{ matrix.target }}" == "win-x64" ]; then # Pack for Windows diff --git a/.github/workflows/test-dotnet.yml b/.github/workflows/test-dotnet.yml index e9a80c26..9f08d220 100644 --- a/.github/workflows/test-dotnet.yml +++ b/.github/workflows/test-dotnet.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: ['7.0.x' ] + dotnet-version: ['8.0.x' ] steps: - name: Checkout diff --git a/.gitignore b/.gitignore index 261c522e..8a30d258 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser @@ -23,6 +23,7 @@ mono_crash.* [Rr]eleases/ x64/ x86/ +[Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ @@ -61,6 +62,9 @@ project.lock.json project.fragment.lock.json artifacts/ +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + # StyleCop StyleCopReport.xml @@ -86,6 +90,7 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -137,6 +142,11 @@ _TeamCity* .axoCover/* !.axoCover/settings.json +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + # Visual Studio code coverage results *.coverage *.coveragexml @@ -284,6 +294,17 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -340,6 +361,9 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ +# Visual Studio History (VSHistory) files +.vshistory/ + # BeatPulse healthcheck temp database healthchecksdb @@ -348,5 +372,27 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ -/deployments/docker-compose/docker-compose.yml -/deployments/docker-compose/docker-compose.override.yml + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/CloudStreams.sln b/CloudStreams.sln index 61418739..37660a7a 100644 --- a/CloudStreams.sln +++ b/CloudStreams.sln @@ -1,178 +1,121 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.6.33815.320 +VisualStudioVersion = 17.9.34714.143 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DC60318D-AFF7-4099-A3E2-AF78BDEE194A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FF66B57F-6960-4E8B-B890-5C1D4287DAE3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{F474FF36-0A30-455E-9A10-A1615E3604D4}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{D14FE817-6B8B-4987-9203-A74B55BA23AF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core", "src\core\CloudStreams.Core\CloudStreams.Core.csproj", "{B0CC48AC-6E34-4D91-AF0C-F63FCDC17900}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gateway", "gateway", "{41B16F55-F5D9-4FC5-A955-A577AFBA6EAF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Infrastructure", "src\core\CloudStreams.Core.Infrastructure\CloudStreams.Core.Infrastructure.csproj", "{2F29487D-C4AD-4CD9-9969-6080D2F910B8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "broker", "broker", "{EE7E5147-3DE2-41C1-A3C4-695CC41B2785}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Infrastructure.EventSourcing.EventStore", "src\core\CloudStreams.Core.Infrastructure.EventSourcing.EventStore\CloudStreams.Core.Infrastructure.EventSourcing.EventStore.csproj", "{4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core", "src\core\CloudStreams.Core\CloudStreams.Core.csproj", "{177F238A-0173-49B9-925C-0B5432C75EC6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Api", "src\core\CloudStreams.Core.Api\CloudStreams.Core.Api.csproj", "{6DFD3670-466E-491E-B1D2-173621722A11}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Application", "src\core\CloudStreams.Core.Application\CloudStreams.Core.Application.csproj", "{D776F8D4-04E8-4095-AD03-D71F5EBF8951}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Api.Client", "src\core\CloudStreams.Core.Api.Client\CloudStreams.Core.Api.Client.csproj", "{19535702-F1D9-420B-BF3B-2A585663F40F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Api.Client", "src\core\CloudStreams.Core.Api.Client\CloudStreams.Core.Api.Client.csproj", "{8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Api.Server", "src\core\CloudStreams.Core.Api.Server\CloudStreams.Core.Api.Server.csproj", "{EFA9105F-F074-4257-8E8C-633B7DF7EBE8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Api", "src\core\CloudStreams.Core.Api\CloudStreams.Core.Api.csproj", "{55E026C9-D780-49E4-8F82-C6F303C10BE9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Application", "src\core\CloudStreams.Core.Application\CloudStreams.Core.Application.csproj", "{19671559-E4BD-49C3-AF13-AEAB2EA0D48B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Api", "src\gateway\CloudStreams.Gateway.Api\CloudStreams.Gateway.Api.csproj", "{2F904542-163A-49A8-832A-3545A962234C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gateway", "gateway", "{5E739A12-4175-4DB9-8094-8EB9BE7CA7F7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Application", "src\gateway\CloudStreams.Gateway.Application\CloudStreams.Gateway.Application.csproj", "{07217222-82B9-490E-B722-4C836A84A0C7}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dashboard", "dashboard", "{AB70869A-91DF-4285-BAB9-6836A68E5F7A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Broker.Application", "src\broker\CloudStreams.Broker.Application\CloudStreams.Broker.Application.csproj", "{09481105-759E-4EC7-AF2E-1343ECF953B2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Dashboard.StateManagement", "src\dashboard\CloudStreams.Dashboard.StateManagement\CloudStreams.Dashboard.StateManagement.csproj", "{932E98CC-50B8-4CA1-9080-B55627A07E97}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Broker.Api", "src\broker\CloudStreams.Broker.Api\CloudStreams.Broker.Api.csproj", "{92EDB067-DD65-4106-A0DA-5DC4B4386DFD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Dashboard", "src\dashboard\CloudStreams.Dashboard\CloudStreams.Dashboard.csproj", "{47133552-BF81-44E4-B127-8256D97EA695}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dashboard", "dashboard", "{BDA26004-D077-442C-A4F7-F389D5AC9FE5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{BE3EB57F-15FA-49E8-B21F-B648E78AC87B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Dashboard", "src\dashboard\CloudStreams.Dashboard\CloudStreams.Dashboard.csproj", "{DEF277AF-8024-49D8-BD80-338A79431F8C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deployments", "deployments", "{43DFCA07-D86F-472B-AF59-B979DC9C948C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Dashboard.StateManagement", "src\dashboard\CloudStreams.Dashboard.StateManagement\CloudStreams.Dashboard.StateManagement.csproj", "{7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "deployments\docker-compose\docker-compose.dcproj", "{0050C106-97E3-40BD-8C2C-34503B5757DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Application", "src\gateway\CloudStreams.Gateway.Application\CloudStreams.Gateway.Application.csproj", "{09853D11-38D0-4DCD-9138-56C1754EEECD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Api.Client", "src\gateway\CloudStreams.Gateway.Api.Client\CloudStreams.Gateway.Api.Client.csproj", "{F75AA225-432E-4A10-8017-75274673E80C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Api", "src\gateway\CloudStreams.Gateway.Api\CloudStreams.Gateway.Api.csproj", "{AD031F2B-498F-490B-AF1E-67DE43F75AC4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Gateway.Api.Server", "src\gateway\CloudStreams.Gateway.Api.Server\CloudStreams.Gateway.Api.Server.csproj", "{5148840D-7E3E-4BC9-A098-EF46999750C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript", "src\core\CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript\CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript.csproj", "{8C91F090-0028-4A78-AD5E-523BBFD4A936}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ", "src\core\CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ\CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ.csproj", "{901F926B-6BA1-4D7F-97D5-F464BDF58113}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "broker", "broker", "{DB906BCE-DF5E-48A4-B2D0-78EBD504EE08}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Broker.Application", "src\broker\CloudStreams.Broker.Application\CloudStreams.Broker.Application.csproj", "{56C38938-070C-46CD-A9A2-812D0389A527}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Broker.Api", "src\broker\CloudStreams.Broker.Api\CloudStreams.Broker.Api.csproj", "{B8BD211C-D29B-4763-8780-B8C1AB7CA867}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudStreams.Broker.Api.Server", "src\broker\CloudStreams.Broker.Api.Server\CloudStreams.Broker.Api.Server.csproj", "{15686F8D-528D-4525-AB4C-920356A9FAF7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "kubernetes", "kubernetes", "{D8AC6338-9A30-4FD5-A2EB-F46760F074C6}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{129B2507-0C27-4324-AFC9-2C802E25E51D}" ProjectSection(SolutionItems) = preProject - deployments\kubernetes\deployment.yaml = deployments\kubernetes\deployment.yaml + LICENSE = LICENSE + code-of-conduct.md = code-of-conduct.md + README.md = README.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deployments", "deployments", "{990DE5C0-9BDB-417C-91D9-B3883C5FCA3E}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "deployments\docker-compose\docker-compose.dcproj", "{0050C106-97E3-40BD-8C2C-34503B5757DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B0CC48AC-6E34-4D91-AF0C-F63FCDC17900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0CC48AC-6E34-4D91-AF0C-F63FCDC17900}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0CC48AC-6E34-4D91-AF0C-F63FCDC17900}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0CC48AC-6E34-4D91-AF0C-F63FCDC17900}.Release|Any CPU.Build.0 = Release|Any CPU - {2F29487D-C4AD-4CD9-9969-6080D2F910B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F29487D-C4AD-4CD9-9969-6080D2F910B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F29487D-C4AD-4CD9-9969-6080D2F910B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F29487D-C4AD-4CD9-9969-6080D2F910B8}.Release|Any CPU.Build.0 = Release|Any CPU - {4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8}.Release|Any CPU.Build.0 = Release|Any CPU - {6DFD3670-466E-491E-B1D2-173621722A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DFD3670-466E-491E-B1D2-173621722A11}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DFD3670-466E-491E-B1D2-173621722A11}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DFD3670-466E-491E-B1D2-173621722A11}.Release|Any CPU.Build.0 = Release|Any CPU - {19535702-F1D9-420B-BF3B-2A585663F40F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19535702-F1D9-420B-BF3B-2A585663F40F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19535702-F1D9-420B-BF3B-2A585663F40F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19535702-F1D9-420B-BF3B-2A585663F40F}.Release|Any CPU.Build.0 = Release|Any CPU - {EFA9105F-F074-4257-8E8C-633B7DF7EBE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFA9105F-F074-4257-8E8C-633B7DF7EBE8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFA9105F-F074-4257-8E8C-633B7DF7EBE8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFA9105F-F074-4257-8E8C-633B7DF7EBE8}.Release|Any CPU.Build.0 = Release|Any CPU - {19671559-E4BD-49C3-AF13-AEAB2EA0D48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19671559-E4BD-49C3-AF13-AEAB2EA0D48B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19671559-E4BD-49C3-AF13-AEAB2EA0D48B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19671559-E4BD-49C3-AF13-AEAB2EA0D48B}.Release|Any CPU.Build.0 = Release|Any CPU - {932E98CC-50B8-4CA1-9080-B55627A07E97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {932E98CC-50B8-4CA1-9080-B55627A07E97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {932E98CC-50B8-4CA1-9080-B55627A07E97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {932E98CC-50B8-4CA1-9080-B55627A07E97}.Release|Any CPU.Build.0 = Release|Any CPU - {47133552-BF81-44E4-B127-8256D97EA695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47133552-BF81-44E4-B127-8256D97EA695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47133552-BF81-44E4-B127-8256D97EA695}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47133552-BF81-44E4-B127-8256D97EA695}.Release|Any CPU.Build.0 = Release|Any CPU + {177F238A-0173-49B9-925C-0B5432C75EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {177F238A-0173-49B9-925C-0B5432C75EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {177F238A-0173-49B9-925C-0B5432C75EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {177F238A-0173-49B9-925C-0B5432C75EC6}.Release|Any CPU.Build.0 = Release|Any CPU + {D776F8D4-04E8-4095-AD03-D71F5EBF8951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D776F8D4-04E8-4095-AD03-D71F5EBF8951}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D776F8D4-04E8-4095-AD03-D71F5EBF8951}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D776F8D4-04E8-4095-AD03-D71F5EBF8951}.Release|Any CPU.Build.0 = Release|Any CPU + {8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254}.Release|Any CPU.Build.0 = Release|Any CPU + {55E026C9-D780-49E4-8F82-C6F303C10BE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55E026C9-D780-49E4-8F82-C6F303C10BE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55E026C9-D780-49E4-8F82-C6F303C10BE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55E026C9-D780-49E4-8F82-C6F303C10BE9}.Release|Any CPU.Build.0 = Release|Any CPU + {2F904542-163A-49A8-832A-3545A962234C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F904542-163A-49A8-832A-3545A962234C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F904542-163A-49A8-832A-3545A962234C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F904542-163A-49A8-832A-3545A962234C}.Release|Any CPU.Build.0 = Release|Any CPU + {07217222-82B9-490E-B722-4C836A84A0C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07217222-82B9-490E-B722-4C836A84A0C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07217222-82B9-490E-B722-4C836A84A0C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07217222-82B9-490E-B722-4C836A84A0C7}.Release|Any CPU.Build.0 = Release|Any CPU + {09481105-759E-4EC7-AF2E-1343ECF953B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09481105-759E-4EC7-AF2E-1343ECF953B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09481105-759E-4EC7-AF2E-1343ECF953B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09481105-759E-4EC7-AF2E-1343ECF953B2}.Release|Any CPU.Build.0 = Release|Any CPU + {92EDB067-DD65-4106-A0DA-5DC4B4386DFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92EDB067-DD65-4106-A0DA-5DC4B4386DFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92EDB067-DD65-4106-A0DA-5DC4B4386DFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92EDB067-DD65-4106-A0DA-5DC4B4386DFD}.Release|Any CPU.Build.0 = Release|Any CPU + {DEF277AF-8024-49D8-BD80-338A79431F8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEF277AF-8024-49D8-BD80-338A79431F8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEF277AF-8024-49D8-BD80-338A79431F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEF277AF-8024-49D8-BD80-338A79431F8C}.Release|Any CPU.Build.0 = Release|Any CPU + {7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06}.Release|Any CPU.Build.0 = Release|Any CPU {0050C106-97E3-40BD-8C2C-34503B5757DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0050C106-97E3-40BD-8C2C-34503B5757DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0050C106-97E3-40BD-8C2C-34503B5757DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0050C106-97E3-40BD-8C2C-34503B5757DF}.Release|Any CPU.Build.0 = Release|Any CPU - {09853D11-38D0-4DCD-9138-56C1754EEECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09853D11-38D0-4DCD-9138-56C1754EEECD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09853D11-38D0-4DCD-9138-56C1754EEECD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09853D11-38D0-4DCD-9138-56C1754EEECD}.Release|Any CPU.Build.0 = Release|Any CPU - {F75AA225-432E-4A10-8017-75274673E80C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F75AA225-432E-4A10-8017-75274673E80C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F75AA225-432E-4A10-8017-75274673E80C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F75AA225-432E-4A10-8017-75274673E80C}.Release|Any CPU.Build.0 = Release|Any CPU - {AD031F2B-498F-490B-AF1E-67DE43F75AC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD031F2B-498F-490B-AF1E-67DE43F75AC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD031F2B-498F-490B-AF1E-67DE43F75AC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD031F2B-498F-490B-AF1E-67DE43F75AC4}.Release|Any CPU.Build.0 = Release|Any CPU - {5148840D-7E3E-4BC9-A098-EF46999750C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5148840D-7E3E-4BC9-A098-EF46999750C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5148840D-7E3E-4BC9-A098-EF46999750C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5148840D-7E3E-4BC9-A098-EF46999750C1}.Release|Any CPU.Build.0 = Release|Any CPU - {8C91F090-0028-4A78-AD5E-523BBFD4A936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C91F090-0028-4A78-AD5E-523BBFD4A936}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C91F090-0028-4A78-AD5E-523BBFD4A936}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C91F090-0028-4A78-AD5E-523BBFD4A936}.Release|Any CPU.Build.0 = Release|Any CPU - {901F926B-6BA1-4D7F-97D5-F464BDF58113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {901F926B-6BA1-4D7F-97D5-F464BDF58113}.Debug|Any CPU.Build.0 = Debug|Any CPU - {901F926B-6BA1-4D7F-97D5-F464BDF58113}.Release|Any CPU.ActiveCfg = Release|Any CPU - {901F926B-6BA1-4D7F-97D5-F464BDF58113}.Release|Any CPU.Build.0 = Release|Any CPU - {56C38938-070C-46CD-A9A2-812D0389A527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56C38938-070C-46CD-A9A2-812D0389A527}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56C38938-070C-46CD-A9A2-812D0389A527}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56C38938-070C-46CD-A9A2-812D0389A527}.Release|Any CPU.Build.0 = Release|Any CPU - {B8BD211C-D29B-4763-8780-B8C1AB7CA867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8BD211C-D29B-4763-8780-B8C1AB7CA867}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8BD211C-D29B-4763-8780-B8C1AB7CA867}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B8BD211C-D29B-4763-8780-B8C1AB7CA867}.Release|Any CPU.Build.0 = Release|Any CPU - {15686F8D-528D-4525-AB4C-920356A9FAF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15686F8D-528D-4525-AB4C-920356A9FAF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15686F8D-528D-4525-AB4C-920356A9FAF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15686F8D-528D-4525-AB4C-920356A9FAF7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {F474FF36-0A30-455E-9A10-A1615E3604D4} = {DC60318D-AFF7-4099-A3E2-AF78BDEE194A} - {B0CC48AC-6E34-4D91-AF0C-F63FCDC17900} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {2F29487D-C4AD-4CD9-9969-6080D2F910B8} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {4C5192B7-2F5D-4513-B8BA-66A5A5FA6EF8} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {6DFD3670-466E-491E-B1D2-173621722A11} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {19535702-F1D9-420B-BF3B-2A585663F40F} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {EFA9105F-F074-4257-8E8C-633B7DF7EBE8} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {19671559-E4BD-49C3-AF13-AEAB2EA0D48B} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {5E739A12-4175-4DB9-8094-8EB9BE7CA7F7} = {DC60318D-AFF7-4099-A3E2-AF78BDEE194A} - {AB70869A-91DF-4285-BAB9-6836A68E5F7A} = {DC60318D-AFF7-4099-A3E2-AF78BDEE194A} - {932E98CC-50B8-4CA1-9080-B55627A07E97} = {AB70869A-91DF-4285-BAB9-6836A68E5F7A} - {47133552-BF81-44E4-B127-8256D97EA695} = {AB70869A-91DF-4285-BAB9-6836A68E5F7A} - {0050C106-97E3-40BD-8C2C-34503B5757DF} = {43DFCA07-D86F-472B-AF59-B979DC9C948C} - {09853D11-38D0-4DCD-9138-56C1754EEECD} = {5E739A12-4175-4DB9-8094-8EB9BE7CA7F7} - {F75AA225-432E-4A10-8017-75274673E80C} = {5E739A12-4175-4DB9-8094-8EB9BE7CA7F7} - {AD031F2B-498F-490B-AF1E-67DE43F75AC4} = {5E739A12-4175-4DB9-8094-8EB9BE7CA7F7} - {5148840D-7E3E-4BC9-A098-EF46999750C1} = {5E739A12-4175-4DB9-8094-8EB9BE7CA7F7} - {8C91F090-0028-4A78-AD5E-523BBFD4A936} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {901F926B-6BA1-4D7F-97D5-F464BDF58113} = {F474FF36-0A30-455E-9A10-A1615E3604D4} - {DB906BCE-DF5E-48A4-B2D0-78EBD504EE08} = {DC60318D-AFF7-4099-A3E2-AF78BDEE194A} - {56C38938-070C-46CD-A9A2-812D0389A527} = {DB906BCE-DF5E-48A4-B2D0-78EBD504EE08} - {B8BD211C-D29B-4763-8780-B8C1AB7CA867} = {DB906BCE-DF5E-48A4-B2D0-78EBD504EE08} - {15686F8D-528D-4525-AB4C-920356A9FAF7} = {DB906BCE-DF5E-48A4-B2D0-78EBD504EE08} - {D8AC6338-9A30-4FD5-A2EB-F46760F074C6} = {43DFCA07-D86F-472B-AF59-B979DC9C948C} + {D14FE817-6B8B-4987-9203-A74B55BA23AF} = {FF66B57F-6960-4E8B-B890-5C1D4287DAE3} + {41B16F55-F5D9-4FC5-A955-A577AFBA6EAF} = {FF66B57F-6960-4E8B-B890-5C1D4287DAE3} + {EE7E5147-3DE2-41C1-A3C4-695CC41B2785} = {FF66B57F-6960-4E8B-B890-5C1D4287DAE3} + {177F238A-0173-49B9-925C-0B5432C75EC6} = {D14FE817-6B8B-4987-9203-A74B55BA23AF} + {D776F8D4-04E8-4095-AD03-D71F5EBF8951} = {D14FE817-6B8B-4987-9203-A74B55BA23AF} + {8F4260F3-8F1D-4C6B-9BC5-A7B8DD1E8254} = {D14FE817-6B8B-4987-9203-A74B55BA23AF} + {55E026C9-D780-49E4-8F82-C6F303C10BE9} = {D14FE817-6B8B-4987-9203-A74B55BA23AF} + {2F904542-163A-49A8-832A-3545A962234C} = {41B16F55-F5D9-4FC5-A955-A577AFBA6EAF} + {07217222-82B9-490E-B722-4C836A84A0C7} = {41B16F55-F5D9-4FC5-A955-A577AFBA6EAF} + {09481105-759E-4EC7-AF2E-1343ECF953B2} = {EE7E5147-3DE2-41C1-A3C4-695CC41B2785} + {92EDB067-DD65-4106-A0DA-5DC4B4386DFD} = {EE7E5147-3DE2-41C1-A3C4-695CC41B2785} + {BDA26004-D077-442C-A4F7-F389D5AC9FE5} = {FF66B57F-6960-4E8B-B890-5C1D4287DAE3} + {DEF277AF-8024-49D8-BD80-338A79431F8C} = {BDA26004-D077-442C-A4F7-F389D5AC9FE5} + {7BD5FCBA-1D5A-4DF2-BDC3-5672D3507C06} = {BDA26004-D077-442C-A4F7-F389D5AC9FE5} + {0050C106-97E3-40BD-8C2C-34503B5757DF} = {990DE5C0-9BDB-417C-91D9-B3883C5FCA3E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A39D1D72-5614-4CCF-B7AD-923E4DC3F452} + SolutionGuid = {5E10D9D7-D5B5-4C0B-B7AC-AC10FF33DA3F} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index d052f71e..67db5ea5 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,30 @@ -

+

Logo


+## Contents + +- [About](#about) + + [Features](#features) + + [Benefits](#benefits) +- [Documentation](#documentation) + + [Fundamentals](#fundamentals) + + [Getting started](#getting-started) + + [API Reference](#api-reference) +- [Contributing](#contributing) + + [How to contribute](#how-to-contribute) + + [Types of contributions](#types-of-contributions) + + [Code of conduct](#code-of-conduct) + + [Get in touch](#get-in-touch) + + [Let's build together](#lets-build-together) +## About + Cloud Streams is a cloud-native tool that empowers users to capture and process [cloud events](https://cloudevents.io/) in real-time, enabling event-driven architectures that are both scalable and efficient. With Cloud Streams, you can customize how you stream, filter, partition, and mutate the [cloud events](https://cloudevents.io/) you consume using declarative rules, giving you full control over how your data is processed and consumed. As event-driven architectures have become more popular, so too have the challenges associated with building and managing them, especially when dealing with large volumes of data. This is where Cloud Streams comes in. This powerful and straightforward tool provides a simple and effective way to capture, process, and route cloud events in real-time, allowing you to create event-driven architectures that are both flexible and reliable. -## Features +### Features - **Streaming**: Cloud Streams allows users to stream cloud events in real-time, ensuring that all relevant data is captured and processed as soon as it becomes available. This feature is especially useful for applications that require real-time data processing, such as financial trading or IoT applications. - **Filtering**: Cloud Streams provides powerful filtering capabilities, allowing users to filter cloud events based on declarative rules. This ensures that only relevant data is captured and processed, reducing processing time and improving overall system performance. @@ -17,7 +34,7 @@ As event-driven architectures have become more popular, so too have the challeng - **Fault Tolerance**: Cloud Streams provides fault tolerance by implementing atomic, configurable retry policies that enable brokers to resend missed events to a consumer that was previously unavailable or that responded with a non-success status code. This ensures that all missed events are eventually received by the consumer in the order they were originally posted. The retry policy enables potential errors to be fixed at the consumer level, without the risk of losing data. - **Playback**: Cloud Streams provides playback capabilities, allowing users to play back previously captured cloud events. This can be useful for debugging, testing, or catastrophic recovery purposes. With this feature, users can recover from system failures or data corruption by replaying previously captured events. -## Benefits +### Benefits - **Scalability**: Cloud Streams is designed to be highly scalable, allowing users to process large volumes of data with ease. This makes it an ideal solution for applications with rapidly changing data requirements. - **Flexibility**: Cloud Streams provides flexible filtering and routing capabilities, allowing users to customize the way their data is processed and consumed. This ensures that users can adapt to changing data requirements quickly and easily. @@ -26,6 +43,138 @@ As event-driven architectures have become more popular, so too have the challeng - **Historical Data Access**: Cloud Streams provides historical data access capabilities, allowing users to access previously captured cloud events. This can be useful for analyzing historical data, as well as for replaying events for debugging, testing, or catastrophic recovery purposes. With this feature, users can recover from system failures or data corruption by accessing previously captured events. - **Easy to Integrate**: Cloud Streams is extremely easy to integrate in existing solutions. As a matter of fact, producers can publish events to Cloud Streams simply by POSTing Cloud Events, using the [structured content mode](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md#32-structured-content-mode), to a specific gateway. Consumers, on the other hand, only need to have an endpoint that can accept cloud events sent over HTTP using the [structured content mode](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md#32-structured-content-mode). That's it. All the configuration, and control, is offloaded to Cloud Streams resources such as subscriptions. -## Solution Overview +### Want to know more? + +Then take a look at our [wiki](https://github.com/neuroglia-io/cloud-streams/wiki)📚, or start a new discussion/issue! + +
+ +*Solution overview:* Solution overview + +## Documentation + +### Fundamentals + +CloudStreams is a platform that implements [resource-oriented architecture (ROA)](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#resources) to allow users to customize the behavior and configuration of event processing. Similar to Kubernetes Custom Resources, CloudStreams resources enable users to define specific configurations without modifying the underlying system. These resources serve as templates for various aspects of event handling within the CloudStreams ecosystem, offering flexibility to tailor event processing to specific architectural needs. + +[Cloud Events](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#cloud-events) are a standardized format used by CloudStreams to represent events in cloud environments. They encapsulate essential information about an event, such as its type, source, and data, fostering interoperability and seamless integration across different cloud services. + +[Streams](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#streams) in CloudStreams are ordered, append-only collections of events, providing comprehensive auditing and enabling efficient event handling. Events are automatically [partitioned](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#partitions) based on attributes like source, type, and subject, allowing for scalable and responsive architectures. + +CloudStreams [subscriptions](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#subscriptions) empower users to configure event dispatching based on partitions, filters, and mutations. This fine-grained control optimizes event-driven applications by ensuring that relevant events are efficiently processed and delivered to consumers based on specific criteria. + +*Please view the [wiki](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals)📚 for additional information.* + +### Getting started + +The easiest way to start the solution is using ``docker-compose``. + +To to this, clone the repository: + +```shell +git clone https://github.com/neuroglia-io/cloud-streams.git +``` + +Navigate to the resulting `/cloud-streams/` directory, then run the following command: + +```shell +docker-compose -f "deployments/docker-compose/docker-compose.yml" up +``` + +Now that everything is up and running, you can start publishing cloud events using POST requests on the gateway's ingestion endpoint, at `http://localhost:8080/api/events/pub`: + +```shell +curl -X 'POST' \ + 'http://localhost:8080/api/events/pub' \ + -H 'accept: */*' \ + -H 'Content-Type: application/cloudevents+json' \ + -d '{ + "id": "1234567890", + "specversion": "1.0", + "source": "https://foo.bar.com", + "type": "bar.foo", + "subject": "foobar", + "dataschema": "string", + "data": { + "foo": "bar" + } +}' +``` + +You can start consuming cloud events by creating a new subscription, which can be done via [Dashboard](http://localhost:8080/subscriptions), or by executing a simple POST request: + +```shell +curl -X 'POST' \ + 'http://localhost:8080/api/resources/v1/subscriptions?dryRun=false' \ + -H 'accept: text/plain' \ + -H 'Content-Type: text/json' \ + -d '{ + "metadata": { + "name": "my-subscription" + }, + "spec": { + "subscriber": { + "uri": "https://webhook.site/00000000-0000-0000-0000-000000000000" + }, + "stream":{ + "offset": 0 + } + } +}' +``` + +The preceeding sample creates a new subscription to all events (no partition has been defined and no filter has been set). Because we have defined the stream offset at '0', when will start receiving all events ever published, even the ones published before the creation of the subscription. + +Note that, by default, subscriptions will only process cloud events published **after** their creation. + +*For more information on subscriptions, please check the [wiki](https://github.com/neuroglia-io/cloud-streams/wiki/Fundamentals#subscriptions)📚.* + +
+ +The `Dashboard UI` is served by the `gateway` and is reachable at [http://localhost:8080](http://localhost:8080/). + +The `Swagger UI`, also served by the `gateway`, is reachable at [http://localhost:8080/api/doc](http://localhost:8080/api/doc). + +### API Reference + +*Please refer to the [wiki](https://github.com/neuroglia-io/cloud-streams/wiki/API-Reference)📚.* + +## Contributing + +👋 **Welcome Contributors!** + +We're thrilled that you're interested in contributing to our project on GitHub. Whether you're a seasoned developer or new to open source, there are many ways you can contribute and make a difference. + +### How to Contribute + +1. **Fork the Repository**: Start by forking our repository to your GitHub account. +2. **Clone the Repository**: Clone the forked repository to your local machine using `git clone`. +3. **Create a Branch**: Create a new branch for your contribution (`git checkout -b my-feature`). +4. **Make Changes**: Make your changes and additions to the codebase. +5. **Commit Changes**: Commit your changes with descriptive commit messages. +6. **Push Changes**: Push your changes to your forked repository (`git push origin my-feature`). +7. **Submit a Pull Request**: Open a pull request from your forked repository to our main repository. + +### Types of Contributions + +- **Bug Fixes**: Help us squash those pesky bugs. +- **Feature Additions**: Introduce new features or enhancements. +- **Documentation**: Improve our documentation to make it more comprehensive. +- **Code Optimization**: Help optimize existing code for better performance. +- **Issue Reporting**: Report bugs, suggest features, or ask questions by opening issues. + +### Code of Conduct + +Please review and adhere to our [Code of Conduct](code-of-conduct.md) while participating in this project. + +### Get in Touch + +If you have any questions or need assistance, feel free to reach out via GitHub issues or discussions. + +### Let's Build Together! + +Every contribution counts, no matter how big or small. We appreciate your time and effort in making our project better. Thank you for being part of our open source community! + +Happy Coding! 🚀 diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 00000000..462518aa --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,41 @@ +# Cloud Streams Code of Conduct + +Cloud Streams is dedicated to providing a welcoming and inclusive environment for all participants, regardless of age, gender identity and expression, sexual orientation, disability, physical appearance, race, ethnicity, religion (or lack thereof), or technology choices. We do not tolerate harassment of participants in any form. + +This Code of Conduct applies to all Cloud Streams spaces, including but not limited to GitHub repositories, issue trackers, Slack channels, email communications, social media, and events. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Reporting Violations + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [info@neuroglia.io](mailto:info@neuroglia.io). All complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html) + +For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) + +[homepage]: [https://www.contributor-covenant.org](https://www.contributor-covenant.org) diff --git a/deployments/docker-compose/docker-compose.dcproj b/deployments/docker-compose/docker-compose.dcproj index 87c2e947..e896ab7e 100644 --- a/deployments/docker-compose/docker-compose.dcproj +++ b/deployments/docker-compose/docker-compose.dcproj @@ -12,6 +12,5 @@ - \ No newline at end of file diff --git a/deployments/docker-compose/docker-compose.yml b/deployments/docker-compose/docker-compose.yml index ad228962..2227c0d7 100644 --- a/deployments/docker-compose/docker-compose.yml +++ b/deployments/docker-compose/docker-compose.yml @@ -6,7 +6,6 @@ services: image: eventstore/eventstore:latest environment: - EVENTSTORE_INSECURE=true - - EVENTSTORE_EXT_TCP_PORT=1113 - EVENTSTORE_HTTP_PORT=2113 - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true - EVENTSTORE_RUN_PROJECTIONS=all @@ -14,48 +13,38 @@ services: ports: - "1113:1113" - "2113:2113" - volumes: - - type: volume - source: eventstore-data - target: /var/lib/eventstore - - type: volume - source: eventstore-logs - target: /var/log/eventstore redis: image: redis:latest - api-server: - image: ${DOCKER_REGISTRY-}api-server + gateway: + image: ${DOCKER_REGISTRY-}gateway build: context: ../../ - dockerfile: ./src/core/CloudStreams.Core.Api.Server/Dockerfile + dockerfile: ./src/gateway/CloudStreams.Gateway.Api/Dockerfile environment: ASPNETCORE_ENVIRONMENT: Development + ASPNETCORE_HTTP_PORTS: 80 CONNECTIONSTRINGS__REDIS: ${REDIS_URI} CONNECTIONSTRINGS__EVENTSTORE: ${EVENTSTORE_URI} - volumes: - - ./plugins:/app/bin/Debug/net7.0/plugins + CLOUDSTREAMS_GATEWAY_NAME: gateway-1 + ports: + - 8080:80 depends_on: - redis - eventstore - gateway: - image: ${DOCKER_REGISTRY-}gateway + broker: + image: ${DOCKER_REGISTRY-}broker build: context: ../../ - dockerfile: ./src/gateway/CloudStreams.Gateway.Api.Server/Dockerfile + dockerfile: ./src/broker/CloudStreams.Broker.Api/Dockerfile environment: ASPNETCORE_ENVIRONMENT: Development + ASPNETCORE_HTTP_PORTS: 80 CONNECTIONSTRINGS__REDIS: ${REDIS_URI} CONNECTIONSTRINGS__EVENTSTORE: ${EVENTSTORE_URI} - CLOUDSTREAMS_GATEWAY_NAME: gateway-1 - volumes: - - ./plugins:/app/bin/Debug/net7.0/plugins + CLOUDSTREAMS_BROKER_NAME: broker-1 depends_on: - redis - - eventstore - -volumes: - eventstore-data: - eventstore-logs: \ No newline at end of file + - eventstore \ No newline at end of file diff --git a/deployments/docker-compose/plugins/event-sourcing.plugin.json b/deployments/docker-compose/plugins/event-sourcing.plugin.json deleted file mode 100644 index 5c603d69..00000000 --- a/deployments/docker-compose/plugins/event-sourcing.plugin.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "assemblyFilePath": "/app/bin/Debug/net7.0/plugins/event-sourcing/CloudStreams.Core.Infrastructure.EventSourcing.EventStore.dll", - "typeName": "CloudStreams.Core.Infrastructure.Services.ESEventStoreProvider" -} \ No newline at end of file diff --git a/deployments/docker-compose/plugins/resource-database.plugin.json b/deployments/docker-compose/plugins/resource-database.plugin.json deleted file mode 100644 index 2aebc8e5..00000000 --- a/deployments/docker-compose/plugins/resource-database.plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "assemblyFilePath": "Hylo.Providers.Redis.dll", - "typeName": "Hylo.Providers.FileSystem.Services.RedisDatabaseProvider", - "nugetPackage": "Hylo.Providers.Redis" -} \ No newline at end of file diff --git a/deployments/kubernetes/deployment.yaml b/deployments/kubernetes/deployment.yaml deleted file mode 100644 index 436646f6..00000000 --- a/deployments/kubernetes/deployment.yaml +++ /dev/null @@ -1,167 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: cloud-streams - labels: - name: cloud-streams - ---- - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api-server - namespace: cloud-streams - labels: - app: api-server -spec: - replicas: 1 - selector: - matchLabels: - app: api-server - template: - metadata: - labels: - app: api-server - spec: - containers: - - name: api-server - image: ghcr.io/neuroglia-io/cloud-streams-api-server:latest - ports: - - containerPort: 80 - env: - - name: CONNECTIONSTRINGS__REDIS - value: ${REDIS_URI} - - name: CONNECTIONSTRINGS__EVENTSTORE - value: ${EVENTSTORE_URI} - volumeMounts: - - name: cloud-streams-plugins - mountPath: /app/plugins/ - volumes: - - name: cloud-streams-plugins - hostPath: - path: /run/desktop/mnt/host/c/Users/User/.cloud-streams/plugins ---- - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: gateway - namespace: cloud-streams - labels: - app: gateway -spec: - replicas: 1 - selector: - matchLabels: - app: gateway - template: - metadata: - labels: - app: gateway - spec: - containers: - - name: gateway - image: ghcr.io/neuroglia-io/cloud-streams-gateway:latest - ports: - - containerPort: 80 - env: - - name: CONNECTIONSTRINGS__REDIS - value: ${REDIS_URI} - - name: CONNECTIONSTRINGS__EVENTSTORE - value: ${EVENTSTORE_URI} - - name: CLOUDSTREAMS_GATEWAY_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - volumeMounts: - - name: cloud-streams-plugins - mountPath: /app/plugins/ - volumes: - - name: cloud-streams-plugins - hostPath: - path: /run/desktop/mnt/host/c/Users/User/.cloud-streams/plugins ---- - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: broker - namespace: cloud-streams - labels: - app: broker -spec: - replicas: 1 - selector: - matchLabels: - app: broker - template: - metadata: - labels: - app: broker - spec: - containers: - - name: broker - image: ghcr.io/neuroglia-io/cloud-streams-broker:latest - ports: - - containerPort: 80 - env: - - name: CONNECTIONSTRINGS__REDIS - value: ${REDIS_URI} - - name: CONNECTIONSTRINGS__EVENTSTORE - value: ${EVENTSTORE_URI} - - name: CLOUDSTREAMS_BROKER_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - volumeMounts: - - name: cloud-streams-plugins - mountPath: /app/plugins/ - volumes: - - name: cloud-streams-plugins - hostPath: - path: /run/desktop/mnt/host/c/Users/User/.cloud-streams/plugins ---- - -apiVersion: v1 -kind: Service -metadata: - name: api-server -spec: - selector: - app: api-server - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 80 - ---- - -apiVersion: v1 -kind: Service -metadata: - name: gateway -spec: - selector: - app: gateway - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 80 - ---- - -apiVersion: v1 -kind: Service -metadata: - name: broker -spec: - selector: - app: broker - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 80 \ No newline at end of file diff --git a/launchSettings.json b/launchSettings.json deleted file mode 100644 index 45328c71..00000000 --- a/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "Docker Compose": { - "commandName": "DockerCompose", - "commandVersion": "1.0", - "serviceActions": { - "cloudstreams.core.api.server": "StartDebugging" - } - } - } -} \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj b/src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj deleted file mode 100644 index 153f9fe0..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - Apache-2.0 - Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - cloud-streams-broker - Linux - ..\..\.. - af3f83b7-cab9-412b-9a4b-21e1921c5b0c - - - - - \ - True - - - \ - True - - - - - - - - - - - - - - - - - diff --git a/src/broker/CloudStreams.Broker.Api.Server/Dockerfile b/src/broker/CloudStreams.Broker.Api.Server/Dockerfile deleted file mode 100644 index b460cb59..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 -RUN apt-get update -RUN apt-get install -y jq - -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj", "src/broker/CloudStreams.Broker.Api.Server/"] -COPY ["src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj", "src/broker/CloudStreams.Broker.Api/"] -COPY ["src/broker/CloudStreams.Broker.Application/CloudStreams.Broker.Application.csproj", "src/broker/CloudStreams.Broker.Application/"] -COPY ["src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj", "src/core/CloudStreams.Core.Application/"] -COPY ["src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj", "src/core/CloudStreams.Core.Infrastructure/"] -COPY ["src/core/CloudStreams.Core/CloudStreams.Core.csproj", "src/core/CloudStreams.Core/"] -RUN dotnet restore "src/broker/CloudStreams.Broker.Api.Server/CloudStreams.Broker.Api.Server.csproj" -COPY . . -WORKDIR "/src/src/broker/CloudStreams.Broker.Api.Server" -RUN dotnet build "CloudStreams.Broker.Api.Server.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "CloudStreams.Broker.Api.Server.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "CloudStreams.Broker.Api.Server.dll"] \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api.Server/Program.cs b/src/broker/CloudStreams.Broker.Api.Server/Program.cs deleted file mode 100644 index 64df6481..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/Program.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Broker.Api.Configuration; -using CloudStreams.Core.Application.Configuration; -using HealthChecks.UI.Client; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Swashbuckle.AspNetCore.SwaggerUI; - -var builder = WebApplication.CreateBuilder(args); -builder.UseCloudStreams(builder => -{ - builder.UseBrokerApi(); -}); - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) app.UseWebAssemblyDebugging(); -else app.UseExceptionHandler("/error"); - -app.UseResponseCompression(); -app.UseBlazorFrameworkFiles(); -app.UseStaticFiles(); -app.UseRouting(); -app.UseAuthorization(); -app.UseHealthChecks("/healthz", new HealthCheckOptions() -{ - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.UseSwagger(builder => -{ - builder.RouteTemplate = "api/{documentName}/doc/oas.{json|yaml}"; -}); -app.UseSwaggerUI(builder => -{ - builder.DocExpansion(DocExpansion.None); - builder.SwaggerEndpoint("/api/v1/doc/oas.json", "Cloud Streams Broker API v1"); - builder.RoutePrefix = "api/doc"; - builder.DisplayOperationId(); -}); - -app.MapControllers(); -app.MapFallbackToFile("index.html"); - -await app.RunAsync(); diff --git a/src/broker/CloudStreams.Broker.Api.Server/Properties/launchSettings.json b/src/broker/CloudStreams.Broker.Api.Server/Properties/launchSettings.json deleted file mode 100644 index f4c4c2bc..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/Properties/launchSettings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "profiles": { - "CloudStreams.Broker.Api.Server": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "CLOUDSTREAMS_BROKER_NAME": "broker-1", - "CONNECTIONSTRINGS__EVENTSTORE": "esdb://localhost:2113?tls=false", - "CONNECTIONSTRINGS__REDIS": "localhost" - }, - "applicationUrl": "https://localhost:52660;http://localhost:52661" - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", - "publishAllPorts": true, - "useSSL": true - } - } -} \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api.Server/appsettings.Development.json b/src/broker/CloudStreams.Broker.Api.Server/appsettings.Development.json deleted file mode 100644 index a6e86ace..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/src/broker/CloudStreams.Broker.Api.Server/appsettings.json b/src/broker/CloudStreams.Broker.Api.Server/appsettings.json deleted file mode 100644 index 10f68b8c..00000000 --- a/src/broker/CloudStreams.Broker.Api.Server/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj b/src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj index 9a654c81..8980af3c 100644 --- a/src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj +++ b/src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj @@ -1,40 +1,52 @@ - net7.0 - enable + net8.0 enable - True - Library + enable 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True Apache-2.0 Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git + true + cloud-streams/broker + Linux + ..\..\.. - - \ - True - - - \ - True - + + + + + + + - + + - + + PreserveNewest + true + PreserveNewest + + + PreserveNewest + true + PreserveNewest + diff --git a/src/broker/CloudStreams.Broker.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs b/src/broker/CloudStreams.Broker.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs deleted file mode 100644 index 778a4e9a..00000000 --- a/src/broker/CloudStreams.Broker.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Broker.Application.Configuration; -using CloudStreams.Broker.Application.Services; -using CloudStreams.Core.Infrastructure.Configuration; -using Hylo.Infrastructure; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Broker.Api.Configuration; - -/// -/// Defines extensions for s -/// -public static class ICloudStreamsApiBuilderExtensions -{ - - /// - /// Configures CloudStreams to use the Broker API - /// - /// The to configure - /// The configured - public static ICloudStreamsApplicationBuilder UseBrokerApi(this ICloudStreamsApplicationBuilder builder) - { - var options = new BrokerOptions(); - builder.Configuration.AddEnvironmentVariables(BrokerOptions.EnvironmentVariablePrefix); - builder.Configuration.Bind(options); - - builder.WithServiceName(options.Name); - builder.Services.Configure(builder.Configuration); - builder.Services.TryAddSingleton(); - builder.Services.AddSingleton>(provider => provider.GetRequiredService()); - builder.Services.AddHostedService(provider => provider.GetRequiredService()); - - return builder; - } - -} diff --git a/src/broker/CloudStreams.Broker.Api/Dockerfile b/src/broker/CloudStreams.Broker.Api/Dockerfile new file mode 100644 index 00000000..e492b4d4 --- /dev/null +++ b/src/broker/CloudStreams.Broker.Api/Dockerfile @@ -0,0 +1,25 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 80 +USER root +RUN apt-get update +RUN apt-get install -y jq + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj", "src/broker/CloudStreams.Broker.Api/"] +RUN dotnet restore "./src/broker/CloudStreams.Broker.Api/CloudStreams.Broker.Api.csproj" +COPY . . +WORKDIR "/src/src/broker/CloudStreams.Broker.Api" +RUN dotnet build "./CloudStreams.Broker.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./CloudStreams.Broker.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "CloudStreams.Broker.Api.dll"] \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api/Program.cs b/src/broker/CloudStreams.Broker.Api/Program.cs new file mode 100644 index 00000000..083ae4d8 --- /dev/null +++ b/src/broker/CloudStreams.Broker.Api/Program.cs @@ -0,0 +1,36 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using CloudStreams.Broker.Application.Configuration; +using CloudStreams.Broker.Application.Services; +using CloudStreams.Core; +using CloudStreams.Core.Api; +using CloudStreams.Core.Resources; +using Neuroglia.Data.Infrastructure.ResourceOriented.Services; + +CloudStreamsDefaults.Telemetry.ActivitySource = new("Cloud Streams Broker"); + +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddEnvironmentVariables(BrokerOptions.EnvironmentVariablePrefix); +builder.UseCloudStreams(builder => { }); + +builder.Services.Configure(builder.Configuration); +builder.Services.AddSingleton(); +builder.Services.AddSingleton>(provider => provider.GetRequiredService()); +builder.Services.AddHostedService(provider => provider.GetRequiredService()); + +using var app = builder.Build(); + +app.UseResponseCompression(); + +await app.RunAsync(); \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api/Properties/launchSettings.json b/src/broker/CloudStreams.Broker.Api/Properties/launchSettings.json index 5f50bbed..d03e269e 100644 --- a/src/broker/CloudStreams.Broker.Api/Properties/launchSettings.json +++ b/src/broker/CloudStreams.Broker.Api/Properties/launchSettings.json @@ -1,19 +1,39 @@ { "profiles": { - "CloudStreams.Broker.Api": { + "http": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "CLOUDSTREAMS_BROKER_NAME": "broker-1" }, - "applicationUrl": "https://localhost:52552;http://localhost:52553" + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5225" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } }, - "Docker": { + "Container (Dockerfile)": { "commandName": "Docker", "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", - "publishAllPorts": true, - "useSSL": true + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:19215", + "sslPort": 0 } } } \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api/appsettings.Development.json b/src/broker/CloudStreams.Broker.Api/appsettings.Development.json new file mode 100644 index 00000000..6b06496b --- /dev/null +++ b/src/broker/CloudStreams.Broker.Api/appsettings.Development.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "eventstore": "esdb://localhost:2113?tls=false&tlsVerifyCert=false", + "redis": "localhost:6379" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/broker/CloudStreams.Broker.Api/appsettings.json b/src/broker/CloudStreams.Broker.Api/appsettings.json new file mode 100644 index 00000000..7b9ce52f --- /dev/null +++ b/src/broker/CloudStreams.Broker.Api/appsettings.json @@ -0,0 +1,44 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Plugins": { + "Sources": [ + { + "name": "event-store", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.EventSourcing.EventStore" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IEventStore, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + }, + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IProjectionManager, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + } + ] + } + }, + { + "name": "resource-database", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.ResourceOriented.Redis" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.ResourceOriented.Services.IDatabase, Neuroglia.Data.Infrastructure.ResourceOriented.Abstractions" + } + ] + } + } + ] + }, + "AllowedHosts": "*" +} diff --git a/src/broker/CloudStreams.Broker.Application/CloudStreams.Broker.Application.csproj b/src/broker/CloudStreams.Broker.Application/CloudStreams.Broker.Application.csproj index fceb5e83..4cfea77a 100644 --- a/src/broker/CloudStreams.Broker.Application/CloudStreams.Broker.Application.csproj +++ b/src/broker/CloudStreams.Broker.Application/CloudStreams.Broker.Application.csproj @@ -1,43 +1,28 @@ - + - net7.0 + net8.0 enable enable - True - Library 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True Apache-2.0 Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git + true - - - - - - - - - \ - True - - - \ - True - - - - - + + + + diff --git a/src/broker/CloudStreams.Broker.Application/Configuration/BrokerOptions.cs b/src/broker/CloudStreams.Broker.Application/Configuration/BrokerOptions.cs index 7fb916f1..616e8acc 100644 --- a/src/broker/CloudStreams.Broker.Application/Configuration/BrokerOptions.cs +++ b/src/broker/CloudStreams.Broker.Application/Configuration/BrokerOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ namespace CloudStreams.Broker.Application.Configuration; /// -/// Represents the options used to configure a Cloud Streams cloud event gateway +/// Represents the options used to configure a Cloud Streams cloud event broker /// public class BrokerOptions { @@ -25,12 +25,12 @@ public class BrokerOptions public const string EnvironmentVariablePrefix = "CLOUDSTREAMS_BROKER_"; /// - /// Gets/sets the gateway's name + /// Gets/sets the broker's name /// public virtual string Name { get; set; } = null!; /// - /// Gets/sets the gateway's namespace + /// Gets/sets the broker's namespace /// public virtual string? Namespace { get; set; } = null!; diff --git a/src/broker/CloudStreams.Broker.Application/Properties/launchSettings.json b/src/broker/CloudStreams.Broker.Application/Properties/launchSettings.json deleted file mode 100644 index 43f002b9..00000000 --- a/src/broker/CloudStreams.Broker.Application/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "CloudStreamsBroker.Application": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:55562;http://localhost:55563" - } - } -} \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Application/Services/SubscriptionHandler.cs b/src/broker/CloudStreams.Broker.Application/Services/SubscriptionHandler.cs index 9ce200b1..5380eda6 100644 --- a/src/broker/CloudStreams.Broker.Application/Services/SubscriptionHandler.cs +++ b/src/broker/CloudStreams.Broker.Application/Services/SubscriptionHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,57 +11,50 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure; -using CloudStreams.Core.Infrastructure.Services; using FluentValidation; using Grpc.Core; -using Hylo; -using Hylo.Infrastructure.Services; -using Json.Patch; using Polly; using Polly.CircuitBreaker; -using System.Net; -using System.Net.Mime; -using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Text.RegularExpressions; namespace CloudStreams.Broker.Application.Services; /// -/// Represents a service used to handle a +/// Represents a service used to handle a /// public class SubscriptionHandler : IAsyncDisposable { - private IDisposable? _Subscription; - private bool _Disposed; + IDisposable? _Subscription; + bool _Disposed; /// /// Initializes a new /// /// The service used to create s /// The service used to manage the application's lifetime - /// The service used to store s + /// The service used to serialize/deserialize data to/from JSON + /// The service used to store s /// The service used to manage s - /// The service used to control resources - /// The service used to monitor the current - /// The service used to evaluate runtime expressions + /// The service used to control resources + /// The service used to monitor the current + /// The service used to evaluate runtime expressions /// An containing registered s /// The service used to perform HTTP requests - /// The to dispatch s to - public SubscriptionHandler(ILoggerFactory loggerFactory, IHostApplicationLifetime hostApplicationLifetime, IEventStoreProvider eventStoreProvider, IRepository resourceRepository, IResourceController subscriptionController, - IResourceMonitor broker, IExpressionEvaluatorProvider expressionEvaluatorProvider, IEnumerable> cloudEventValidators, HttpClient httpClient, Subscription subscription) + /// The to dispatch s to + public SubscriptionHandler(ILoggerFactory loggerFactory, IHostApplicationLifetime hostApplicationLifetime, IJsonSerializer serializer, ICloudEventStore cloudEventStore, IRepository resourceRepository, IResourceController subscriptionController, + IResourceMonitor broker, IExpressionEvaluator expressionEvaluator, IEnumerable> cloudEventValidators, HttpClient httpClient, Subscription subscription) { this.Logger = loggerFactory.CreateLogger(this.GetType()); this.HostApplicationLifetime = hostApplicationLifetime; - this.EventStoreProvider = eventStoreProvider; + this.Serializer = serializer; + this.EventStore = cloudEventStore; this.ResourceRepository = resourceRepository; this.SubscriptionController = subscriptionController; this.Broker = broker; - this.ExpressionEvaluatorProvider = expressionEvaluatorProvider; + this.ExpressionEvaluator = expressionEvaluator; this.CloudEventValidators = cloudEventValidators; this.HttpClient = httpClient; this.Subscription = subscription; @@ -80,9 +73,14 @@ public SubscriptionHandler(ILoggerFactory loggerFactory, IHostApplicationLifetim protected IHostApplicationLifetime HostApplicationLifetime { get; } /// - /// Gets the service used to store s + /// Gets the service used to serialize/deserialize data to/from JSON /// - protected IEventStoreProvider EventStoreProvider { get; } + protected IJsonSerializer Serializer { get; } + + /// + /// Gets the service used to stream s + /// + protected ICloudEventStore EventStore { get; } /// /// Gets the service used to manage s @@ -90,19 +88,19 @@ public SubscriptionHandler(ILoggerFactory loggerFactory, IHostApplicationLifetim protected IRepository ResourceRepository { get; } /// - /// Gets the service used to control resources + /// Gets the service used to control resources /// protected IResourceController SubscriptionController { get; } /// - /// Gets the service used to monitor the current + /// Gets the service used to monitor the current /// - protected IResourceMonitor Broker { get; } + protected IResourceMonitor Broker { get; } /// /// Gets the service used to evaluate runtime expressions /// - protected IExpressionEvaluatorProvider ExpressionEvaluatorProvider { get; } + protected IExpressionEvaluator ExpressionEvaluator { get; } /// /// Gets an containing registered s @@ -115,7 +113,7 @@ public SubscriptionHandler(ILoggerFactory loggerFactory, IHostApplicationLifetim protected HttpClient HttpClient { get; } /// - /// Gets the to dispatch s to + /// Gets the to dispatch s to /// protected Subscription Subscription { get; private set; } @@ -202,7 +200,7 @@ public virtual async Task InitializeAsync(CancellationToken cancellationToken) } /// - /// Initializes the 's + /// Initializes the 's /// /// A new awaitable protected virtual async Task InitializeCloudEventStreamAsync() @@ -223,33 +221,58 @@ protected virtual async Task InitializeCloudEventStreamAsync() { try { - this.StreamOffset = (await this.EventStoreProvider.GetEventStore().GetStreamMetadataAsync(this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false)).Length; + this.StreamOffset = (await this.EventStore.GetStreamMetadataAsync(this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false)).Length; } - catch (HyloException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) + catch (ProblemDetailsException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { this.StreamOffset = 0; } if (offset >= 0 && (ulong)offset == this.StreamOffset) offset = -1; - this.CloudEventStream = await this.EventStoreProvider.GetEventStore().SubscribeAsync(offset, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false); + while (true) + { + try + { + this.CloudEventStream = await this.EventStore.ObserveAsync(offset, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false); + break; + } + catch (StreamNotFoundException) + { + var delay = 5000; + this.Logger.LogDebug("Failed to observe the cloud event stream because the first cloud event is yet to be published. Retrying in {delay} milliseconds...", delay); + await Task.Delay(delay).ConfigureAwait(false); + } + } } else { try { - this.StreamOffset = (await this.EventStoreProvider.GetEventStore().GetPartitionMetadataAsync(this.Subscription.Spec.Partition, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false)).Length; + this.StreamOffset = (await this.EventStore.GetPartitionMetadataAsync(this.Subscription.Spec.Partition, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false)).Length; } - catch (HyloException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) + catch (ProblemDetailsException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { this.StreamOffset = 0; } if (offset >= 0 && (ulong)offset == this.StreamOffset) offset = -1; - this.CloudEventStream = await this.EventStoreProvider.GetEventStore().SubscribeToPartitionAsync(this.Subscription.Spec.Partition, offset, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false); - + while (true) + { + try + { + this.CloudEventStream = await this.EventStore.ObservePartitionAsync(this.Subscription.Spec.Partition, offset, this.StreamInitializationCancellationTokenSource.Token).ConfigureAwait(false); + break; + } + catch (StreamNotFoundException) + { + var delay = 5000; + this.Logger.LogDebug("Failed to observe the cloud event stream because the first cloud event is yet to be published. Retrying in {delay} milliseconds...", delay); + await Task.Delay(delay).ConfigureAwait(false); + } + } } - this._Subscription = this.CloudEventStream.Where(this.Filters).SubscribeAsync(this.OnCloudEventAsync, onErrorAsync: this.OnSubscriptionErrorAsync, null); + this._Subscription = this.CloudEventStream.ToAsyncEnumerable().WhereAwait(this.FiltersAsync).ToObservable().SubscribeAsync(this.OnCloudEventAsync, onErrorAsync: this.OnSubscriptionErrorAsync, null); if (offset != StreamPosition.EndOfStream && (ulong)offset < this.StreamOffset) _ = this.CatchUpAsync().ConfigureAwait(false); } - catch (Exception ex) when (ex is ObjectDisposedException || ex is TaskCanceledException || ex is OperationCanceledException || (ex is RpcException rpcex && rpcex.StatusCode == StatusCode.Cancelled)) + catch (Exception ex) when (ex is ObjectDisposedException || ex is TaskCanceledException || ex is OperationCanceledException || (ex is RpcException rpcException && rpcException.StatusCode == StatusCode.Cancelled)) { if (this.StreamInitializationTaskCompletionSource?.Task.IsCompleted == false && this.StreamInitializationCancellationTokenSource != null && !this.StreamInitializationCancellationTokenSource.IsCancellationRequested) this.StreamInitializationCancellationTokenSource.Cancel(); } @@ -265,54 +288,54 @@ protected virtual async Task InitializeCloudEventStreamAsync() } /// - /// Determines whether or not the filters the specified + /// Determines whether or not the filters the specified /// /// The to filter - /// A boolean indicating whether or not the filters the specified - protected virtual bool Filters(CloudEventRecord e) + /// A boolean indicating whether or not the filters the specified + protected virtual ValueTask FiltersAsync(CloudEventRecord e) { - if(e == null) throw new ArgumentNullException(nameof(e)); - if (this.Subscription.Spec.Filter == null) return true; + ArgumentNullException.ThrowIfNull(e); + if (this.Subscription.Spec.Filter == null) return ValueTask.FromResult(true); return this.Subscription.Spec.Filter.Type switch { - CloudEventFilterType.Attributes => this.Filters(e, this.Subscription.Spec.Filter.Attributes!), - CloudEventFilterType.Expression => this.Filters(e, this.Subscription.Spec.Filter.Expression!), + CloudEventFilterType.Attributes => this.FiltersAsync(e, this.Subscription.Spec.Filter.Attributes!), + CloudEventFilterType.Expression => this.FiltersAsync(e, this.Subscription.Spec.Filter.Expression!), _ => throw new NotSupportedException($"The specified {nameof(CloudEventFilterType)} '{EnumHelper.Stringify(this.Subscription.Spec.Filter.Type)}' is not supported") }; } /// - /// Determines whether or not the filters the specified + /// Determines whether or not the filters the specified /// /// The to filter /// An containing the key/value mappings of the attributes to filter the specified by - /// A boolean indicating whether or not the filters the specified - protected virtual bool Filters(CloudEventRecord e, IDictionary attributeFilters) + /// A boolean indicating whether or not the filters the specified + protected virtual async ValueTask FiltersAsync(CloudEventRecord e, IDictionary attributeFilters) { - if (e == null) throw new ArgumentNullException(nameof(e)); - if (attributeFilters == null) throw new ArgumentNullException(nameof(attributeFilters)); + ArgumentNullException.ThrowIfNull(e); + ArgumentNullException.ThrowIfNull(attributeFilters); var attributes = e.Metadata.ContextAttributes.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString()); foreach (var attributeFilter in attributeFilters) { if (!attributes.TryGetValue(attributeFilter.Key, out var attributeValue) || string.IsNullOrWhiteSpace(attributeValue)) return false; if (string.IsNullOrWhiteSpace(attributeFilter.Value)) continue; - if (attributeValue.IsRuntimeExpression() && !this.ExpressionEvaluatorProvider.GetExpressionEvaluator().EvaluateCondition(attributeFilter.Value, attributeValue)) return false; + if (attributeValue.IsRuntimeExpression() && !await this.ExpressionEvaluator.EvaluateConditionAsync(attributeFilter.Value, attributeValue, cancellationToken: this.CancellationToken).ConfigureAwait(false)) return false; else if (!Regex.IsMatch(attributeValue, attributeFilter.Value)) return false; } return true; } /// - /// Determines whether or not the filters the specified + /// Determines whether or not the filters the specified /// /// The to filter /// A runtime expression used to determine whether or not the specified should be dispatched to subscribers - /// A boolean indicating whether or not the filters the specified - protected virtual bool Filters(CloudEventRecord e, string expression) + /// A boolean indicating whether or not the filters the specified + protected virtual async ValueTask FiltersAsync(CloudEventRecord e, string expression) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - return this.ExpressionEvaluatorProvider.GetExpressionEvaluator().EvaluateCondition(expression, e); + return await this.ExpressionEvaluator.EvaluateConditionAsync(expression, e, cancellationToken: this.CancellationToken).ConfigureAwait(false); } /// @@ -322,7 +345,7 @@ protected virtual bool Filters(CloudEventRecord e, string expression) /// The mutated protected virtual async Task MutateAsync(CloudEvent e) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); if(this.Subscription.Spec.Mutation == null) return e.Clone()!; var mutated = this.Subscription.Spec.Mutation.Type switch { @@ -340,12 +363,12 @@ protected virtual async Task MutateAsync(CloudEvent e) /// The to mutate /// An object used to configure the mutation to perform /// The mutated - protected virtual Task MutateAsync(CloudEvent e, object mutation) + protected virtual async Task MutateAsync(CloudEvent e, object mutation) { - if (e == null) throw new ArgumentNullException(nameof(e)); - if (mutation == null) throw new ArgumentNullException(nameof(mutation)); - if (mutation is string expression) return Task.FromResult(this.ExpressionEvaluatorProvider.GetExpressionEvaluator().Evaluate(expression, e)); - else return Task.FromResult(this.ExpressionEvaluatorProvider.GetExpressionEvaluator().Mutate(mutation, e)); + ArgumentNullException.ThrowIfNull(e); + ArgumentNullException.ThrowIfNull(mutation); + if (mutation is string expression) return await this.ExpressionEvaluator.EvaluateAsync(expression, e); + else return await this.ExpressionEvaluator.EvaluateAsync(mutation, e); } /// @@ -356,8 +379,8 @@ protected virtual async Task MutateAsync(CloudEvent e) /// The mutated protected virtual async Task MutateAsync(CloudEvent e, Webhook webhook) { - if (e == null) throw new ArgumentNullException(nameof(e)); - if (webhook == null) throw new ArgumentNullException(nameof(webhook)); + ArgumentNullException.ThrowIfNull(e); + ArgumentNullException.ThrowIfNull(webhook); using var requestContent = e.ToHttpContent(); using var request = new HttpRequestMessage(HttpMethod.Post, webhook.ServiceUri) { Content = requestContent }; request.Headers.Accept.Add(new(MediaTypeNames.Application.Json)); @@ -365,7 +388,7 @@ protected virtual async Task MutateAsync(CloudEvent e) var responseContent = await response.Content.ReadAsStringAsync(this.CancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); if (response.Content.Headers.ContentType?.MediaType == MediaTypeNames.Application.Json) throw new Exception($"Unexpected HTTP response's content type: {response.Content.Headers.ContentType?.MediaType}"); //todo: better feedback - return Serializer.Json.Deserialize(responseContent); + return this.Serializer.Deserialize(responseContent); } /// @@ -375,10 +398,10 @@ protected virtual async Task MutateAsync(CloudEvent e) /// A new awaitable protected virtual async Task ValidateAsync(CloudEvent e) { - if(e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); var validationTasks = this.CloudEventValidators.Select(v => v.ValidateAsync(e, this.CancellationToken)); await Task.WhenAll(validationTasks).ConfigureAwait(false); - if (validationTasks.Any(t => !t.Result.IsValid)) throw new FormatException("Failed to validate the specified cloud event"); //todo: better feeback + if (validationTasks.Any(t => !t.Result.IsValid)) throw new FormatException("Failed to validate the specified cloud event"); //todo: better feedback } /// @@ -390,9 +413,9 @@ protected virtual async Task ValidateAsync(CloudEvent e) /// A new awaitable protected virtual async Task DispatchAsync(CloudEventRecord e, bool retryOnError, bool catchUpWhenAvailable) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); var cloudEvent = e.ToCloudEvent(this.Broker.Resource.Spec.Dispatch?.Sequencing); - if (!this.Filters(e)) return; + if (!await this.FiltersAsync(e).ConfigureAwait(false)) return; cloudEvent = await this.MutateAsync(cloudEvent).ConfigureAwait(false); await this.DispatchAsync(cloudEvent, e.Sequence, retryOnError, catchUpWhenAvailable).ConfigureAwait(false); } @@ -436,16 +459,16 @@ protected virtual async Task RetryDispatchAsync(CloudEvent e, ulong offset, bool this.SubscriberAvailable = false; this.SubscriptionOutOfSync = true; var policyConfiguration = this.Subscription.Spec.Subscriber.RetryPolicy ?? this.DefaultRetryPolicy; - var exceptionPreficate = (HttpRequestException ex) => policyConfiguration.StatusCodes == null || !policyConfiguration.StatusCodes.Any() || (ex.StatusCode.HasValue && ex.StatusCode.HasValue && policyConfiguration.StatusCodes.Contains((int)ex.StatusCode.Value)); + var exceptionPredicate = (HttpRequestException ex) => policyConfiguration.StatusCodes == null || policyConfiguration.StatusCodes.Count == 0 || (ex.StatusCode.HasValue && ex.StatusCode.HasValue && policyConfiguration.StatusCodes.Contains((int)ex.StatusCode.Value)); - AsyncCircuitBreakerPolicy? circuitBreakerPolicy = policyConfiguration.CircuitBreaker == null ? null : Policy.Handle(exceptionPreficate) - .CircuitBreakerAsync(policyConfiguration.CircuitBreaker.BreakAfter, policyConfiguration.CircuitBreaker.BreakDuration); + AsyncCircuitBreakerPolicy? circuitBreakerPolicy = policyConfiguration.CircuitBreaker == null ? null : Policy.Handle(exceptionPredicate) + .CircuitBreakerAsync(policyConfiguration.CircuitBreaker.BreakAfter, policyConfiguration.CircuitBreaker.BreakDuration.ToTimeSpan()); AsyncPolicy retryPolicy = policyConfiguration.MaxAttempts.HasValue ? - Policy.Handle(exceptionPreficate) - .WaitAndRetryAsync(policyConfiguration.MaxAttempts.Value, policyConfiguration.BackoffDuration.ForAttempt) - : Policy.Handle(exceptionPreficate) - .WaitAndRetryForeverAsync(policyConfiguration.BackoffDuration.ForAttempt); + Policy.Handle(exceptionPredicate) + .WaitAndRetryAsync(policyConfiguration.MaxAttempts.Value, attempt => policyConfiguration.BackoffDuration == null ? TimeSpan.FromSeconds(3) : policyConfiguration.BackoffDuration.ForAttempt(attempt)) + : Policy.Handle(exceptionPredicate) + .WaitAndRetryForeverAsync(attempt => policyConfiguration.BackoffDuration == null ? TimeSpan.FromSeconds(3) : policyConfiguration.BackoffDuration.ForAttempt(attempt)); retryPolicy = circuitBreakerPolicy == null ? retryPolicy : retryPolicy.WrapAsync(circuitBreakerPolicy); await retryPolicy.ExecuteAsync(async _ => await this.DispatchAsync(e, offset, false, catchUpWhenAvailable), this.CancellationToken).ConfigureAwait(false); @@ -470,15 +493,14 @@ protected virtual async Task CatchUpAsync() this.SubscriptionOutOfSync = true; this.StreamInitializationTaskCompletionSource ??= new(); var currentOffset = this.Subscription.GetOffset(); - var eventStore = this.EventStoreProvider.GetEventStore(); if (currentOffset == StreamPosition.EndOfStream) currentOffset = this.Subscription.Spec.Partition == null ? - (long)(await eventStore.ReadOneAsync(StreamReadDirection.Backwards, StreamPosition.EndOfStream, this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false))!.Sequence - : (long)(await eventStore.ReadPartitionAsync(this.Subscription.Spec.Partition, StreamReadDirection.Backwards, StreamPosition.EndOfStream, 1, this.StreamInitializationCancellationTokenSource!.Token).SingleAsync(this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false))!.Sequence; + (long)(await this.EventStore.ReadOneAsync(StreamReadDirection.Backwards, StreamPosition.EndOfStream, this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false))!.Sequence + : (long)(await this.EventStore.ReadPartitionAsync(this.Subscription.Spec.Partition, StreamReadDirection.Backwards, StreamPosition.EndOfStream, 1, this.StreamInitializationCancellationTokenSource!.Token).SingleAsync(this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false))!.Sequence; do { var record = this.Subscription.Spec.Partition == null ? - await eventStore.ReadOneAsync(StreamReadDirection.Forwards, currentOffset!, this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false) - : await eventStore.ReadPartitionAsync(this.Subscription.Spec.Partition, StreamReadDirection.Forwards, currentOffset, 1, this.StreamInitializationCancellationTokenSource!.Token).SingleOrDefaultAsync(this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false); + await this.EventStore.ReadOneAsync(StreamReadDirection.Forwards, currentOffset!, this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false) + : await this.EventStore.ReadPartitionAsync(this.Subscription.Spec.Partition, StreamReadDirection.Forwards, currentOffset, 1, this.StreamInitializationCancellationTokenSource!.Token).SingleOrDefaultAsync(this.StreamInitializationCancellationTokenSource!.Token).ConfigureAwait(false); if (record == null) { await Task.Delay(50); @@ -490,11 +512,8 @@ await eventStore.ReadOneAsync(StreamReadDirection.Forwards, currentOffset!, this while (!this.StreamInitializationCancellationTokenSource.Token.IsCancellationRequested && (ulong)currentOffset <= this.StreamOffset); this.SubscriptionOutOfSync = false; } - catch (Exception ex) when (ex is ObjectDisposedException || ex is TaskCanceledException || ex is OperationCanceledException || (ex is RpcException rpcex && rpcex.StatusCode == StatusCode.Cancelled)) { } - finally - { - this.StreamInitializationTaskCompletionSource?.SetResult(); - } + catch (Exception ex) when (ex is ObjectDisposedException || ex is TaskCanceledException || ex is OperationCanceledException || (ex is RpcException rpcException && rpcException.StatusCode == StatusCode.Cancelled)) { } + finally { this.StreamInitializationTaskCompletionSource?.SetResult(); } } /// @@ -509,15 +528,15 @@ protected virtual async Task CommitOffsetAsync(ulong? offset) if (resource.Status.Stream == null) resource.Status.Stream = new(); resource.Status.Stream.AckedOffset = offset; resource.Status.ObservedGeneration = this.Subscription.Metadata.Generation; - var patch = JsonPatchHelper.CreateJsonPatchFromDiff(this.Subscription, resource); + var patch = JsonPatchUtility.CreateJsonPatchFromDiff(this.Subscription, resource); if (!patch.Operations.Any()) return; await this.ResourceRepository.PatchStatusAsync(new Patch(PatchType.JsonPatch, patch), resource.GetName(), resource.GetNamespace(), false, this.CancellationToken).ConfigureAwait(false); } /// - /// Sets the 's status phase + /// Sets the 's status phase /// - /// The 's status phase + /// The 's status phase /// A new awaitable protected virtual async Task SetStatusPhaseAsync(SubscriptionStatusPhase phase) { @@ -525,7 +544,7 @@ protected virtual async Task SetStatusPhaseAsync(SubscriptionStatusPhase phase) if (resource.Status == null) resource.Status = new(); else if (resource.Status.Phase == phase) return; resource.Status.Phase = phase; - var patch = JsonPatchHelper.CreateJsonPatchFromDiff(this.Subscription, resource); + var patch = JsonPatchUtility.CreateJsonPatchFromDiff(this.Subscription, resource); if (!patch.Operations.Any()) return; await this.ResourceRepository.PatchStatusAsync(new Patch(PatchType.JsonPatch, patch), resource.GetName(), resource.GetNamespace(), false, this.CancellationToken).ConfigureAwait(false); } @@ -552,7 +571,7 @@ protected virtual void OnDefaultRetryPolicyChanged(HttpClientRetryPolicy retryPo } /// - /// Handles changes to the 's offset + /// Handles changes to the 's offset /// /// The desired offset /// A new awaitable @@ -566,7 +585,7 @@ protected virtual async Task OnSubscriptionOffsetChangedAsync(long? offset) if (resource.Status == null) resource.Status = new() { ObservedGeneration = this.Subscription.Metadata.Generation }; if (resource.Status.Stream == null) resource.Status.Stream = new(); resource.Status.Stream.Fault = null; - var patch = JsonPatchHelper.CreateJsonPatchFromDiff(this.Subscription, resource); + var patch = JsonPatchUtility.CreateJsonPatchFromDiff(this.Subscription, resource); await this.ResourceRepository.PatchStatusAsync(new Patch(PatchType.JsonPatch, patch), resource.GetName(), resource.GetNamespace(), false, this.CancellationToken).ConfigureAwait(false); return; } @@ -624,13 +643,13 @@ protected virtual async Task OnCloudEventAsync(CloudEventRecord e) protected virtual async Task OnSubscriptionErrorAsync(Exception ex) { this.StreamInitializationCancellationTokenSource?.Cancel(); - this.Logger.LogError("An error occured while streaming cloud events for subscription '{subscription}': {ex}", this.Subscription, ex); + this.Logger.LogError("An error occurred while streaming cloud events for subscription '{subscription}': {ex}", this.Subscription, ex); var resource = this.Subscription.Clone()!; if (resource.Spec.Stream == null) return; if (resource.Status == null) resource.Status = new() { ObservedGeneration = this.Subscription.Metadata.Generation }; if (resource.Status.Stream == null) resource.Status.Stream = new(); resource.Status.Stream.Fault = ex.ToProblemDetails(); - var patch = JsonPatchHelper.CreateJsonPatchFromDiff(this.Subscription, resource); + var patch = JsonPatchUtility.CreateJsonPatchFromDiff(this.Subscription, resource); await this.ResourceRepository.PatchStatusAsync(new Patch(PatchType.JsonPatch, patch), resource.GetName(), resource.GetNamespace(), false, this.CancellationToken).ConfigureAwait(false); } diff --git a/src/broker/CloudStreams.Broker.Application/Services/SubscriptionManager.cs b/src/broker/CloudStreams.Broker.Application/Services/SubscriptionManager.cs index 1dce9c45..604e02b3 100644 --- a/src/broker/CloudStreams.Broker.Application/Services/SubscriptionManager.cs +++ b/src/broker/CloudStreams.Broker.Application/Services/SubscriptionManager.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,47 +12,39 @@ // limitations under the License. using CloudStreams.Broker.Application.Configuration; -using Hylo; -using Hylo.Infrastructure.Configuration; -using Hylo.Infrastructure.Services; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Neuroglia.Data.Infrastructure.ResourceOriented.Configuration; using System.Collections.Concurrent; -using System.Net; -using System.Reactive.Linq; namespace CloudStreams.Broker.Application.Services; /// /// Represents a service used to manage s /// -public class SubscriptionManager - : ResourceController +/// +/// Initializes a new +/// +/// The current +/// The service used to create s +/// The service used to access the current +/// The service used to manage s +/// The service used to access the current +public class SubscriptionManager(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository, IOptions brokerOptions) + : ResourceController(loggerFactory, controllerOptions, repository) { - /// - /// Initializes a new - /// - /// The current - /// The service used to create s - /// The service used to access the current - /// The service used to manage s - /// The service used to access the current - public SubscriptionManager(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository, IOptions brokerOptions) - : base(loggerFactory, controllerOptions, repository) - { - this.ServiceProvider = serviceProvider; - this.BrokerOptions = brokerOptions.Value; - } + readonly List _lockedKeys = []; /// /// Gets the current /// - protected IServiceProvider ServiceProvider { get; } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; /// - /// Gets the running 's options + /// Gets the running 's options /// - protected BrokerOptions BrokerOptions { get; } + protected BrokerOptions BrokerOptions { get; } = brokerOptions.Value; /// /// Gets a containing the key/value mappings of handled s @@ -60,9 +52,9 @@ public SubscriptionManager(IServiceProvider serviceProvider, ILoggerFactory logg protected ConcurrentDictionary Subscriptions { get; } = new(); /// - /// Gets the service used to monitor the current + /// Gets the service used to monitor the current /// - protected IResourceMonitor? Broker { get; private set; } + protected IResourceMonitor? Broker { get; private set; } /// /// Gets the 's @@ -72,17 +64,17 @@ public SubscriptionManager(IServiceProvider serviceProvider, ILoggerFactory logg /// public override async Task StartAsync(CancellationToken cancellationToken) { - Core.Data.Broker? broker = null; + Core.Resources.Broker? broker = null; try { - broker = await this.Repository.GetAsync(this.BrokerOptions.Name, this.BrokerOptions.Namespace, cancellationToken).ConfigureAwait(false); + broker = await this.Repository.GetAsync(this.BrokerOptions.Name, this.BrokerOptions.Namespace, cancellationToken).ConfigureAwait(false); } - catch (HyloException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { } + catch (ProblemDetailsException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { } finally { if (broker == null) { - broker = new Core.Data.Broker(new ResourceMetadata(this.BrokerOptions.Name, this.BrokerOptions.Namespace), new BrokerSpec() + broker = new Core.Resources.Broker(new ResourceMetadata(this.BrokerOptions.Name, this.BrokerOptions.Namespace), new BrokerSpec() { Dispatch = new() { @@ -91,7 +83,7 @@ public override async Task StartAsync(CancellationToken cancellationToken) }); broker = await this.Repository.AddAsync(broker, false, cancellationToken).ConfigureAwait(false); } - this.Broker = await this.Repository.MonitorAsync(this.BrokerOptions.Name, this.BrokerOptions.Namespace, false, cancellationToken).ConfigureAwait(false); + this.Broker = await this.Repository.MonitorAsync(this.BrokerOptions.Name, this.BrokerOptions.Namespace, false, cancellationToken).ConfigureAwait(false); } await base.StartAsync(cancellationToken).ConfigureAwait(false); foreach (var subscription in this.Resources.Values.ToList()) @@ -136,6 +128,7 @@ protected virtual async Task OnSubscriptionLabelChangedAsync(Subscription subscr { if (this.Broker == null) return; var key = this.GetSubscriptionHandlerCacheKey(subscription.GetName(), subscription.GetNamespace()); + if (this._lockedKeys.Contains(key)) return; if (this.Options.LabelSelectors == null || this.Options.LabelSelectors.All(s => s.Selects(subscription)) == true) { if (this.Subscriptions.TryGetValue(key, out _)) return; @@ -164,9 +157,11 @@ protected override async Task OnResourceDeletedAsync(Subscription subscription, protected virtual async Task OnSubscriptionCreatedAsync(Subscription subscription) { var key = this.GetSubscriptionHandlerCacheKey(subscription.GetName(), subscription.GetNamespace()); + this._lockedKeys.Add(key); var handler = ActivatorUtilities.CreateInstance(this.ServiceProvider, subscription, this.Broker!); await handler.InitializeAsync(this.CancellationToken).ConfigureAwait(false); this.Subscriptions.AddOrUpdate(key, handler, (_, _) => handler); + this._lockedKeys.Remove(key); } /// diff --git a/src/broker/CloudStreams.Broker.Application/Usings.cs b/src/broker/CloudStreams.Broker.Application/Usings.cs index fd1fa627..f06a5256 100644 --- a/src/broker/CloudStreams.Broker.Application/Usings.cs +++ b/src/broker/CloudStreams.Broker.Application/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,4 +12,21 @@ // limitations under the License. global using CloudStreams.Core; -global using CloudStreams.Core.Data; +global using CloudStreams.Core.Application; +global using CloudStreams.Core.Application.Services; +global using CloudStreams.Core.Resources; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Neuroglia; +global using Neuroglia.Data; +global using Neuroglia.Data.Expressions; +global using Neuroglia.Data.Expressions.Services; +global using Neuroglia.Data.Infrastructure.EventSourcing; +global using Neuroglia.Data.Infrastructure.ResourceOriented; +global using Neuroglia.Data.Infrastructure.ResourceOriented.Services; +global using Neuroglia.Eventing.CloudEvents; +global using Neuroglia.Reactive; +global using Neuroglia.Serialization; +global using System.Net; +global using System.Net.Mime; +global using System.Reactive.Linq; \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj b/src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj index ac902892..8b6f43ec 100644 --- a/src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj +++ b/src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj @@ -1,47 +1,28 @@ - + - net7.0 + net8.0 enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;api;client - Contains the client implementation of the CloudStreams Core API - logo.png - Apache-2.0 - True - True + true - - \ - True - - - \ - True - + + - - - - - - - - - diff --git a/src/core/CloudStreams.Core.Api.Client/Configuration/CoreApiClientOptions.cs b/src/core/CloudStreams.Core.Api.Client/Configuration/CoreApiClientOptions.cs index 083c90d8..b802542e 100644 --- a/src/core/CloudStreams.Core.Api.Client/Configuration/CoreApiClientOptions.cs +++ b/src/core/CloudStreams.Core.Api.Client/Configuration/CoreApiClientOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Api.Client/Extensions/ICloudStreamsResourceManagementApiClientExtensions.cs b/src/core/CloudStreams.Core.Api.Client/Extensions/ICloudStreamsResourceManagementApiClientExtensions.cs index 26099745..e88f1c3d 100644 --- a/src/core/CloudStreams.Core.Api.Client/Extensions/ICloudStreamsResourceManagementApiClientExtensions.cs +++ b/src/core/CloudStreams.Core.Api.Client/Extensions/ICloudStreamsResourceManagementApiClientExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Api.Client/Extensions/IServiceCollectionExtensions.cs b/src/core/CloudStreams.Core.Api.Client/Extensions/IServiceCollectionExtensions.cs index 0847c6d5..1e7d9f5e 100644 --- a/src/core/CloudStreams.Core.Api.Client/Extensions/IServiceCollectionExtensions.cs +++ b/src/core/CloudStreams.Core.Api.Client/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public static IServiceCollection AddCloudStreamsCoreApiClient(this IServiceColle { var options = provider.GetRequiredService>().Value; var connection = new HubConnectionBuilder() - .WithUrl($"{options.BaseAddress}api/resource-management/v1/ws/watch") + .WithUrl($"{options.BaseAddress}api/ws/resources/watch") .WithAutomaticReconnect() .Build(); return new ResourceWatchEventHubClient(connection); diff --git a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.CloudEvents.cs b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.CloudEvents.cs index 8ac73fc0..aadbfef0 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.CloudEvents.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.CloudEvents.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia; +using Neuroglia.Eventing.CloudEvents; +using Neuroglia.Serialization; namespace CloudStreams.Core.Api.Client.Services; @@ -36,7 +38,7 @@ public partial class CloudStreamsCoreApiClient var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, $"{CloudEventPartitionsApiPath}{type}"), cancellationToken).ConfigureAwait(false); var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken); + return this.Serializer.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken); } /// @@ -46,7 +48,7 @@ public partial class CloudStreamsCoreApiClient using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, $"{CloudEventPartitionsApiPath}{type}/{id}"), cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json); + return this.Serializer.Deserialize(json); } /// @@ -55,7 +57,7 @@ public virtual async Task GetStreamMetadataAsync(CancellationTok using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, CloudEventStreamApiPath), cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// @@ -75,7 +77,7 @@ public virtual async Task GetStreamMetadataAsync(CancellationTok var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, uri), cancellationToken).ConfigureAwait(false); var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken); + return this.Serializer.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken); } } diff --git a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.ResourceManagement.cs b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.ResourceManagement.cs index db461431..acf18889 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.ResourceManagement.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.ResourceManagement.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - namespace CloudStreams.Core.Api.Client.Services; public partial class CloudStreamsCoreApiClient diff --git a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.cs b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.cs index bf92f92b..0314f683 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/CloudStreamsCoreApiClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -13,6 +13,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Neuroglia; +using Neuroglia.Serialization; namespace CloudStreams.Core.Api.Client.Services; @@ -30,11 +32,13 @@ public partial class CloudStreamsCoreApiClient /// /// The current /// The service used to create s + /// The service used to serialize/deserialize objects to/from JSON /// The service used to perform http requests - public CloudStreamsCoreApiClient(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, HttpClient httpClient) + public CloudStreamsCoreApiClient(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IJsonSerializer serializer, HttpClient httpClient) { this.ServiceProvider = serviceProvider; this.Logger = loggerFactory.CreateLogger(this.GetType()); + this.Serializer = serializer; this.HttpClient = httpClient; foreach(var apiProperty in this.GetType().GetProperties().Where(p => p.CanRead && p.PropertyType.GetGenericType(typeof(IResourceManagementApi<>)) != null)) { @@ -55,6 +59,11 @@ public CloudStreamsCoreApiClient(IServiceProvider serviceProvider, ILoggerFactor /// protected ILogger Logger { get; } + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + protected IJsonSerializer Serializer { get; } + /// /// Gets the service used to perform http requests /// @@ -74,7 +83,7 @@ public CloudStreamsCoreApiClient(IServiceProvider serviceProvider, ILoggerFactor /// The processed protected virtual Task ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { - if(request == null) throw new ArgumentNullException(nameof(request)); + ArgumentNullException.ThrowIfNull(request); return Task.FromResult(request); } @@ -86,7 +95,7 @@ protected virtual Task ProcessRequestAsync(HttpRequestMessag /// The processed protected virtual async Task ProcessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { - if (response == null) throw new ArgumentNullException(nameof(response)); + ArgumentNullException.ThrowIfNull(response); if (response.IsSuccessStatusCode) return response; var content = string.Empty; if (response.Content != null) content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); @@ -94,7 +103,7 @@ protected virtual async Task ProcessResponseAsync(HttpRespo if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(content)) response.EnsureSuccessStatusCode(); - else throw new CloudStreamsException(Serializer.Json.Deserialize(content)); + else throw new ProblemDetailsException(this.Serializer.Deserialize(content)!); } return response; } diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventPartitionsApi.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventPartitionsApi.cs index cae46196..3f6b013e 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventPartitionsApi.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventPartitionsApi.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia.Eventing.CloudEvents; namespace CloudStreams.Core.Api.Client.Services; @@ -20,7 +20,7 @@ namespace CloudStreams.Core.Api.Client.Services; /// public interface ICloudEventPartitionsApi { - + /// /// Gets the metadata used to describe the specified partition /// diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventStreamApi.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventStreamApi.cs index 87121b2a..b1a39c84 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventStreamApi.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventStreamApi.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia.Eventing.CloudEvents; namespace CloudStreams.Core.Api.Client.Services; diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventsApi.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventsApi.cs index 412217ab..34b57fd0 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventsApi.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudEventsApi.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia.Eventing.CloudEvents; namespace CloudStreams.Core.Api.Client.Services; @@ -20,7 +20,7 @@ namespace CloudStreams.Core.Api.Client.Services; /// public interface ICloudEventsApi { - + /// /// Gets the API used to manage partitions /// diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudStreamsCoreApiClient.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudStreamsCoreApiClient.cs index 2a2ad0e2..3644f365 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudStreamsCoreApiClient.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/ICloudStreamsCoreApiClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHub.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHub.cs index 13095c03..8a1caab3 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHub.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHub.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHubClient.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHubClient.cs index 2db976a2..706bea14 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHubClient.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceEventWatchHubClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApi.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApi.cs index 79c212db..0994e9f9 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApi.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApi.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Neuroglia.Data; + namespace CloudStreams.Core.Api.Client.Services; /// diff --git a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApiClient.cs b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApiClient.cs index 25b2c57f..6fefec40 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApiClient.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/Interfaces/IResourceManagementApiClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - namespace CloudStreams.Core.Api.Client.Services; /// diff --git a/src/core/CloudStreams.Core.Api.Client/Services/ResourceManagementApi.cs b/src/core/CloudStreams.Core.Api.Client/Services/ResourceManagementApi.cs index 8385ecf3..ca96f1e1 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/ResourceManagementApi.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/ResourceManagementApi.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,6 +12,9 @@ // limitations under the License. using Microsoft.Extensions.Logging; +using Neuroglia; +using Neuroglia.Data; +using Neuroglia.Serialization; using System.Net.Mime; using System.Text; @@ -21,49 +24,45 @@ namespace CloudStreams.Core.Api.Client.Services; /// Represents the default implementation of the /// /// The type of s to manage -public class ResourceManagementApi +/// The service used to perform logging +/// The service used to serialize/deserialize objects from/to JSON +/// The service used to perform HTTP requests +/// The path to the API used to manage s of the specified type +public class ResourceManagementApi(ILogger> logger, IJsonSerializer serializer, HttpClient httpClient, string path) : IResourceManagementApi where TResource : IResource, new() { /// - /// Initializes a new + /// Gets the service used to perform logging /// - /// The service used to create s - /// The service used to perform HTTP requests - /// The path to the API used to manage s of the specified type - public ResourceManagementApi(ILoggerFactory loggerFactory, HttpClient httpClient, string path) - { - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.HttpClient = httpClient; - this.Path = CloudStreamsCoreApiClient.ResourceManagementApiPath + $"{new TResource().GetVersion()}/{path}"; - } + protected ILogger Logger { get; } = logger; /// - /// Gets the service used to perform logging + /// Gets the service used to serialize/deserialize objects from/to JSON /// - protected ILogger Logger { get; } + protected IJsonSerializer Serializer { get; } = serializer; /// /// Gets the service used to perform HTTP requests /// - protected HttpClient HttpClient { get; } + protected HttpClient HttpClient { get; } = httpClient; /// /// Gets the path to the API used to manage s of the specified type /// - protected string Path { get; } + protected string Path { get; } = CloudStreamsCoreApiClient.ResourceManagementApiPath + $"{new TResource().GetVersion()}/{path}"; /// public virtual async Task CreateAsync(TResource resource, CancellationToken cancellationToken = default) { if (resource == null) throw new ArgumentNullException(nameof(resource)); - var json = Serializer.Json.Serialize(resource); + var json = this.Serializer.SerializeToText(resource); using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Post, this.Path) { Content = content }, cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// @@ -72,7 +71,7 @@ public virtual async Task GetDefinitionAsync(CancellationTok using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, $"{this.Path}/definition"), cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// @@ -83,7 +82,7 @@ public virtual async Task GetAsync(string name, string? @namespace = using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, uri), cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// @@ -93,48 +92,48 @@ public virtual async Task> ListAsync(string? @namesp var queryStringArguments = new Dictionary(); if (!string.IsNullOrWhiteSpace(@namespace)) queryStringArguments.Add("namespace", @namespace!); if (labelSelectors?.Any() == true) queryStringArguments.Add(nameof(labelSelectors), labelSelectors.Select(s => s.ToString()).Join(',')); - if (queryStringArguments.Any()) uri += $"?{queryStringArguments.Select(kvp => $"{kvp.Key}={kvp.Value}").Join('&')}"; + if (queryStringArguments.Count != 0) uri += $"?{queryStringArguments.Select(kvp => $"{kvp.Key}={kvp.Value}").Join('&')}"; var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Get, uri), cancellationToken).ConfigureAwait(false); var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken)!; + return this.Serializer.DeserializeAsyncEnumerable(responseStream, cancellationToken: cancellationToken)!; } /// public virtual async Task UpdateAsync(TResource resource, CancellationToken cancellationToken = default) { if (resource == null) throw new ArgumentNullException(nameof(resource)); - var json = Serializer.Json.Serialize(resource); + var json = this.Serializer.SerializeToText(resource); using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Put, this.Path) { Content = content }, cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// public virtual async Task PatchAsync(Patch patch, string name, string? @namespace = null, CancellationToken cancellationToken = default) { - if (patch == null) throw new ArgumentNullException(nameof(patch)); + ArgumentNullException.ThrowIfNull(patch); var uri = string.IsNullOrWhiteSpace(@namespace) ? $"{this.Path}/{name}" : $"{this.Path}/namespace/{@namespace}/{name}"; - var json = Serializer.Json.Serialize(patch); + var json = this.Serializer.SerializeToText(patch); using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Patch, uri) { Content = content }, cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// public virtual async Task PatchStatusAsync(Patch patch, string name, string? @namespace = null, CancellationToken cancellationToken = default) { - if (patch == null) throw new ArgumentNullException(nameof(patch)); - var json = Serializer.Json.Serialize(patch); + ArgumentNullException.ThrowIfNull(patch); + var json = this.Serializer.SerializeToText(patch); using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Patch, $"{this.Path}/status") { Content = content }, cancellationToken).ConfigureAwait(false); using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - return Serializer.Json.Deserialize(json)!; + return this.Serializer.Deserialize(json)!; } /// @@ -154,7 +153,7 @@ public virtual async Task DeleteAsync(string name, string? @namespace = null, Ca /// The processed protected virtual Task ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { - if (request == null) throw new ArgumentNullException(nameof(request)); + ArgumentNullException.ThrowIfNull(request); return Task.FromResult(request); } @@ -166,7 +165,7 @@ protected virtual Task ProcessRequestAsync(HttpRequestMessag /// The processed protected virtual async Task ProcessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { - if (response == null) throw new ArgumentNullException(nameof(response)); + ArgumentNullException.ThrowIfNull(response); if (response.IsSuccessStatusCode) return response; var content = string.Empty; if (response.Content != null) content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); @@ -174,7 +173,7 @@ protected virtual async Task ProcessResponseAsync(HttpRespo if (!response.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(content)) response.EnsureSuccessStatusCode(); - else throw new CloudStreamsException(Serializer.Json.Deserialize(content)); + else throw new ProblemDetailsException(this.Serializer.Deserialize(content)!); } return response; } diff --git a/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatch.cs b/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatch.cs index ae8a15bb..d0092487 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatch.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatch.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,48 +11,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - namespace CloudStreams.Core.Api.Client.Services; /// /// Represents the service used to handle a resource watch event subscription /// /// The type of resources to watch -public class ResourceWatch +/// +/// Initializes a new +/// +/// The service used to interact with the +/// The namespace watched resources belong to, if any +/// The used to watch resources of the specified type +public class ResourceWatch(ResourceWatchEventHubClient resourceWatchEventHub, string? resourceNamespace, IObservable> stream) : IObservable>, IAsyncDisposable where TResource : class, IResource, new() { private bool _Disposed; - /// - /// Initializes a new - /// - /// The service used to interact with the - /// The namespace watched resources belong to, if any - /// The used to watch resources of the specified type - public ResourceWatch(ResourceWatchEventHubClient resourceWatchEventHub, string? resourceNamespace, IObservable> stream) - { - this.ResourceWatchEventHub = resourceWatchEventHub ?? throw new ArgumentNullException(nameof(resourceWatchEventHub)); - this.ResourceNamespace = resourceNamespace; - this.Stream = stream; - } - /// /// Gets the service used to interact with the /// - protected ResourceWatchEventHubClient ResourceWatchEventHub { get; } + protected ResourceWatchEventHubClient ResourceWatchEventHub { get; } = resourceWatchEventHub ?? throw new ArgumentNullException(nameof(resourceWatchEventHub)); /// /// Gets the namespace watched resources belong to, if any /// - protected virtual string? ResourceNamespace { get; } + protected virtual string? ResourceNamespace { get; } = resourceNamespace; /// /// Gets the used to watch resources of the specified type /// - protected IObservable> Stream { get; } + protected IObservable> Stream { get; } = stream; /// public IDisposable Subscribe(IObserver> observer) => this.Stream.Subscribe(observer); diff --git a/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatchEventHubClient.cs b/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatchEventHubClient.cs index f02c3d84..923f9b55 100644 --- a/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatchEventHubClient.cs +++ b/src/core/CloudStreams.Core.Api.Client/Services/ResourceWatchEventHubClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; using Microsoft.AspNetCore.SignalR.Client; +using Neuroglia.Eventing.CloudEvents; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -25,8 +24,8 @@ namespace CloudStreams.Core.Api.Client.Services; public class ResourceWatchEventHubClient : IAsyncDisposable { - - private bool _Disposed; + + bool _Disposed; /// /// Initializes a new diff --git a/src/core/CloudStreams.Core.Api.Client/Usings.cs b/src/core/CloudStreams.Core.Api.Client/Usings.cs index 807e0d8b..1b998df8 100644 --- a/src/core/CloudStreams.Core.Api.Client/Usings.cs +++ b/src/core/CloudStreams.Core.Api.Client/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,5 +11,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using CloudStreams.Core; -global using Hylo; \ No newline at end of file +global using CloudStreams.Core.Resources; +global using Neuroglia.Data.Infrastructure.ResourceOriented; \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj b/src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj deleted file mode 100644 index d3abcb64..00000000 --- a/src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net7.0 - enable - enable - 0.14.0 - Linux - ..\..\.. - ..\..\..\docker-compose.dcproj - cloudstreams-api-server - - - - - \ - True - - - \ - True - - - - - - - - - - - - - - - - - - diff --git a/src/core/CloudStreams.Core.Api.Server/Dockerfile b/src/core/CloudStreams.Core.Api.Server/Dockerfile deleted file mode 100644 index 5d7d54a6..00000000 --- a/src/core/CloudStreams.Core.Api.Server/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 -RUN apt-get update -RUN apt-get install -y jq - -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj", "src/core/CloudStreams.Core.Api.Server/"] -COPY ["src/dashboard/CloudStreams.Dashboard/CloudStreams.Dashboard.csproj", "src/dashboard/CloudStreams.Dashboard/"] -COPY ["src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj", "src/core/CloudStreams.Core.Api.Client/"] -COPY ["src/core/CloudStreams.Core/CloudStreams.Core.csproj", "src/core/CloudStreams.Core/"] -COPY ["src/dashboard/CloudStreams.Dashboard.StateManagement/CloudStreams.Dashboard.StateManagement.csproj", "src/dashboard/CloudStreams.Dashboard.StateManagement/"] -COPY ["src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj", "src/core/CloudStreams.Core.Api/"] -COPY ["src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj", "src/core/CloudStreams.Core.Application/"] -COPY ["src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj", "src/core/CloudStreams.Core.Infrastructure/"] -RUN dotnet restore "src/core/CloudStreams.Core.Api.Server/CloudStreams.Core.Api.Server.csproj" -COPY . . -WORKDIR "/src/src/core/CloudStreams.Core.Api.Server" -RUN dotnet build "CloudStreams.Core.Api.Server.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "CloudStreams.Core.Api.Server.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "CloudStreams.Core.Api.Server.dll"] \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api.Server/Properties/launchSettings.json b/src/core/CloudStreams.Core.Api.Server/Properties/launchSettings.json deleted file mode 100644 index db4354ee..00000000 --- a/src/core/CloudStreams.Core.Api.Server/Properties/launchSettings.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "profiles": { - "http": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "api/doc", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "CONNECTIONSTRINGS__EVENTSTORE": "esdb://localhost:2113?tls=false", - "CONNECTIONSTRINGS__REDIS": "localhost" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5094" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/doc", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/doc", - "publishAllPorts": true - } - }, - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:36955", - "sslPort": 0 - } - } -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api.Server/appsettings.Development.json b/src/core/CloudStreams.Core.Api.Server/appsettings.Development.json deleted file mode 100644 index a6e86ace..00000000 --- a/src/core/CloudStreams.Core.Api.Server/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/src/core/CloudStreams.Core.Api.Server/appsettings.json b/src/core/CloudStreams.Core.Api.Server/appsettings.json deleted file mode 100644 index 10f68b8c..00000000 --- a/src/core/CloudStreams.Core.Api.Server/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudStreamsGatewayApiClient.cs b/src/core/CloudStreams.Core.Api/ApiController.cs similarity index 56% rename from src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudStreamsGatewayApiClient.cs rename to src/core/CloudStreams.Core.Api/ApiController.cs index a755b8fb..342d1414 100644 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudStreamsGatewayApiClient.cs +++ b/src/core/CloudStreams.Core.Api/ApiController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,19 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - -namespace CloudStreams.Gateway.Api.Client.Services; +namespace CloudStreams.Core.Api; /// -/// Defines the fundamentals of a service used to interact with a Cloud Streams gateway's API +/// Represents the base class for all s /// -public interface ICloudStreamsGatewayApiClient +/// +/// Initializes a new +/// +/// The service used to mediate calls +public abstract class ApiController(IMediator mediator) + : ControllerBase { /// - /// Gets the API used to manage s + /// Gets the service used to mediate calls /// - ICloudEventsApi CloudEvents { get; } + protected IMediator Mediator { get; } = mediator; } \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/CloudStreams - Backup.Core.Api.csproj b/src/core/CloudStreams.Core.Api/CloudStreams - Backup.Core.Api.csproj new file mode 100644 index 00000000..5b181042 --- /dev/null +++ b/src/core/CloudStreams.Core.Api/CloudStreams - Backup.Core.Api.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + Library + + + + + + + + + + + diff --git a/src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj b/src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj index 8fa1bffb..c9b313af 100644 --- a/src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj +++ b/src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj @@ -1,44 +1,43 @@ - + - net7.0 - Library - enable + net8.0 enable - True + enable 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;api; - Contains the Cloud Streams Core API controllers and services - logo.png - Apache-2.0 - True - True - True + true + cloud-streams/broker + Linux + ..\..\.. - - \ - True - - - \ - True - + + + + + + + + + + + + + + - - - - - + diff --git a/src/core/CloudStreams.Core.Api/ClusterResourceApiController.cs b/src/core/CloudStreams.Core.Api/ClusterResourceApiController.cs new file mode 100644 index 00000000..74c878fb --- /dev/null +++ b/src/core/CloudStreams.Core.Api/ClusterResourceApiController.cs @@ -0,0 +1,76 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using CloudStreams.Core.Application.Commands.Resources.Generic; +using CloudStreams.Core.Application.Queries.Resources.Generic; + +namespace CloudStreams.Core.Api; + +/// +/// Represents a used to manage cluster resources +/// +/// The type of cluster to manage +/// +public abstract class ClusterResourceApiController(IMediator mediator) + : ResourceApiController(mediator) + where TResource : class, IResource, new() +{ + + /// + /// Gets the specified cluster resourced + /// + /// The name of the resource to get + /// A + /// A new + [HttpGet("{name}")] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task GetClusterResource(string name, CancellationToken cancellationToken = default) + { + return this.Process(await this.Mediator.ExecuteAsync(new GetResourceQuery(name, null), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Patches the specified resource + /// + /// The patch to apply + /// The name of the resource to patch + /// A boolean indicating whether or not to persist changes + /// A + /// A new + [HttpPatch("{name}")] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task PatchResource(string name, [FromBody] Patch patch, bool dryRun = false, CancellationToken cancellationToken = default) + { + if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); + return this.Process(await this.Mediator.ExecuteAsync(new PatchResourceCommand(name, null, patch, dryRun), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Deletes the specified resource + /// + /// The name of the resource to delete + /// A boolean indicating whether or not to persist changes + /// A + /// A new + [HttpDelete("{name}")] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task DeleteResource(string name, bool dryRun = false, CancellationToken cancellationToken = default) + { + if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); + return this.Process(await this.Mediator.ExecuteAsync(new DeleteResourceCommand(name, null, dryRun), cancellationToken).ConfigureAwait(false)); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Controllers/BrokersController.cs b/src/core/CloudStreams.Core.Api/Controllers/BrokersController.cs index edd13ca8..7ee14bb7 100644 --- a/src/core/CloudStreams.Core.Api/Controllers/BrokersController.cs +++ b/src/core/CloudStreams.Core.Api/Controllers/BrokersController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,22 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo.Api.Http; -using MediatR; -using Microsoft.AspNetCore.Mvc; - namespace CloudStreams.Core.Api.Controllers; /// /// Represents the used to manage s /// +/// [Route("api/resources/v1/brokers")] -public class BrokersController - : ClusterResourceApiController +public class BrokersController(IMediator mediator) + : ClusterResourceApiController(mediator) { - - /// - public BrokersController(IMediator mediator) : base(mediator) { } - } diff --git a/src/core/CloudStreams.Core.Api/Controllers/CloudEventPartitionsController.cs b/src/core/CloudStreams.Core.Api/Controllers/CloudEventPartitionsController.cs index 3195e663..29ed4973 100644 --- a/src/core/CloudStreams.Core.Api/Controllers/CloudEventPartitionsController.cs +++ b/src/core/CloudStreams.Core.Api/Controllers/CloudEventPartitionsController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,25 +12,18 @@ // limitations under the License. using CloudStreams.Core.Application.Queries.Partitions; -using CloudStreams.Core.Data; -using Hylo.Api.Http; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using System.Net; namespace CloudStreams.Core.Api.Controllers; /// /// Represents the API controller used to manage the cloud event partitions /// +/// [Route("api/core/v1/cloud-events/partitions")] -public class CloudEventPartitionsController - : ApiController +public class CloudEventPartitionsController(IMediator mediator) + : ApiController(mediator) { - /// - public CloudEventPartitionsController(IMediator mediator) : base(mediator) { } - /// /// Lists the ids of the cloud event partitions of the specified type /// @@ -39,15 +32,15 @@ public CloudEventPartitionsController(IMediator mediator) : base(mediator) { } /// A new [HttpGet("{type}")] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(Hylo.ProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.BadRequest)] public virtual async Task ListPartitionsByType(CloudEventPartitionType type, CancellationToken cancellationToken) { if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); - return this.Process(await this.Mediator.Send(new ListEventPartitionIdsQuery(type), cancellationToken).ConfigureAwait(false)); + return this.Process(await this.Mediator.ExecuteAsync(new ListEventPartitionIdsQuery(type), cancellationToken).ConfigureAwait(false)); } /// - /// Gets the specificied cloud event partition's metadata + /// Gets the specified cloud event partition's metadata /// /// The type of the partition to get the metadata of /// The id of the partition to get the metadata of @@ -55,11 +48,11 @@ public virtual async Task ListPartitionsByType(CloudEventPartitio /// A new [HttpGet("{type}/{id}")] [ProducesResponseType(typeof(PartitionMetadata), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(Hylo.ProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.BadRequest)] public virtual async Task GetPartitionMetadata(CloudEventPartitionType type, string id, CancellationToken cancellationToken) { if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); - return this.Process(await this.Mediator.Send(new GetEventPartitionMetadataQuery(new(type, id)), cancellationToken).ConfigureAwait(false)); + return this.Process(await this.Mediator.ExecuteAsync(new GetEventPartitionMetadataQuery(new(type, id)), cancellationToken).ConfigureAwait(false)); } } \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Controllers/CloudEventStreamController.cs b/src/core/CloudStreams.Core.Api/Controllers/CloudEventStreamController.cs index cce46497..98455c65 100644 --- a/src/core/CloudStreams.Core.Api/Controllers/CloudEventStreamController.cs +++ b/src/core/CloudStreams.Core.Api/Controllers/CloudEventStreamController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,25 +12,18 @@ // limitations under the License. using CloudStreams.Core.Application.Queries.Streams; -using CloudStreams.Core.Data; -using Hylo.Api.Http; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using System.Net; namespace CloudStreams.Core.Api.Controllers; /// /// Represents the API controller used to manage the cloud event streams /// +/// [Route("api/core/v1/cloud-events/stream")] -public class CloudEventStreamController - : ApiController +public class CloudEventStreamController(IMediator mediator) + : ApiController(mediator) { - /// - public CloudEventStreamController(IMediator mediator) : base(mediator) { } - /// /// Gets the cloud event stream's metadata /// @@ -38,11 +31,11 @@ public CloudEventStreamController(IMediator mediator) : base(mediator) { } /// A new [HttpGet] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(Hylo.ProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.BadRequest)] public virtual async Task GetStreamMetadata(CancellationToken cancellationToken) { if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); - return this.Process(await this.Mediator.Send(new GetEventStreamMetadataQuery(), cancellationToken).ConfigureAwait(false)); + return this.Process(await this.Mediator.ExecuteAsync(new GetEventStreamMetadataQuery(), cancellationToken).ConfigureAwait(false)); } /// @@ -53,11 +46,11 @@ public virtual async Task GetStreamMetadata(CancellationToken can /// A new [HttpGet("read")] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(Hylo.ProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.BadRequest)] public virtual async Task ReadStream([FromQuery] StreamReadOptions options, CancellationToken cancellationToken) { if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); - return this.Process(await this.Mediator.Send(new ReadEventStreamQuery(options), cancellationToken).ConfigureAwait(false)); + return this.Process(await this.Mediator.ExecuteAsync(new ReadEventStreamQuery(options), cancellationToken).ConfigureAwait(false)); } } diff --git a/src/core/CloudStreams.Core.Api/Controllers/GatewaysController.cs b/src/core/CloudStreams.Core.Api/Controllers/GatewaysController.cs index 9992170b..37595a73 100644 --- a/src/core/CloudStreams.Core.Api/Controllers/GatewaysController.cs +++ b/src/core/CloudStreams.Core.Api/Controllers/GatewaysController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,24 +12,18 @@ // limitations under the License. using CloudStreams.Core.Application.Queries.Gateways; -using CloudStreams.Core.Data; -using Hylo.Api.Http; -using MediatR; -using Microsoft.AspNetCore.Mvc; namespace CloudStreams.Core.Api.Controllers; /// /// Represents the used to manage s /// +/// [Route("api/resources/v1/gateways")] -public class GatewaysController - : ClusterResourceApiController +public class GatewaysController(IMediator mediator) + : ClusterResourceApiController(mediator) { - /// - public GatewaysController(IMediator mediator) : base(mediator) { } - /// /// Checks the health of the specified gateway service, if any /// @@ -39,7 +33,7 @@ public GatewaysController(IMediator mediator) : base(mediator) { } [HttpGet("{name}/health")] public virtual async Task CheckGatewayHealth(string name, CancellationToken cancellationToken = default) { - return this.Process(await this.Mediator.Send(new CheckGatewayHealthQuery(name), cancellationToken).ConfigureAwait(false)); + return this.Process(await this.Mediator.ExecuteAsync(new CheckGatewayHealthQuery(name), cancellationToken).ConfigureAwait(false)); } } diff --git a/src/core/CloudStreams.Core.Api/Controllers/SubscriptionsController.cs b/src/core/CloudStreams.Core.Api/Controllers/SubscriptionsController.cs index a7ebe5bb..55b8f2cc 100644 --- a/src/core/CloudStreams.Core.Api/Controllers/SubscriptionsController.cs +++ b/src/core/CloudStreams.Core.Api/Controllers/SubscriptionsController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,22 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo.Api.Http; -using MediatR; -using Microsoft.AspNetCore.Mvc; - namespace CloudStreams.Core.Api.Controllers; /// /// Represents the used to manage s /// +/// [Route("api/resources/v1/subscriptions")] -public class SubscriptionsController - : ClusterResourceApiController +public class SubscriptionsController(IMediator mediator) + : ClusterResourceApiController(mediator) { - - /// - public SubscriptionsController(IMediator mediator) : base(mediator) { } - } \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Dockerfile b/src/core/CloudStreams.Core.Api/Dockerfile new file mode 100644 index 00000000..589c8583 --- /dev/null +++ b/src/core/CloudStreams.Core.Api/Dockerfile @@ -0,0 +1,28 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 80 +USER root +RUN apt-get update +RUN apt-get install -y jq + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj", "src/core/CloudStreams.Core.Api/"] +COPY ["src/core/CloudStreams.Core.Api.Client/CloudStreams.Core.Api.Client.csproj", "src/core/CloudStreams.Core.Api.Client/"] +COPY ["src/core/CloudStreams.Core/CloudStreams.Core.csproj", "src/core/CloudStreams.Core/"] +COPY ["src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj", "src/core/CloudStreams.Core.Application/"] +RUN dotnet restore "./src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj" +COPY . . +WORKDIR "/src/src/core/CloudStreams.Core.Api" +RUN dotnet build "./CloudStreams.Core.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./CloudStreams.Core.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "CloudStreams.Core.Api.dll"] \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs b/src/core/CloudStreams.Core.Api/Extensions/ICloudStreamsApiBuilderExtensions.cs similarity index 88% rename from src/core/CloudStreams.Core.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs rename to src/core/CloudStreams.Core.Api/Extensions/ICloudStreamsApiBuilderExtensions.cs index c96d9fde..fdc5b91c 100644 --- a/src/core/CloudStreams.Core.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs +++ b/src/core/CloudStreams.Core.Api/Extensions/ICloudStreamsApiBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,10 +12,9 @@ // limitations under the License. using CloudStreams.Core.Api.Services; -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure.Configuration; +using CloudStreams.Core.Application.Services; -namespace CloudStreams.Core.Api.Configuration; +namespace CloudStreams.Core.Api; /// /// Defines extensions for s @@ -30,6 +29,7 @@ public static class ICloudStreamsApiBuilderExtensions /// The configured public static ICloudStreamsApplicationBuilder UseCoreApi(this ICloudStreamsApplicationBuilder builder) { + builder.Services.AddSignalR(); builder.Services.AddSingleton(); builder.Services.AddSingleton(provider => provider.GetRequiredService()); builder.Services.AddHostedService(); @@ -38,3 +38,4 @@ public static ICloudStreamsApplicationBuilder UseCoreApi(this ICloudStreamsAppli } } + diff --git a/src/core/CloudStreams.Core.Application/Configuration/IWebApplicationBuilderExtensions.cs b/src/core/CloudStreams.Core.Api/Extensions/IWebApplicationBuilderExtensions.cs similarity index 90% rename from src/core/CloudStreams.Core.Application/Configuration/IWebApplicationBuilderExtensions.cs rename to src/core/CloudStreams.Core.Api/Extensions/IWebApplicationBuilderExtensions.cs index 403e2d08..3c7661f9 100644 --- a/src/core/CloudStreams.Core.Application/Configuration/IWebApplicationBuilderExtensions.cs +++ b/src/core/CloudStreams.Core.Api/Extensions/IWebApplicationBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Infrastructure.Configuration; +using CloudStreams.Core.Api.Services; +using CloudStreams.Core.Application.Services; -namespace CloudStreams.Core.Application.Configuration; +namespace CloudStreams.Core.Api; /// /// Defines extensions for s @@ -38,3 +39,4 @@ public static WebApplicationBuilder UseCloudStreams(this WebApplicationBuilder a } } + diff --git a/src/core/CloudStreams.Core.Api/Hubs/ResourceEventWatchHub.cs b/src/core/CloudStreams.Core.Api/Hubs/ResourceEventWatchHub.cs index 4b068f77..642d1591 100644 --- a/src/core/CloudStreams.Core.Api/Hubs/ResourceEventWatchHub.cs +++ b/src/core/CloudStreams.Core.Api/Hubs/ResourceEventWatchHub.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -13,8 +13,6 @@ using CloudStreams.Core.Api.Client.Services; using CloudStreams.Core.Api.Services; -using Hylo; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; namespace CloudStreams.Core.Api.Hubs; @@ -22,24 +20,19 @@ namespace CloudStreams.Core.Api.Hubs; /// /// Represents the used to notify clients about resource-related changes /// +/// +/// Initializes a new +/// +/// The service used to control s [Route("api/resource-management/v1/ws/watch")] -public class ResourceEventWatchHub - : Hub, IResourceEventWatchHub +public class ResourceEventWatchHub(ResourceWatchEventHubController controller) + : Hub, IResourceEventWatchHub { - /// - /// Initializes a new - /// - /// The service used to control s - public ResourceEventWatchHub(ResourceWatchEventHubController controller) - { - this.Controller = controller; - } - /// /// Gets the service used to control s /// - protected ResourceWatchEventHubController Controller { get; } + protected ResourceWatchEventHubController Controller { get; } = controller; /// public virtual Task Watch(ResourceDefinitionInfo definition, string? @namespace = null) => this.Controller.WatchResourcesAsync(this.Context.ConnectionId, definition, @namespace); diff --git a/src/core/CloudStreams.Core.Api.Server/Program.cs b/src/core/CloudStreams.Core.Api/Program.cs similarity index 88% rename from src/core/CloudStreams.Core.Api.Server/Program.cs rename to src/core/CloudStreams.Core.Api/Program.cs index a25b79ca..34ff37b7 100644 --- a/src/core/CloudStreams.Core.Api.Server/Program.cs +++ b/src/core/CloudStreams.Core.Api/Program.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Api.Configuration; +using CloudStreams.Core.Api; using CloudStreams.Core.Api.Hubs; -using CloudStreams.Core.Application.Configuration; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Swashbuckle.AspNetCore.SwaggerUI; @@ -52,7 +51,7 @@ }); app.MapControllers(); -app.MapHub("api/resource-management/v1/ws/watch"); +app.MapHub("api/resources/v1/ws/watch"); app.MapFallbackToFile("index.html"); await app.RunAsync().ConfigureAwait(false); \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Properties/launchSettings.json b/src/core/CloudStreams.Core.Api/Properties/launchSettings.json index 56033ec8..9de4d2bb 100644 --- a/src/core/CloudStreams.Core.Api/Properties/launchSettings.json +++ b/src/core/CloudStreams.Core.Api/Properties/launchSettings.json @@ -6,7 +6,18 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:51335;http://localhost:51336" + "applicationUrl": "https://localhost:55424;http://localhost:55425" + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true } } } \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/ResourceApiController.cs b/src/core/CloudStreams.Core.Api/ResourceApiController.cs new file mode 100644 index 00000000..46401ada --- /dev/null +++ b/src/core/CloudStreams.Core.Api/ResourceApiController.cs @@ -0,0 +1,155 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using CloudStreams.Core.Application.Commands.Resources.Generic; +using CloudStreams.Core.Application.Queries.Resources.Generic; + +namespace CloudStreams.Core.Api; + +/// +/// Represents the base class of s used to manage s +/// +/// The type of to manage +/// +public abstract class ResourceApiController(IMediator mediator) + : ApiController(mediator) + where TResource : class, IResource, new() +{ + + /// + /// Creates a new resource of the specified type + /// + /// The resource to create + /// A boolean indicating whether or not to persist changes + /// A + /// A new + [HttpPost] + [ProducesResponseType(typeof(Resource), (int)HttpStatusCode.Created)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public async Task CreateResource([FromBody] TResource resource, bool dryRun = false, CancellationToken cancellationToken = default) + { + if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); + return this.Process(await this.Mediator.ExecuteAsync(new CreateResourceCommand(resource, dryRun), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Gets the definition of the managed resources + /// + /// A + /// A new + [HttpGet("definition")] + [ProducesResponseType(typeof(ResourceDefinition), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task GetResourceDefinition(CancellationToken cancellationToken = default) + { + return this.Process(await this.Mediator.ExecuteAsync(new GetResourceDefinitionQuery(), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Asynchronously enumerates through all resources that matches the specified label selector, if any. To enumerate asynchronously, consumers must read the response stream in chunks + /// + /// A comma-separated list of label selectors, if any + /// A + /// A new + [HttpGet] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task GetClusterResources(string? labelSelector = null, CancellationToken cancellationToken = default) + { + if (!this.TryParseLabelSelectors(labelSelector, out var labelSelectors)) return this.InvalidLabelSelector(labelSelector!); + return this.Process(await this.Mediator.ExecuteAsync(new GetResourcesQuery(null, labelSelectors), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Lists matching resources + /// + /// A comma-separated list of label selectors, if any + /// The maximum amount, if any, of results to list at once + /// A token, defined by a previously retrieved collection, used to continue enumerating through matches + /// A + /// A new + [HttpGet("list")] + [ProducesResponseType(typeof(Collection), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task ListClusterResources(string? labelSelector = null, ulong? maxResults = null, string? continuationToken = null, CancellationToken cancellationToken = default) + { + if (!this.TryParseLabelSelectors(labelSelector, out var labelSelectors)) return this.InvalidLabelSelector(labelSelector!); + return this.Process(await this.Mediator.ExecuteAsync(new ListResourcesQuery(null, labelSelectors, maxResults, continuationToken), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Watches matching resources + /// + /// A comma-separated list of label selectors, if any + /// A + /// A new + [HttpGet("watch")] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task WatchClusterResources(string? labelSelector = null, CancellationToken cancellationToken = default) + { + if (!this.TryParseLabelSelectors(labelSelector, out var labelSelectors)) return this.InvalidLabelSelector(labelSelector!); + var response = await this.Mediator.ExecuteAsync(new WatchResourcesQuery(null, labelSelectors), cancellationToken).ConfigureAwait(false); + if (response.Status >=200 && response.Status < 300) return this.Process(response); + var watch = response.Data!; + return this.Ok(watch.ToAsyncEnumerable()); + } + + /// + /// Replaces the specified resource + /// + /// The resource to replace + /// A boolean indicating whether or not to persist changes + /// A + /// A new + [HttpPut] + [ProducesResponseType(typeof(IAsyncEnumerable), (int)HttpStatusCode.OK)] + [ProducesErrorResponseType(typeof(Neuroglia.ProblemDetails))] + public virtual async Task ReplaceResource(TResource resource, bool dryRun = false, CancellationToken cancellationToken = default) + { + if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); + return this.Process(await this.Mediator.ExecuteAsync(new ReplaceResourceCommand(resource, dryRun), cancellationToken).ConfigureAwait(false)); + } + + /// + /// Parses the specified string into a new of s + /// + /// The string to parse + /// A new containing the parsed s + /// A boolean indicating whether or not the input could be parse + protected virtual bool TryParseLabelSelectors(string? labelSelector, out IEnumerable? labelSelectors) + { + labelSelectors = null; + try + { + if (!string.IsNullOrWhiteSpace(labelSelector)) labelSelectors = LabelSelector.ParseList(labelSelector); + return true; + } + catch + { + return false; + } + } + + /// + /// Creates a new that describes an error while parsing the request's label selector + /// + /// The invalid label selector + /// A new + protected IActionResult InvalidLabelSelector(string labelSelector) + { + this.ModelState.AddModelError(nameof(labelSelector), $"The specified value '{labelSelector}' is not a valid comma-separated label selector list"); + return this.ValidationProblem("Bad Request", statusCode: (int)HttpStatusCode.BadRequest, title: "Bad Request", modelStateDictionary: this.ModelState); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/Services/BrokerHealthMonitor.cs b/src/core/CloudStreams.Core.Api/Services/BrokerHealthMonitor.cs index 24e22e73..ab5acc4b 100644 --- a/src/core/CloudStreams.Core.Api/Services/BrokerHealthMonitor.cs +++ b/src/core/CloudStreams.Core.Api/Services/BrokerHealthMonitor.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; -using Hylo.Infrastructure.Services; -using Json.Patch; -using Json.Pointer; -using System.Diagnostics; -using System.Net; -using System.Net.Mime; -using System.Reactive.Linq; using System.Text; using System.Text.Json.Nodes; @@ -28,7 +19,15 @@ namespace CloudStreams.Core.Api.Services; /// /// Represents a service used to monitor the health of a gateway /// -public class BrokerHealthMonitor +/// +/// Initializes a new +/// +/// The service used to perform logging +/// The service used to serialize/deserialize objects to/from JSON +/// The service used to manage resources +/// The service used to monitor the handled +/// The service used to perform HTTP requests +public class BrokerHealthMonitor(ILogger logger, IJsonSerializer serializer, IRepository repository, IResourceMonitor monitor, HttpClient httpClient) : IHostedService, IDisposable, IAsyncDisposable { @@ -36,47 +35,37 @@ public class BrokerHealthMonitor bool _disposed; /// - /// Initializes a new + /// Gets the service used to perform logging /// - /// The service used to create s - /// The service used to manage resources - /// The service used to monitor the handled - /// The service used to perform HTTP requests - public BrokerHealthMonitor(ILoggerFactory loggerFactory, IRepository repository, IResourceMonitor monitor, HttpClient httpClient) - { - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.Repository = repository; - this.Monitor = monitor; - this.HttpClient = httpClient; - } + protected ILogger Logger { get; } = logger; /// - /// Gets the service used to perform logging + /// Gets the service used to serialize/deserialize objects to/from JSON /// - protected ILogger Logger { get; } + protected IJsonSerializer Serializer { get; } = serializer; /// /// Gets the service used to manage resources /// - protected IRepository Repository { get; } + protected IRepository Repository { get; } = repository; /// - /// Gets the service used to monitor the handled + /// Gets the service used to monitor the handled /// - protected IResourceMonitor Monitor { get; } + protected IResourceMonitor Monitor { get; } = monitor; /// /// Gets the service used to perform HTTP requests /// - protected HttpClient HttpClient { get; } + protected HttpClient HttpClient { get; } = httpClient; /// - /// Gets the monitored + /// Gets the monitored /// protected Broker Broker => this.Monitor.Resource; /// - /// Gets the used to periodically check the health of the + /// Gets the used to periodically check the health of the /// protected Timer? HealthCheckTimer { get; private set; } @@ -104,7 +93,7 @@ public virtual async Task StopAsync(CancellationToken cancellationToken) } /// - /// Handles changes to the 's + /// Handles changes to the 's /// /// The updated /// A new awaitable @@ -115,8 +104,8 @@ protected virtual async Task OnHealthChecksConfigurationChangedAsync(ServiceHeal if (this.Broker.Spec.Service == null || this.Broker.Spec.Service.HealthChecks == null) return; var delay = this.Broker.Status?.LastHealthCheckAt.HasValue == true ? DateTimeOffset.Now - this.Broker.Status.LastHealthCheckAt : TimeSpan.Zero; if (delay < TimeSpan.Zero) delay = TimeSpan.Zero; - if (this.Broker.Spec.Service.HealthChecks.Interval.HasValue && this.Broker.Spec.Service.HealthChecks.Interval > delay) delay = this.Broker.Spec.Service.HealthChecks.Interval; - this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Broker.Spec.Service.HealthChecks.Interval ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); + if (this.Broker.Spec.Service.HealthChecks.Interval != null && this.Broker.Spec.Service.HealthChecks.Interval > delay) delay = this.Broker.Spec.Service.HealthChecks.Interval; + this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Broker.Spec.Service.HealthChecks.Interval?.ToTimeSpan() ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); } /// @@ -137,7 +126,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) var uri = $"{hostNameAndPort}{path}"; using var request = new HttpRequestMessage(new HttpMethod(this.Broker.Spec.Service.HealthChecks.Request.Method), uri); if (this.Broker.Spec.Service.HealthChecks.Request.Headers != null) this.Broker.Spec.Service.HealthChecks.Request.Headers!.ToList().ForEach(h => request.Headers.TryAddWithoutValidation(h.Key, h.Value)); - if (this.Broker.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(Serializer.Json.Serialize(this.Broker.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); + if (this.Broker.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(this.Serializer.SerializeToText(this.Broker.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); HealthCheckResponse? healthCheckResponse = null; try { @@ -150,12 +139,12 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) { try { - healthCheckResponse = Serializer.Json.Deserialize(content)!; + healthCheckResponse = this.Serializer.Deserialize(content)!; } catch { } if(healthCheckResponse == null) { - var result = Serializer.Json.Deserialize(content); + var result = this.Serializer.Deserialize(content); if (result?.TryGetPropertyValue(nameof(HealthCheckResult.Status).ToCamelCase(), out var node) == true && node != null) healthCheckResponse = new(node.GetValue().ToCamelCase()); else healthCheckResponse = new(response.IsSuccessStatusCode ? HealthStatus.Healthy : HealthStatus.Unhealthy); } @@ -178,7 +167,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) patchTarget.Status ??= new(); patchTarget.Status.HealthStatus = healthCheckResponse.Status; patchTarget.Status.LastHealthCheckAt = DateTimeOffset.Now; - var patch = new Patch(PatchType.JsonPatch, JsonPatchHelper.CreateJsonPatchFromDiff(patchSource, patchTarget)); + var patch = new Patch(PatchType.JsonPatch, JsonPatchUtility.CreateJsonPatchFromDiff(patchSource, patchTarget)); await this.Repository.PatchStatusAsync(patch, this.Broker.GetName(), this.Broker.GetNamespace(), false, this.CancellationTokenSource!.Token).ConfigureAwait(false); } } @@ -190,7 +179,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) { if (this.Broker.Spec.Service != null && this.Broker.Spec.Service.HealthChecks != null) { - this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Broker.Spec.Service.HealthChecks.Interval ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); + this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Broker.Spec.Service.HealthChecks.Interval?.ToTimeSpan() ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); } } } diff --git a/src/core/CloudStreams.Core.Api/Services/BrokerResourceController.cs b/src/core/CloudStreams.Core.Api/Services/BrokerResourceController.cs index 1f5219fb..fc0d0bdb 100644 --- a/src/core/CloudStreams.Core.Api/Services/BrokerResourceController.cs +++ b/src/core/CloudStreams.Core.Api/Services/BrokerResourceController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,11 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; -using Hylo.Infrastructure.Configuration; -using Hylo.Infrastructure.Services; using Microsoft.Extensions.Options; +using Neuroglia.Data.Infrastructure.ResourceOriented.Configuration; using System.Collections.Concurrent; namespace CloudStreams.Core.Api.Services; @@ -23,21 +20,15 @@ namespace CloudStreams.Core.Api.Services; /// /// Represents a used to control s /// -public class BrokerResourceController - : ResourceController +/// +public class BrokerResourceController(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository) + : ResourceController(loggerFactory, controllerOptions, repository) { - /// - public BrokerResourceController(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository) - : base(loggerFactory, controllerOptions, repository) - { - this.ServiceProvider = serviceProvider; - } - /// /// Gets the current /// - protected IServiceProvider ServiceProvider { get; } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; /// /// Gets a containing key/health monitor mappings of managed gateways @@ -48,10 +39,7 @@ public BrokerResourceController(IServiceProvider serviceProvider, ILoggerFactory public override async Task StartAsync(CancellationToken cancellationToken) { await base.StartAsync(cancellationToken).ConfigureAwait(false); - foreach(var gateway in this.Resources.Values) - { - await this.OnResourceCreatedAsync(gateway, cancellationToken).ConfigureAwait(false); - } + foreach(var gateway in this.Resources.Values) await this.OnResourceCreatedAsync(gateway, cancellationToken).ConfigureAwait(false); } /// @@ -79,10 +67,7 @@ protected override async ValueTask DisposeAsync(bool disposing) { await base.DisposeAsync(disposing).ConfigureAwait(false); if (!disposing) return; - foreach(var kvp in this.HealthMonitors) - { - await kvp.Value.DisposeAsync().ConfigureAwait(false); - } + foreach(var kvp in this.HealthMonitors) await kvp.Value.DisposeAsync().ConfigureAwait(false); this.HealthMonitors.Clear(); } @@ -91,10 +76,7 @@ protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) return; - foreach (var kvp in this.HealthMonitors) - { - kvp.Value.Dispose(); - } + foreach (var kvp in this.HealthMonitors) kvp.Value.Dispose(); this.HealthMonitors.Clear(); } diff --git a/src/core/CloudStreams.Core.Application/Configuration/CloudStreamsApplicationBuilder.cs b/src/core/CloudStreams.Core.Api/Services/CloudStreamsApplicationBuilder.cs similarity index 56% rename from src/core/CloudStreams.Core.Application/Configuration/CloudStreamsApplicationBuilder.cs rename to src/core/CloudStreams.Core.Api/Services/CloudStreamsApplicationBuilder.cs index 6f996dff..a044b996 100644 --- a/src/core/CloudStreams.Core.Application/Configuration/CloudStreamsApplicationBuilder.cs +++ b/src/core/CloudStreams.Core.Api/Services/CloudStreamsApplicationBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,25 +11,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +using CloudStreams.Core.Application; +using CloudStreams.Core.Application.Commands.Resources; using CloudStreams.Core.Application.Services; -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure.Configuration; -using CloudStreams.Core.Infrastructure.Services; using FluentValidation; -using Hylo; -using Hylo.Infrastructure; -using MediatR; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.OpenApi.Models; +using Neuroglia.Data.Expressions.JQ; +using Neuroglia.Data.Infrastructure.EventSourcing.Services; +using Neuroglia.Data.PatchModel.Services; +using Neuroglia.Mediation.Services; +using Neuroglia.Plugins; +using Neuroglia.Security.Services; +using Neuroglia.Serialization.Json; +using Neuroglia.Serialization.Yaml; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using System.Reflection; -namespace CloudStreams.Core.Application.Configuration; +namespace CloudStreams.Core.Api.Services; /// /// Represents the default implementation of the @@ -52,9 +55,62 @@ public CloudStreamsApplicationBuilder(ConfigurationManager configuration, IHostE this.Services = services; this.Logging = logging; this.Services.AddHttpClient(); - this.Services.AddHylo(this.Configuration, setup => + this.Services.AddResponseCompression(options => { - setup.UseDatabaseInitializer(); + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); + }); + this.Services.AddProblemDetails(); + this.Services.AddEndpointsApiExplorer(); + this.Services.AddCoreApiCommands(); + this.Services.AddCoreApiQueries(); + this.Services.AddSerialization(); + this.Services.AddJsonSerializer(); + this.Services.AddYamlDotNetSerializer(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddPluginProvider(this.Configuration); + this.Services.AddPlugin(); + this.Services.AddPlugin(); + this.Services.AddPlugin(); + this.Services.AddSingleton(); + this.Services.AddSingleton(provider => provider.GetRequiredService()); + this.Services.AddSingleton(provider => provider.GetRequiredService()); + this.Services.AddHostedService(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddSingleton(); + this.Services.AddJQExpressionEvaluator(); + this.Services.AddSwaggerGen(builder => + { + builder.CustomOperationIds(o => + { + var action = (ControllerActionDescriptor)o.ActionDescriptor; + return $"{action.ActionName}".ToCamelCase(); + }); + builder.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + builder.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Cloud Streams REST API", + Version = "v1", + Description = "The Open API documentation for the Cloud Streams REST API", + License = new OpenApiLicense() + { + Name = "Apache-2.0", + Url = new("https://raw.githubusercontent.com/neuroglia-io/cloud-streams/main/LICENSE") + }, + Contact = new() + { + Name = "The Cloud Streams Authors", + Url = new Uri("https://github.com/neuroglia-io/cloud-streams") + } + }); + builder.IncludeXmlComments(typeof(Broker).Assembly.Location.Replace(".dll", ".xml")); }); } @@ -80,45 +136,25 @@ public CloudStreamsApplicationBuilder(ConfigurationManager configuration, IHostE /// protected string? ServiceVersion { get; set; } - /// - /// Gets the type of to use - /// - protected Type? ExpressionEvaluatorProviderType { get; set; } = typeof(PluginExpressionEvaluatorProvider); - - /// - /// Gets the type of to use - /// - protected Type? CloudEventStoreProviderType { get; set; } = typeof(PluginEventStoreProvider); - - /// - /// Gets the type of to use - /// - protected Type? SchemaRegistryProviderType { get; set; } = typeof(PluginSchemaRegistryProvider); - /// /// Gets an containing the assemblies to scan for mediation components /// - protected HashSet ApplicationParts { get; } = new(); + protected HashSet ApplicationParts { get; } = []; /// /// Gets an containing the assemblies to scan for mediation components /// - protected HashSet MediationAssemblies { get; } = new() { typeof(CloudStreamsApplicationBuilder).Assembly }; - - /// - /// Gets a containing the types of the s to register - /// - protected List MediationPipelineBehaviors { get; } = new(); + protected HashSet MediationAssemblies { get; } = [typeof(CloudStreamsApplicationBuilder).Assembly]; /// /// Gets an containing the assemblies to scan for fluent validators /// - protected HashSet ValidationAssemblies { get; } = new() { }; + protected HashSet ValidationAssemblies { get; } = []; /// /// Gets a containing the s used to setup the application health checks /// - protected List> HealthCheckConfigurations { get; } = new(); + protected List> HealthCheckConfigurations { get; } = []; /// public virtual ICloudStreamsApplicationBuilder WithServiceName(string name, string? version = null) @@ -133,7 +169,7 @@ public virtual ICloudStreamsApplicationBuilder WithServiceName(string name, stri public virtual ICloudStreamsApplicationBuilder RegisterApplicationPart() { var assembly = typeof(TMarkup).Assembly; - if (!this.ApplicationParts.Contains(assembly)) this.ApplicationParts.Add(assembly); + this.ApplicationParts.Add(assembly); return this; } @@ -141,7 +177,7 @@ public virtual ICloudStreamsApplicationBuilder RegisterApplicationPart( public virtual ICloudStreamsApplicationBuilder RegisterMediationAssembly() { var assembly = typeof(TMarkup).Assembly; - if (!this.MediationAssemblies.Contains(assembly)) this.MediationAssemblies.Add(assembly); + this.MediationAssemblies.Add(assembly); return this; } @@ -149,80 +185,34 @@ public virtual ICloudStreamsApplicationBuilder RegisterMediationAssembly() { var assembly = typeof(TMarkup).Assembly; - if (!this.ValidationAssemblies.Contains(assembly)) this.ValidationAssemblies.Add(assembly); + this.ValidationAssemblies.Add(assembly); return this; } /// public virtual ICloudStreamsApplicationBuilder RegisterHealthCheck(Action setup) { - if (setup == null) throw new ArgumentNullException(nameof(setup)); + ArgumentNullException.ThrowIfNull(setup); this.HealthCheckConfigurations.Add(setup); return this; } - /// - public virtual ICloudStreamsApplicationBuilder RegisterMediationPipelineBehavior(Type behaviorType) - { - if (behaviorType == null) throw new ArgumentNullException(nameof(behaviorType)); - if (behaviorType.GetGenericType(typeof(IPipelineBehavior<,>)) == null) throw new ArgumentException($"Failed to cast the specified type to type '{typeof(IPipelineBehavior<,>)}'", nameof(behaviorType)); - this.MediationPipelineBehaviors.Add(behaviorType); - return this; - } - - /// - public virtual ICloudStreamsApplicationBuilder UseEventStoreProvider() - where TProvider : class, IEventStoreProvider - { - this.CloudEventStoreProviderType = typeof(TProvider); - return this; - } - - /// - public virtual ICloudStreamsApplicationBuilder UseSchemaRegistryProvider() - where TProvider : class, ISchemaRegistryProvider - { - this.SchemaRegistryProviderType = typeof(TProvider); - return this; - } - - /// - public virtual ICloudStreamsApplicationBuilder UseExpressionEvaluatorProvider() - where TProvider : class, IExpressionEvaluatorProvider - { - this.ExpressionEvaluatorProviderType = typeof(TProvider); - return this; - } - /// public virtual void Build() { - if (this.CloudEventStoreProviderType == null) throw new Exception("Invalid Cloud Streams API configuration: the cloud event store provider type must be set"); - if (this.SchemaRegistryProviderType == null) throw new Exception("Invalid Cloud Streams API configuration: the schema registry provider type must be set"); - if (this.ExpressionEvaluatorProviderType == null) throw new Exception("Invalid Cloud Streams API configuration: the expression evaluator provider type must be set"); - var mvc = this.Services.AddControllers(options => { - options.InputFormatters.Add(new YamlInputFormatter()); - options.OutputFormatters.Add(new YamlOutputFormatter()); - }); - mvc.AddJsonOptions(options => - { - Serializer.Json.DefaultOptionsConfiguration?.Invoke(options.JsonSerializerOptions); + options.InputFormatters.Add(new YamlInputFormatter(YamlSerializer.Default)); + options.OutputFormatters.Add(new YamlOutputFormatter(YamlSerializer.Default)); }); - foreach(var applicationPart in this.ApplicationParts) - { - mvc.AddApplicationPart(applicationPart); - } + mvc.AddJsonOptions(options => JsonSerializer.DefaultOptionsConfiguration(options.JsonSerializerOptions)); + foreach(var applicationPart in this.ApplicationParts) mvc.AddApplicationPart(applicationPart); var healthChecks = this.Services.AddHealthChecks(); - foreach(var configureHealthChecks in this.HealthCheckConfigurations) - { - configureHealthChecks(healthChecks); - } + foreach(var configureHealthChecks in this.HealthCheckConfigurations) configureHealthChecks(healthChecks); var resourceBuilder = ResourceBuilder.CreateDefault().AddService(serviceName: this.ServiceName, serviceVersion: this.ServiceVersion); - Telemetry.ActivitySource = new(this.ServiceName, this.ServiceVersion); + CloudStreamsDefaults.Telemetry.ActivitySource = new(this.ServiceName, this.ServiceVersion); this.Logging.AddOpenTelemetry(builder => { @@ -253,60 +243,14 @@ public virtual void Build() .AddOtlpExporter(); }); - this.Services.AddSignalR(); - this.Services.AddMediatR(options => + this.Services.AddMediator(options => { - options.RegisterServicesFromAssemblies(this.MediationAssemblies.ToArray()); - options.BehaviorsToRegister.Add(new(typeof(IPipelineBehavior<,>), typeof(ExceptionHandlingPipelineBehavior<,>), ServiceLifetime.Transient)); - this.MediationPipelineBehaviors.ForEach(t => options.BehaviorsToRegister.Add(new(typeof(IPipelineBehavior<,>), t, ServiceLifetime.Transient))); + options.ScanAssembly(typeof(CreateResourceCommand).Assembly); + options.UseDefaultPipelineBehavior(typeof(GuardExceptionHandlingMiddleware<,>)); + options.UseDefaultPipelineBehavior(typeof(ProblemDetailsExceptionHandlingMiddleware<,>)); + this.MediationAssemblies.ToList().ForEach(a => options.ScanAssembly(a)); }); this.Services.AddValidatorsFromAssemblies(this.ValidationAssemblies, ServiceLifetime.Transient); - this.Services.AddResponseCompression(options => - { - options.EnableForHttps = true; - options.Providers.Add(); - options.Providers.Add(); - }); - this.Services.AddProblemDetails(); - this.Services.AddEndpointsApiExplorer(); - this.Services.TryAddSingleton(); - this.Services.TryAddSingleton(); - - this.Services.TryAddSingleton(this.CloudEventStoreProviderType); - this.Services.TryAddSingleton(provider => (IEventStoreProvider)provider.GetRequiredService(this.CloudEventStoreProviderType)); - this.Services.TryAddSingleton(this.SchemaRegistryProviderType); - this.Services.TryAddSingleton(provider => (ISchemaRegistryProvider)provider.GetRequiredService(this.SchemaRegistryProviderType)); - this.Services.TryAddSingleton(this.ExpressionEvaluatorProviderType); - this.Services.TryAddSingleton(provider => (IExpressionEvaluatorProvider)provider.GetRequiredService(this.ExpressionEvaluatorProviderType)); - - this.Services.AddSwaggerGen(builder => - { - builder.CustomOperationIds(o => - { - var action = (ControllerActionDescriptor)o.ActionDescriptor; - return $"{action.ActionName}".ToCamelCase(); - }); - builder.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); - builder.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Cloud Streams REST API", - Version = "v1", - Description = "The Open API documentation for the Cloud Streams REST API", - License = new OpenApiLicense() - { - Name = "Apache-2.0", - Url = new("https://raw.githubusercontent.com/neuroglia-io/cloud-streams/main/LICENSE") - }, - Contact = new() - { - Name = "The Cloud Streams Authors", - Url = new Uri("https://github.com/neuroglia-io/cloud-streams") - } - }); - builder.IncludeXmlComments(typeof(Broker).Assembly.Location.Replace(".dll", ".xml")); - }); - this.Services.AddGenericQueryHandlers(); - this.Services.AddGenericCommandHandlers(); } } diff --git a/src/core/CloudStreams.Core.Api/Services/GatewayHealthMonitor.cs b/src/core/CloudStreams.Core.Api/Services/GatewayHealthMonitor.cs index 6ead24a9..15f8928c 100644 --- a/src/core/CloudStreams.Core.Api/Services/GatewayHealthMonitor.cs +++ b/src/core/CloudStreams.Core.Api/Services/GatewayHealthMonitor.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,13 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; -using Hylo.Infrastructure.Services; -using Json.Patch; -using Json.Pointer; +using Neuroglia.Data.Infrastructure.ResourceOriented.Services; +using Neuroglia.Reactive; +using Neuroglia.Serialization; using System.Diagnostics; -using System.Net; using System.Net.Mime; using System.Reactive.Linq; using System.Text; @@ -28,7 +25,15 @@ namespace CloudStreams.Core.Api.Services; /// /// Represents a service used to monitor the health of a gateway /// -public class GatewayHealthMonitor +/// +/// Initializes a new +/// +/// The service used to perform logging +/// The service used to serialize/deserialize objects to/from JSON +/// The service used to manage resources +/// The service used to monitor the handled +/// The service used to perform HTTP requests +public class GatewayHealthMonitor(ILogger logger, IJsonSerializer serializer, IRepository repository, IResourceMonitor monitor, HttpClient httpClient) : IHostedService, IDisposable, IAsyncDisposable { @@ -36,47 +41,37 @@ public class GatewayHealthMonitor bool _disposed; /// - /// Initializes a new + /// Gets the service used to perform logging /// - /// The service used to create s - /// The service used to manage resources - /// The service used to monitor the handled - /// The service used to perform HTTP requests - public GatewayHealthMonitor(ILoggerFactory loggerFactory, IRepository repository, IResourceMonitor monitor, HttpClient httpClient) - { - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.Repository = repository; - this.Monitor = monitor; - this.HttpClient = httpClient; - } + protected ILogger Logger { get; } = logger; /// - /// Gets the service used to perform logging + /// Gets the service used to serialize/deserialize objects to/from JSON /// - protected ILogger Logger { get; } + protected IJsonSerializer Serializer { get; } = serializer; /// /// Gets the service used to manage resources /// - protected IRepository Repository { get; } + protected IRepository Repository { get; } = repository; /// - /// Gets the service used to monitor the handled + /// Gets the service used to monitor the handled /// - protected IResourceMonitor Monitor { get; } + protected IResourceMonitor Monitor { get; } = monitor; /// /// Gets the service used to perform HTTP requests /// - protected HttpClient HttpClient { get; } + protected HttpClient HttpClient { get; } = httpClient; /// - /// Gets the monitored + /// Gets the monitored /// protected Gateway Gateway => this.Monitor.Resource; /// - /// Gets the used to periodically check the health of the + /// Gets the used to periodically check the health of the /// protected Timer? HealthCheckTimer { get; private set; } @@ -104,7 +99,7 @@ public virtual async Task StopAsync(CancellationToken cancellationToken) } /// - /// Handles changes to the 's + /// Handles changes to the 's /// /// The updated /// A new awaitable @@ -115,8 +110,8 @@ protected virtual async Task OnHealthChecksConfigurationChangedAsync(ServiceHeal if (this.Gateway.Spec.Service == null || this.Gateway.Spec.Service.HealthChecks == null) return; var delay = this.Gateway.Status?.LastHealthCheckAt.HasValue == true ? DateTimeOffset.Now - this.Gateway.Status.LastHealthCheckAt : TimeSpan.Zero; if (delay < TimeSpan.Zero) delay = TimeSpan.Zero; - if (this.Gateway.Spec.Service.HealthChecks.Interval.HasValue && this.Gateway.Spec.Service.HealthChecks.Interval > delay) delay = this.Gateway.Spec.Service.HealthChecks.Interval; - this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Gateway.Spec.Service.HealthChecks.Interval ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); + if (this.Gateway.Spec.Service.HealthChecks.Interval != null && this.Gateway.Spec.Service.HealthChecks.Interval > delay) delay = this.Gateway.Spec.Service.HealthChecks.Interval; + this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Gateway.Spec.Service.HealthChecks.Interval?.ToTimeSpan() ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); } /// @@ -137,7 +132,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) var uri = $"{hostNameAndPort}{path}"; using var request = new HttpRequestMessage(new HttpMethod(this.Gateway.Spec.Service.HealthChecks.Request.Method), uri); if (this.Gateway.Spec.Service.HealthChecks.Request.Headers != null) this.Gateway.Spec.Service.HealthChecks.Request.Headers!.ToList().ForEach(h => request.Headers.TryAddWithoutValidation(h.Key, h.Value)); - if (this.Gateway.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(Serializer.Json.Serialize(this.Gateway.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); + if (this.Gateway.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(this.Serializer.SerializeToText(this.Gateway.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); HealthCheckResponse? healthCheckResponse = null; try { @@ -150,12 +145,12 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) { try { - healthCheckResponse = Serializer.Json.Deserialize(content)!; + healthCheckResponse = this.Serializer.Deserialize(content)!; } catch { } if(healthCheckResponse == null) { - var result = Serializer.Json.Deserialize(content); + var result = this.Serializer.Deserialize(content); if (result?.TryGetPropertyValue(nameof(HealthCheckResult.Status).ToCamelCase(), out var node) == true && node != null) healthCheckResponse = new(node.GetValue().ToCamelCase()); else healthCheckResponse = new(response.IsSuccessStatusCode ? HealthStatus.Healthy : HealthStatus.Unhealthy); } @@ -178,7 +173,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) patchTarget.Status ??= new GatewayStatus(); patchTarget.Status.HealthStatus = healthCheckResponse.Status; patchTarget.Status.LastHealthCheckAt = DateTimeOffset.Now; - var patch = new Patch(PatchType.JsonPatch, JsonPatchHelper.CreateJsonPatchFromDiff(patchSource, patchTarget)); + var patch = new Patch(PatchType.JsonPatch, JsonPatchUtility.CreateJsonPatchFromDiff(patchSource, patchTarget)); await this.Repository.PatchStatusAsync(patch, this.Gateway.GetName(), this.Gateway.GetNamespace(), false, this.CancellationTokenSource!.Token).ConfigureAwait(false); } } @@ -190,7 +185,7 @@ protected virtual async void OnHealthCheckIntervalEllapsedAsync(object? state) { if (this.Gateway.Spec.Service != null && this.Gateway.Spec.Service.HealthChecks != null) { - this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Gateway.Spec.Service.HealthChecks.Interval ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); + this.HealthCheckTimer = new Timer(this.OnHealthCheckIntervalEllapsedAsync, null, this.Gateway.Spec.Service.HealthChecks.Interval?.ToTimeSpan() ?? TimeSpan.FromSeconds(DefaultInterval), Timeout.InfiniteTimeSpan); } } } diff --git a/src/core/CloudStreams.Core.Api/Services/GatewayResourceController.cs b/src/core/CloudStreams.Core.Api/Services/GatewayResourceController.cs index 5fb0d54a..90ba77b8 100644 --- a/src/core/CloudStreams.Core.Api/Services/GatewayResourceController.cs +++ b/src/core/CloudStreams.Core.Api/Services/GatewayResourceController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,11 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; -using Hylo.Infrastructure.Configuration; -using Hylo.Infrastructure.Services; using Microsoft.Extensions.Options; +using Neuroglia.Data.Infrastructure.ResourceOriented.Configuration; using System.Collections.Concurrent; namespace CloudStreams.Core.Api.Services; @@ -23,21 +20,15 @@ namespace CloudStreams.Core.Api.Services; /// /// Represents a used to control s /// -public class GatewayResourceController - : ResourceController +/// +public class GatewayResourceController(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository) + : ResourceController(loggerFactory, controllerOptions, repository) { - /// - public GatewayResourceController(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions> controllerOptions, IRepository repository) - : base(loggerFactory, controllerOptions, repository) - { - this.ServiceProvider = serviceProvider; - } - /// /// Gets the current /// - protected IServiceProvider ServiceProvider { get; } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; /// /// Gets a containing key/health monitor mappings of managed gateways @@ -48,10 +39,7 @@ public GatewayResourceController(IServiceProvider serviceProvider, ILoggerFactor public override async Task StartAsync(CancellationToken cancellationToken) { await base.StartAsync(cancellationToken).ConfigureAwait(false); - foreach(var gateway in this.Resources.Values) - { - await this.OnResourceCreatedAsync(gateway, cancellationToken).ConfigureAwait(false); - } + foreach(var gateway in this.Resources.Values) await this.OnResourceCreatedAsync(gateway, cancellationToken).ConfigureAwait(false); } /// @@ -79,10 +67,7 @@ protected override async ValueTask DisposeAsync(bool disposing) { await base.DisposeAsync(disposing).ConfigureAwait(false); if (!disposing) return; - foreach(var kvp in this.HealthMonitors) - { - await kvp.Value.DisposeAsync().ConfigureAwait(false); - } + foreach(var kvp in this.HealthMonitors) await kvp.Value.DisposeAsync().ConfigureAwait(false); this.HealthMonitors.Clear(); } @@ -91,10 +76,7 @@ protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) return; - foreach (var kvp in this.HealthMonitors) - { - kvp.Value.Dispose(); - } + foreach (var kvp in this.HealthMonitors) kvp.Value.Dispose(); this.HealthMonitors.Clear(); } diff --git a/src/core/CloudStreams.Core.Api/Services/ResourceWatchEventHubController.cs b/src/core/CloudStreams.Core.Api/Services/ResourceWatchEventHubController.cs index bd12de9b..390f6861 100644 --- a/src/core/CloudStreams.Core.Api/Services/ResourceWatchEventHubController.cs +++ b/src/core/CloudStreams.Core.Api/Services/ResourceWatchEventHubController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -13,8 +13,6 @@ using CloudStreams.Core.Api.Client.Services; using CloudStreams.Core.Api.Hubs; -using Hylo; -using Hylo.Infrastructure.Services; using Microsoft.AspNetCore.SignalR; using System.Collections.Concurrent; @@ -23,30 +21,24 @@ namespace CloudStreams.Core.Api.Services; /// /// Represents a service used to dispatch s to all s /// -public class ResourceWatchEventHubController +/// +/// Initializes a new +/// +/// The service used to manage s +/// The current 's +public class ResourceWatchEventHubController(IRepository resources, IHubContext hubContext) : BackgroundService { - /// - /// Initializes a new - /// - /// The service used to manage s - /// The current 's - public ResourceWatchEventHubController(IRepository resources, IHubContext hubContext) - { - this.Resources = resources; - this.HubContext = hubContext; - } - /// /// Gets the service used to manage s /// - protected IRepository Resources { get; } + protected IRepository Resources { get; } = resources; /// /// Gets the current 's /// - protected IHubContext HubContext { get; } + protected IHubContext HubContext { get; } = hubContext; /// /// Gets a containing the mapping of active ^subscriptions per hub connection id @@ -76,7 +68,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) public virtual async Task WatchResourcesAsync(string connectionId, ResourceDefinitionInfo definition, string? @namespace = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(connectionId)) throw new ArgumentNullException(nameof(connectionId)); - if (definition == null) throw new ArgumentNullException(nameof(definition)); + ArgumentNullException.ThrowIfNull(definition); var subscriptionKey = this.GetSubscriptionKey(definition, @namespace); if (this.Connections.TryGetValue(connectionId, out var subscriptions) && subscriptions != null && subscriptions.TryGetValue(subscriptionKey, out var watch) && watch != null) return; if (subscriptions == null) @@ -108,8 +100,8 @@ public virtual async Task WatchResourcesAsync(string connectionId, ResourceDefin public virtual Task StopWatchingResourcesAsync(string connectionId, ResourceDefinitionInfo definition, string? @namespace = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(connectionId)) throw new ArgumentNullException(nameof(connectionId)); - if (definition == null) throw new ArgumentNullException(nameof(definition)); - if (!this.Connections.TryGetValue(connectionId, out var subscriptions) || subscriptions == null || !subscriptions.Any()) return Task.CompletedTask; + ArgumentNullException.ThrowIfNull(definition); + if (!this.Connections.TryGetValue(connectionId, out var subscriptions) || subscriptions == null || subscriptions.IsEmpty) return Task.CompletedTask; var subscriptionKey = this.GetSubscriptionKey(definition, @namespace); if (subscriptions.Remove(subscriptionKey, out var subscription) && subscription != null) subscription.Dispose(); return Task.CompletedTask; @@ -124,7 +116,7 @@ public virtual Task StopWatchingResourcesAsync(string connectionId, ResourceDefi public virtual Task ReleaseConnectionResourcesAsync(string connectionId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(connectionId)) throw new ArgumentNullException(nameof(connectionId)); - if (!this.Connections.Remove(connectionId, out var subscriptions) || subscriptions == null || !subscriptions.Any()) return Task.CompletedTask; + if (!this.Connections.Remove(connectionId, out var subscriptions) || subscriptions == null || subscriptions.IsEmpty) return Task.CompletedTask; subscriptions.Keys.ToList().ForEach(subscriptionId => { subscriptions.Remove(subscriptionId, out var subscription); diff --git a/src/core/CloudStreams.Core.Application/Services/YamlInputFormatter.cs b/src/core/CloudStreams.Core.Api/Services/YamlInputFormatter.cs similarity index 74% rename from src/core/CloudStreams.Core.Application/Services/YamlInputFormatter.cs rename to src/core/CloudStreams.Core.Api/Services/YamlInputFormatter.cs index 345b7a54..dd70382e 100644 --- a/src/core/CloudStreams.Core.Application/Services/YamlInputFormatter.cs +++ b/src/core/CloudStreams.Core.Api/Services/YamlInputFormatter.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,31 +11,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; using Microsoft.AspNetCore.Mvc.Formatters; +using Neuroglia.Serialization; using System.Text; -namespace CloudStreams.Core.Application.Services; +namespace CloudStreams.Core.Api.Services; /// /// Represents the used to deserialize YAML /// -public class YamlInputFormatter +public class YamlInputFormatter : TextInputFormatter { /// /// Initializes a new /// - public YamlInputFormatter() + /// The service used to serialize/deserialize objects to/from YAML + public YamlInputFormatter(IYamlSerializer serializer) { + this.Serializer = serializer; this.SupportedEncodings.Add(Encoding.UTF8); this.SupportedEncodings.Add(Encoding.Unicode); this.SupportedMediaTypes.Add("application/x-yaml"); this.SupportedMediaTypes.Add("text/yaml"); - this.SupportedMediaTypes.Add(CloudEventMediaTypeNames.CloudEventsYaml); } + /// + /// Gets the service used to serialize/deserialize objects to/from YAML + /// + protected IYamlSerializer Serializer { get; } + /// public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { @@ -46,10 +52,9 @@ public override async Task ReadRequestBodyAsync(InputForma await request.Body.CopyToAsync(stream); await stream.FlushAsync(); stream.Position = 0; - using var streamReader = new StreamReader(stream); try { - var model = Serializer.Yaml.Deserialize(streamReader, context.ModelType); + var model = this.Serializer.Deserialize(stream, context.ModelType); return await InputFormatterResult.SuccessAsync(model); } catch (Exception) diff --git a/src/core/CloudStreams.Core.Application/Services/YamlOutputFormatter.cs b/src/core/CloudStreams.Core.Api/Services/YamlOutputFormatter.cs similarity index 65% rename from src/core/CloudStreams.Core.Application/Services/YamlOutputFormatter.cs rename to src/core/CloudStreams.Core.Api/Services/YamlOutputFormatter.cs index 022204a4..a967f2d4 100644 --- a/src/core/CloudStreams.Core.Application/Services/YamlOutputFormatter.cs +++ b/src/core/CloudStreams.Core.Api/Services/YamlOutputFormatter.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,44 +11,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; using Microsoft.AspNetCore.Mvc.Formatters; -using System.Net.NetworkInformation; +using Neuroglia.Serialization; using System.Text; -namespace CloudStreams.Core.Application.Services; +namespace CloudStreams.Core.Api.Services; /// /// Represents the used to serialize YAML /// -public class YamlOutputFormatter +public class YamlOutputFormatter : TextOutputFormatter { /// /// Initializes a new /// - public YamlOutputFormatter() + /// The service used to serialize/deserialize objects to/from YAML + public YamlOutputFormatter(IYamlSerializer serializer) { + this.Serializer = serializer; this.SupportedEncodings.Add(Encoding.UTF8); this.SupportedEncodings.Add(Encoding.Unicode); this.SupportedMediaTypes.Add("application/x-yaml"); this.SupportedMediaTypes.Add("text/yaml"); - this.SupportedMediaTypes.Add(CloudEventMediaTypeNames.CloudEventsYaml); } + /// + /// Gets the service used to serialize/deserialize objects to/from YAML + /// + protected IYamlSerializer Serializer { get; } + /// public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (selectedEncoding == null) throw new ArgumentNullException(nameof(selectedEncoding)); + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(selectedEncoding); var response = context.HttpContext.Response; using var stream = new MemoryStream(); - using var streamWriter = new StreamWriter(stream); - Serializer.Yaml.Serialize(streamWriter, context.Object); - await streamWriter.FlushAsync(); + this.Serializer.Serialize(context.Object, stream); + await stream.FlushAsync().ConfigureAwait(false); stream.Position = 0; await stream.CopyToAsync(response.Body); } -} +} \ No newline at end of file diff --git a/src/broker/CloudStreams.Broker.Api.Server/Usings.cs b/src/core/CloudStreams.Core.Api/Usings.cs similarity index 53% rename from src/broker/CloudStreams.Broker.Api.Server/Usings.cs rename to src/core/CloudStreams.Core.Api/Usings.cs index 0cfd63b4..0eb90655 100644 --- a/src/broker/CloudStreams.Broker.Api.Server/Usings.cs +++ b/src/core/CloudStreams.Core.Api/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using CloudStreams.Core; -global using CloudStreams.Core.Data; -global using CloudStreams.Core.Infrastructure.Configuration; -global using MediatR; +global using CloudStreams.Core.Resources; global using Microsoft.AspNetCore.Mvc; +global using Neuroglia; +global using Neuroglia.Data; +global using Neuroglia.Data.Infrastructure.ResourceOriented; +global using Neuroglia.Data.Infrastructure.ResourceOriented.Services; +global using Neuroglia.Mediation; +global using Neuroglia.Mediation.AspNetCore; +global using Neuroglia.Reactive; +global using Neuroglia.Serialization; +global using System.Diagnostics; global using System.Net; +global using System.Net.Mime; +global using System.Reactive.Linq; \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Api/appsettings.Development.json b/src/core/CloudStreams.Core.Api/appsettings.Development.json new file mode 100644 index 00000000..6b06496b --- /dev/null +++ b/src/core/CloudStreams.Core.Api/appsettings.Development.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "eventstore": "esdb://localhost:2113?tls=false&tlsVerifyCert=false", + "redis": "localhost:6379" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/core/CloudStreams.Core.Api/appsettings.json b/src/core/CloudStreams.Core.Api/appsettings.json new file mode 100644 index 00000000..7b9ce52f --- /dev/null +++ b/src/core/CloudStreams.Core.Api/appsettings.json @@ -0,0 +1,44 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Plugins": { + "Sources": [ + { + "name": "event-store", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.EventSourcing.EventStore" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IEventStore, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + }, + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IProjectionManager, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + } + ] + } + }, + { + "name": "resource-database", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.ResourceOriented.Redis" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.ResourceOriented.Services.IDatabase, Neuroglia.Data.Infrastructure.ResourceOriented.Abstractions" + } + ] + } + } + ] + }, + "AllowedHosts": "*" +} diff --git a/src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj b/src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj index fc1be02a..4f4472c9 100644 --- a/src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj +++ b/src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj @@ -1,54 +1,32 @@ - + - - net7.0 - Library - enable + + net8.0 + enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;application - Contains CloudStreams Core application services, commands and queries - logo.png - Apache-2.0 - True - True - True + true - - - \ - True - - - \ - True - - - - - - - - - - - - + + + + - + diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/CreateResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/CreateResourceCommand.cs new file mode 100644 index 00000000..2a93c4f5 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/CreateResourceCommand.cs @@ -0,0 +1,85 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources; + +/// +/// Represents the command used to create a new +/// +public class CreateResourceCommand + : Command +{ + + /// + /// Initializes a new + /// + /// The resource to create + /// The API group the resource to create belongs to + /// The version of the resource to create + /// The plural name of the type of resource to create + /// A boolean indicating whether or not to persist the changes induces by the command + public CreateResourceCommand(IResource resource, string group, string version, string plural, bool dryRun) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + Resource = resource ?? throw new ArgumentNullException(nameof(resource)); + Group = group; + Version = version; + Plural = plural; + DryRun = dryRun; + } + + /// + /// Gets the resource to create + /// + public IResource Resource { get; } + + /// + /// Gets the API group the resource to create belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resource to create + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resource to create + /// + public string Plural { get; } + + /// + /// Gets a boolean indicating whether or not to persist the changes induces by the command + /// + public bool DryRun { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The service used to manage s +public class CreateResourceCommandHandler(IRepository repository) + : ICommandHandler +{ + + /// + public virtual async Task> HandleAsync(CreateResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.AddAsync(command.Resource, command.Group, command.Version, command.Plural, command.DryRun, cancellationToken); + return new OperationResult((int)HttpStatusCode.Created, resource); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/DeleteResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/DeleteResourceCommand.cs new file mode 100644 index 00000000..866197b0 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/DeleteResourceCommand.cs @@ -0,0 +1,93 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources; + +/// +/// Represents the used to delete an existing +/// +public class DeleteResourceCommand + : Command +{ + + /// + /// Initializes a new + /// + /// The API group the resource to delete belongs to + /// The version of the resource to delete + /// The plural name of the type of resource to delete + /// The name of the to delete + /// The namespace the to delete belongs to + /// A boolean indicating whether or not to persist changes + public DeleteResourceCommand(string group, string version, string plural, string name, string? @namespace, bool dryRun) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + Group = group; + Version = version; + Plural = plural; + Name = name; + Namespace = @namespace; + DryRun = dryRun; + } + + /// + /// Gets the API group the resource to delete belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resource to delete + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resource to delete + /// + public string Plural { get; } + + /// + /// Gets the name of the to delete + /// + public string Name { get; } + + /// + /// Gets the namespace the to delete belongs to + /// + public string? Namespace { get; } + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The service used to manage s +public class DeleteResourceCommandHandler(IRepository repository) + : ICommandHandler +{ + + /// + public virtual async Task> HandleAsync(DeleteResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.RemoveAsync(command.Group, command.Version, command.Plural, command.Name, command.Namespace, command.DryRun, cancellationToken).ConfigureAwait(false); + return this.Ok(resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/CreateResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/CreateResourceCommand.cs new file mode 100644 index 00000000..a252d5b3 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/CreateResourceCommand.cs @@ -0,0 +1,59 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources.Generic; + +/// +/// Represents the command used to create a new +/// +/// The type of to create +/// +/// Initializes a new +/// +/// The resource to create +/// A boolean indicating whether or not to persist the changes induces by the command +public class CreateResourceCommand(TResource resource, bool dryRun) + : Command + where TResource : class, IResource, new() +{ + + /// + /// Gets the resource to create + /// + public TResource Resource { get; } = resource ?? throw new ArgumentNullException(nameof(resource)); + + /// + /// Gets a boolean indicating whether or not to persist the changes induces by the command + /// + public bool DryRun { get; } = dryRun; + +} + +/// +/// Represents the service used to handle s +/// +/// The type of to create +/// The service used to manage s +public class CreateResourceCommandHandler(IRepository repository) + : ICommandHandler, TResource> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(CreateResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.AddAsync(command.Resource, command.DryRun, cancellationToken); + return new OperationResult((int)HttpStatusCode.Created, resource); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/DeleteResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/DeleteResourceCommand.cs new file mode 100644 index 00000000..7e018799 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/DeleteResourceCommand.cs @@ -0,0 +1,73 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources.Generic; + +/// +/// Represents the used to delete an existing +/// +/// The type of to create +public class DeleteResourceCommand + : Command + where TResource : class, IResource, new() +{ + + /// + /// Initializes a new + /// + /// The name of the to delete + /// The namespace the to delete belongs to + /// A boolean indicating whether or not to persist changes + public DeleteResourceCommand(string name, string? @namespace, bool dryRun) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + Name = name; + Namespace = @namespace; + DryRun = dryRun; + } + + /// + /// Gets the name of the to delete + /// + public string Name { get; } + + /// + /// Gets the namespace the to delete belongs to + /// + public string? Namespace { get; } + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The type of to create +/// The service used to manage s +public class DeleteResourceCommandHandler(IRepository repository) + : ICommandHandler, TResource> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(DeleteResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.RemoveAsync(command.Name, command.Namespace, command.DryRun, cancellationToken).ConfigureAwait(false); + return this.Ok(resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/PatchResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/PatchResourceCommand.cs new file mode 100644 index 00000000..1a756809 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/PatchResourceCommand.cs @@ -0,0 +1,80 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources.Generic; + +/// +/// Represents the used to delete an existing +/// +/// The type of to patch +public class PatchResourceCommand + : Command + where TResource : class, IResource, new() +{ + + /// + /// Initializes a new + /// + /// The name of the to patch + /// The namespace the to patch belongs to + /// The patch to apply + /// A boolean indicating whether or not to persist changes + public PatchResourceCommand(string name, string? @namespace, Patch patch, bool dryRun) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + Name = name; + Namespace = @namespace; + Patch = patch ?? throw new ArgumentNullException(nameof(patch)); + DryRun = dryRun; + } + + /// + /// Gets the name of the to patch + /// + public string Name { get; } + + /// + /// Gets the name of the to patch + /// + public string? Namespace { get; } + + /// + /// Gets the patch to apply + /// + public Patch Patch { get; } + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The type of to patch +/// The service used to manage s +public class PatchResourceCommandHandler(IRepository repository) + : ICommandHandler, TResource> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(PatchResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.PatchAsync(command.Patch, command.Name, command.Namespace, command.DryRun, cancellationToken).ConfigureAwait(false); + return this.Ok(resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/ReplaceResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/ReplaceResourceCommand.cs new file mode 100644 index 00000000..dca62cd9 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/Generic/ReplaceResourceCommand.cs @@ -0,0 +1,60 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources.Generic; + +/// +/// Represents the used to replace an existing +/// +/// The type of to replace +/// +/// Initializes a new +/// +/// The updated to replace +/// A boolean indicating whether or not to persist changes +public class ReplaceResourceCommand(TResource resource, bool dryRun) + : Command + where TResource : class, IResource, new() +{ + + /// + /// Gets the updated to replace + /// + public TResource Resource { get; } = resource; + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } = dryRun; + +} + + +/// +/// Represents the service used to handle s +/// +/// The type of to replace +/// The service used to manage s +public class ReplaceResourceCommandHandler(IRepository repository) + : ICommandHandler, TResource> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(ReplaceResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.ReplaceAsync(command.Resource, command.DryRun, cancellationToken).ConfigureAwait(false); + return new OperationResult((int)HttpStatusCode.OK, resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/PatchResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/PatchResourceCommand.cs new file mode 100644 index 00000000..7648823f --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/PatchResourceCommand.cs @@ -0,0 +1,105 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources; + +/// +/// Represents the used to delete an existing +/// +public class PatchResourceCommand + : Command +{ + + /// + /// Initializes a new + /// + protected PatchResourceCommand() { } + + /// + /// Initializes a new + /// + /// The API group the resource to patch belongs to + /// The version of the resource to patch + /// The plural name of the type of resource to patch + /// The name of the to patch + /// The namespace the to patch belongs to + /// The patch to apply + /// A boolean indicating whether or not to persist changes + public PatchResourceCommand(string group, string version, string plural, string name, string? @namespace, Patch patch, bool dryRun) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + Group = group; + Version = version; + Plural = plural; + Name = name; + Namespace = @namespace; + Patch = patch ?? throw new ArgumentNullException(nameof(patch)); + DryRun = dryRun; + } + + /// + /// Gets the API group the resource to patch belongs to + /// + public string Group { get; } = null!; + + /// + /// Gets the version of the resource to patch + /// + public string Version { get; } = null!; + + /// + /// Gets the plural name of the type of resource to patch + /// + public string Plural { get; } = null!; + + /// + /// Gets the name of the to patch + /// + public string Name { get; } = null!; + + /// + /// Gets the name of the to patch + /// + public string? Namespace { get; } + + /// + /// Gets the patch to apply + /// + public Patch Patch { get; } = null!; + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The service used to manage s +public class PatchResourceCommandHandler(IRepository repository) + : ICommandHandler +{ + + /// + public virtual async Task> HandleAsync(PatchResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.PatchAsync(command.Patch, command.Group, command.Version, command.Plural, command.Name, command.Namespace, command.DryRun, cancellationToken).ConfigureAwait(false); + return this.Ok(resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Commands/Resources/ReplaceResourceCommand.cs b/src/core/CloudStreams.Core.Application/Commands/Resources/ReplaceResourceCommand.cs new file mode 100644 index 00000000..073ce0b0 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Commands/Resources/ReplaceResourceCommand.cs @@ -0,0 +1,86 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Commands.Resources; + +/// +/// Represents the used to replace an existing +/// +public class ReplaceResourceCommand + : Command +{ + + /// + /// Initializes a new + /// + /// The API group the resource to replace belongs to + /// The version of the resource to replace + /// The plural name of the type of resource to replace + /// The updated to replace + /// A boolean indicating whether or not to persist changes + public ReplaceResourceCommand(string group, string version, string plural, IResource resource, bool dryRun) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + Group = group; + Version = version; + Plural = plural; + Resource = resource; + DryRun = dryRun; + } + + /// + /// Gets the API group the resource to replace belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resource to replace + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resource to replace + /// + public string Plural { get; } + + /// + /// Gets the updated to replace + /// + public IResource Resource { get; } + + /// + /// Gets a boolean indicating whether or not to persist changes + /// + public bool DryRun { get; } + +} + + +/// +/// Represents the service used to handle s +/// +/// The service used to manage s +public class ReplaceResourceCommandHandler(IRepository repository) + : ICommandHandler +{ + + /// + public virtual async Task> HandleAsync(ReplaceResourceCommand command, CancellationToken cancellationToken) + { + var resource = await repository.ReplaceAsync(command.Resource, command.Group, command.Version, command.Plural, command.DryRun, cancellationToken).ConfigureAwait(false); + return new OperationResult((int)HttpStatusCode.OK, resource); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Extensions/CloudEventRecordExtensions.cs b/src/core/CloudStreams.Core.Application/Extensions/CloudEventRecordExtensions.cs similarity index 57% rename from src/core/CloudStreams.Core/Extensions/CloudEventRecordExtensions.cs rename to src/core/CloudStreams.Core.Application/Extensions/CloudEventRecordExtensions.cs index 7dab7450..0133af47 100644 --- a/src/core/CloudStreams.Core/Extensions/CloudEventRecordExtensions.cs +++ b/src/core/CloudStreams.Core.Application/Extensions/CloudEventRecordExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia.Serialization.Json; +using System.Text.Json.Nodes; -namespace CloudStreams.Core; +namespace CloudStreams.Core.Application; /// /// Defines extensions for s @@ -21,6 +22,20 @@ namespace CloudStreams.Core; public static class CloudEventRecordExtensions { + /// + /// Converts the into the it describes + /// + /// The to convert + /// The described by the converted + public static CloudEvent ToCloudEvent(this CloudEventDescriptor descriptor) + { + ArgumentNullException.ThrowIfNull(descriptor); + var e = (JsonObject)JsonSerializer.Default.SerializeToNode(descriptor.Metadata.ContextAttributes)!; + var data = JsonSerializer.Default.SerializeToNode(descriptor.Data); + e[CloudEventAttributes.Data] = data; + return JsonSerializer.Default.Deserialize(e)!; + } + /// /// Converts the into the it describes /// @@ -29,15 +44,13 @@ public static class CloudEventRecordExtensions /// The described by the converted public static CloudEvent ToCloudEvent(this CloudEventRecord record, CloudEventSequencingConfiguration? sequencingConfiguration) { - if (record == null) throw new ArgumentNullException(nameof(record)); + ArgumentNullException.ThrowIfNull(record); sequencingConfiguration ??= CloudEventSequencingConfiguration.Default; var e = record.ToCloudEvent(); if (sequencingConfiguration.Strategy == CloudEventSequencingStrategy.None) return e; e.ExtensionAttributes ??= new Dictionary(); - if (e.ExtensionAttributes.ContainsKey(sequencingConfiguration.AttributeName!) && sequencingConfiguration.AttributeConflictResolution == CloudEventAttributeConflictResolution.Fallback) - e.ExtensionAttributes[sequencingConfiguration.FallbackAttributeName!] = record.Sequence; - else - e.ExtensionAttributes[sequencingConfiguration.AttributeName!] = record.Sequence; + if (e.ExtensionAttributes.ContainsKey(sequencingConfiguration.AttributeName!) && sequencingConfiguration.AttributeConflictResolution == CloudEventAttributeConflictResolution.Fallback) e.ExtensionAttributes[sequencingConfiguration.FallbackAttributeName!] = record.Sequence; + else e.ExtensionAttributes[sequencingConfiguration.AttributeName!] = record.Sequence; return e; } diff --git a/src/core/CloudStreams.Core.Infrastructure/Extensions/IEventStoreExtensions.cs b/src/core/CloudStreams.Core.Application/Extensions/ICloudEventStoreExtensions.cs similarity index 70% rename from src/core/CloudStreams.Core.Infrastructure/Extensions/IEventStoreExtensions.cs rename to src/core/CloudStreams.Core.Application/Extensions/ICloudEventStoreExtensions.cs index c6e034eb..ebbecb63 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Extensions/IEventStoreExtensions.cs +++ b/src/core/CloudStreams.Core.Application/Extensions/ICloudEventStoreExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,27 +11,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Infrastructure.Services; +using CloudStreams.Core.Application.Services; +using Neuroglia.Data.Infrastructure.EventSourcing; -namespace CloudStreams.Core.Infrastructure; +namespace CloudStreams.Core.Application; /// -/// Defines extensions for s +/// Defines extensions for s /// -public static class IEventStoreExtensions +public static class ICloudEventStoreExtensions { /// /// Reads a single event from the stream /// - /// The to read the event from + /// The to read the event from /// The direction in which to read the stream /// The offset of the event to read /// A /// The at the specified offset - public static async Task ReadOneAsync(this IEventStore events, StreamReadDirection direction, long offset, CancellationToken cancellationToken = default) + public static async Task ReadOneAsync(this ICloudEventStore events, StreamReadDirection direction, long offset, CancellationToken cancellationToken = default) { return await events.ReadAsync(direction, offset, 1, cancellationToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Extensions/IServiceCollectionExtensions.cs b/src/core/CloudStreams.Core.Application/Extensions/IServiceCollectionExtensions.cs index e474a2c2..9f9f5dd5 100644 --- a/src/core/CloudStreams.Core.Application/Extensions/IServiceCollectionExtensions.cs +++ b/src/core/CloudStreams.Core.Application/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,12 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using Hylo; -using Hylo.Api.Application; -using Hylo.Api.Application.Commands.Resources.Generic; -using Hylo.Api.Application.Queries.Resources.Generic; -using Hylo.Infrastructure.Services; +using CloudStreams.Core.Application.Commands.Resources.Generic; +using CloudStreams.Core.Application.Queries.Resources.Generic; +using Microsoft.Extensions.DependencyInjection; namespace CloudStreams.Core.Application; @@ -31,9 +28,9 @@ public static class IServiceCollectionExtensions /// /// The to configure /// The configured - public static IServiceCollection AddGenericQueryHandlers(this IServiceCollection services) + public static IServiceCollection AddCoreApiQueries(this IServiceCollection services) { - foreach (Type queryableType in typeof(Broker).Assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && typeof(Hylo.Resource).IsAssignableFrom(t))) + foreach (Type queryableType in typeof(Broker).Assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && typeof(Resource).IsAssignableFrom(t))) { var serviceLifetime = ServiceLifetime.Scoped; Type queryType; @@ -42,32 +39,32 @@ public static IServiceCollection AddGenericQueryHandlers(this IServiceCollection Type handlerImplementationType; queryType = typeof(GetResourceQuery<>).MakeGenericType(queryableType); - resultType = typeof(ApiResponse<>).MakeGenericType(queryableType); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(queryType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(queryableType); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(queryType, resultType); handlerImplementationType = typeof(GetResourceQueryHandler<>).MakeGenericType(queryableType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); queryType = typeof(GetResourceDefinitionQuery<>).MakeGenericType(queryableType); - resultType = typeof(ApiResponse); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(queryType, resultType); + resultType = typeof(IOperationResult); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(queryType, resultType); handlerImplementationType = typeof(GetResourceDefinitionQueryHandler<>).MakeGenericType(queryableType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); queryType = typeof(GetResourcesQuery<>).MakeGenericType(queryableType); - resultType = typeof(ApiResponse<>).MakeGenericType(typeof(IAsyncEnumerable<>).MakeGenericType(queryableType)); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(queryType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(typeof(IAsyncEnumerable<>).MakeGenericType(queryableType)); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(queryType, resultType); handlerImplementationType = typeof(GetResourcesQueryHandler<>).MakeGenericType(queryableType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); queryType = typeof(ListResourcesQuery<>).MakeGenericType(queryableType); - resultType = typeof(ApiResponse<>).MakeGenericType(typeof(Hylo.ICollection<>).MakeGenericType(queryableType)); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(queryType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(typeof(Neuroglia.Data.Infrastructure.ResourceOriented.ICollection<>).MakeGenericType(queryableType)); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(queryType, resultType); handlerImplementationType = typeof(ListResourcesQueryHandler<>).MakeGenericType(queryableType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); queryType = typeof(WatchResourcesQuery<>).MakeGenericType(queryableType); - resultType = typeof(ApiResponse<>).MakeGenericType(typeof(IResourceWatch<>).MakeGenericType(queryableType)); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(queryType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(typeof(IResourceWatch<>).MakeGenericType(queryableType)); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(queryType, resultType); handlerImplementationType = typeof(WatchResourcesQueryHandler<>).MakeGenericType(queryableType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); } @@ -79,9 +76,9 @@ public static IServiceCollection AddGenericQueryHandlers(this IServiceCollection /// /// The to configure /// The configured - public static IServiceCollection AddGenericCommandHandlers(this IServiceCollection services) + public static IServiceCollection AddCoreApiCommands(this IServiceCollection services) { - foreach (Type resourceType in typeof(Broker).Assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && typeof(Hylo.Resource).IsAssignableFrom(t))) + foreach (Type resourceType in typeof(Broker).Assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && typeof(Resource).IsAssignableFrom(t))) { var serviceLifetime = ServiceLifetime.Scoped; Type commandType; @@ -90,26 +87,26 @@ public static IServiceCollection AddGenericCommandHandlers(this IServiceCollecti Type handlerImplementationType; commandType = typeof(CreateResourceCommand<>).MakeGenericType(resourceType); - resultType = typeof(ApiResponse<>).MakeGenericType(resourceType); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(commandType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(resourceType); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(commandType, resultType); handlerImplementationType = typeof(CreateResourceCommandHandler<>).MakeGenericType(resourceType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); commandType = typeof(ReplaceResourceCommand<>).MakeGenericType(resourceType); - resultType = typeof(ApiResponse<>).MakeGenericType(resourceType); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(commandType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(resourceType); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(commandType, resultType); handlerImplementationType = typeof(ReplaceResourceCommandHandler<>).MakeGenericType(resourceType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); commandType = typeof(PatchResourceCommand<>).MakeGenericType(resourceType); - resultType = typeof(ApiResponse<>).MakeGenericType(resourceType); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(commandType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(resourceType); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(commandType, resultType); handlerImplementationType = typeof(PatchResourceCommandHandler<>).MakeGenericType(resourceType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); commandType = typeof(DeleteResourceCommand<>).MakeGenericType(resourceType); - resultType = typeof(ApiResponse<>).MakeGenericType(resourceType); - handlerServiceType = typeof(MediatR.IRequestHandler<,>).MakeGenericType(commandType, resultType); + resultType = typeof(IOperationResult<>).MakeGenericType(resourceType); + handlerServiceType = typeof(IRequestHandler<,>).MakeGenericType(commandType, resultType); handlerImplementationType = typeof(DeleteResourceCommandHandler<>).MakeGenericType(resourceType); services.Add(new ServiceDescriptor(handlerServiceType, handlerImplementationType, serviceLifetime)); diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/CloudEventPartitionRefExtensions.cs b/src/core/CloudStreams.Core.Application/Extensions/PartitionReferenceExtensions.cs similarity index 63% rename from src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/CloudEventPartitionRefExtensions.cs rename to src/core/CloudStreams.Core.Application/Extensions/PartitionReferenceExtensions.cs index 9cb060bb..6fd07944 100644 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/CloudEventPartitionRefExtensions.cs +++ b/src/core/CloudStreams.Core.Application/Extensions/PartitionReferenceExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,14 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - -namespace CloudStreams.Core.Infrastructure; +namespace CloudStreams.Core.Application; /// /// Defines extensions for s /// -public static class CloudEventPartitionRefExtensions +public static class PartitionReferenceExtensions { /// @@ -30,11 +28,11 @@ public static string GetStreamName(this PartitionReference partition) { return partition.Type switch { - CloudEventPartitionType.BySource => EventStoreStreams.ByCloudEventSource(new(partition.Id)), - CloudEventPartitionType.BySubject => EventStoreStreams.ByCloudEventSubject(partition.Id), - CloudEventPartitionType.ByType => EventStoreStreams.ByCloudEventType(partition.Id), - CloudEventPartitionType.ByCorrelationId => EventStoreStreams.ByCorrelationId(partition.Id), - CloudEventPartitionType.ByCausationId => EventStoreStreams.ByCausationId(partition.Id), + CloudEventPartitionType.BySource => Streams.ByCloudEventSource(new(partition.Id)), + CloudEventPartitionType.BySubject => Streams.ByCloudEventSubject(partition.Id), + CloudEventPartitionType.ByType => Streams.ByCloudEventType(partition.Id), + CloudEventPartitionType.ByCorrelationId => Streams.ByCorrelationId(partition.Id), + CloudEventPartitionType.ByCausationId => Streams.ByCausationId(partition.Id), _ => throw new NotSupportedException($"The specified partition type '{partition.Type}' is not supported") }; } diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreProjections.cs b/src/core/CloudStreams.Core.Application/Projections.cs similarity index 93% rename from src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreProjections.cs rename to src/core/CloudStreams.Core.Application/Projections.cs index 935ede16..54bc7d3f 100644 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreProjections.cs +++ b/src/core/CloudStreams.Core.Application/Projections.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,14 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - -namespace CloudStreams.Core.Infrastructure; +namespace CloudStreams; /// /// Exposes constants about the EventStore projections used by Cloud Streams /// -public static class EventStoreProjections +public static class Projections { /// @@ -70,4 +68,4 @@ public static class BuiltInProjections } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Properties/launchSettings.json b/src/core/CloudStreams.Core.Application/Properties/launchSettings.json deleted file mode 100644 index d4103829..00000000 --- a/src/core/CloudStreams.Core.Application/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "CloudStreams.Core.Application": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:61078;http://localhost:61079" - } - } -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Gateways/CheckGatewayHealthQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Gateways/CheckGatewayHealthQuery.cs index b49948d8..df304a35 100644 --- a/src/core/CloudStreams.Core.Application/Queries/Gateways/CheckGatewayHealthQuery.cs +++ b/src/core/CloudStreams.Core.Application/Queries/Gateways/CheckGatewayHealthQuery.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,13 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Properties; -using Hylo; -using Hylo.Api.Application; -using Hylo.Infrastructure.Services; +using Neuroglia.Serialization; using System.Diagnostics; -using System.Net; using System.Net.Mime; using System.Text; using System.Text.Json.Nodes; @@ -25,10 +20,10 @@ namespace CloudStreams.Core.Application.Queries.Gateways; /// -/// Represents the used to check the health of a +/// Represents the used to check the health of a /// public class CheckGatewayHealthQuery - : IQuery + : Query { /// @@ -55,47 +50,38 @@ public CheckGatewayHealthQuery(string name) /// /// Represents the service used to handle instances /// -public class CheckGatewayHealthQueryHandler +public class CheckGatewayHealthQueryHandler(IRepository repository, HttpClient httpClient, IJsonSerializer serializer) : IQueryHandler { - readonly IRepository _repository; - readonly HttpClient _httpClient; - /// - public CheckGatewayHealthQueryHandler(IRepository repository, HttpClient httpClient) - { - this._repository = repository; - this._httpClient = httpClient; - } - - async Task> MediatR.IRequestHandler>.Handle(CheckGatewayHealthQuery query, CancellationToken cancellationToken) + public virtual async Task> HandleAsync(CheckGatewayHealthQuery query, CancellationToken cancellationToken) { - var gateway = await this._repository.GetAsync(query.Name, null, cancellationToken).ConfigureAwait(false); - if (gateway == null) return new(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound); + var gateway = await repository.GetAsync(query.Name, null, cancellationToken).ConfigureAwait(false); + if (gateway == null) return this.NotFound(); if (gateway.Spec.Service == null || gateway.Spec.Service.HealthChecks == null) return this.Ok(null); using var request = new HttpRequestMessage(new HttpMethod(gateway.Spec.Service.HealthChecks.Request.Method), $"{gateway.Spec.Service.Uri.OriginalString}{gateway.Spec.Service.HealthChecks.Request.Path}"); if (gateway.Spec.Service.HealthChecks.Request.Headers != null) gateway.Spec.Service.HealthChecks.Request.Headers!.ToList().ForEach(h => request.Headers.TryAddWithoutValidation(h.Key, h.Value)); - if (gateway.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(Serializer.Json.Serialize(gateway.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); + if (gateway.Spec.Service.HealthChecks.Request.Body != null) request.Content = new StringContent(serializer.SerializeToText(gateway.Spec.Service.HealthChecks.Request.Body), Encoding.UTF8, MediaTypeNames.Application.Json); HealthCheckResponse? healthCheckResponse = null; try { var stopwatch = new Stopwatch(); stopwatch.Start(); - using var response = await this._httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); stopwatch.Stop(); var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); try { try { - healthCheckResponse = Serializer.Json.Deserialize(content)!; + healthCheckResponse = serializer.Deserialize(content)!; } catch { } if (healthCheckResponse == null) { - var result = Serializer.Json.Deserialize(content); - if (result?.TryGetPropertyValue(nameof(Data.HealthCheckResult.Status).ToCamelCase(), out var node) == true && node != null) healthCheckResponse = new(node.GetValue()); + var result = serializer.Deserialize(content); + if (result?.TryGetPropertyValue(nameof(HealthCheckResult.Status).ToCamelCase(), out var node) == true && node != null) healthCheckResponse = new(node.GetValue()); else healthCheckResponse = new(response.IsSuccessStatusCode ? HealthStatus.Healthy : HealthStatus.Unhealthy); } } diff --git a/src/core/CloudStreams.Core.Application/Queries/Partitions/GetEventPartitionMetadataQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Partitions/GetEventPartitionMetadataQuery.cs index fe3b5b95..65a87944 100644 --- a/src/core/CloudStreams.Core.Application/Queries/Partitions/GetEventPartitionMetadataQuery.cs +++ b/src/core/CloudStreams.Core.Application/Queries/Partitions/GetEventPartitionMetadataQuery.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure.Services; -using Hylo.Api.Application; +using CloudStreams.Core.Application.Services; using System.ComponentModel.DataAnnotations; namespace CloudStreams.Core.Application.Queries.Partitions; @@ -21,45 +19,33 @@ namespace CloudStreams.Core.Application.Queries.Partitions; /// /// Represents the used to get the metadata of the application's cloud event stream /// -public class GetEventPartitionMetadataQuery - : IQuery +/// +/// Initializes a new +/// +/// The cloud event partition to get metadata for +public class GetEventPartitionMetadataQuery(PartitionReference partition) + : Query { - /// - /// Initializes a new - /// - /// The cloud event partition to get metadata for - public GetEventPartitionMetadataQuery(PartitionReference partition) - { - this.Partition = partition; - } - /// /// Gets the cloud event partition to get metadata for /// [Required] - public virtual PartitionReference Partition { get; protected set; } + public virtual PartitionReference Partition { get; protected set; } = partition; } /// /// Represents the service used to handle instances /// -public class GetEventStreamMetadataQueryHandler +public class GetEventStreamMetadataQueryHandler(ICloudEventStore eventStore) : IQueryHandler { /// - public GetEventStreamMetadataQueryHandler(IEventStoreProvider eventStoreProvider) - { - this._eventStoreProvider = eventStoreProvider; - } - - IEventStoreProvider _eventStoreProvider; - - async Task> MediatR.IRequestHandler>.Handle(GetEventPartitionMetadataQuery query, CancellationToken cancellationToken) + public virtual async Task> HandleAsync(GetEventPartitionMetadataQuery query, CancellationToken cancellationToken) { - return this.Ok(await this._eventStoreProvider.GetEventStore().GetPartitionMetadataAsync(query.Partition, cancellationToken)); + return this.Ok(await eventStore.GetPartitionMetadataAsync(query.Partition, cancellationToken)); } } diff --git a/src/core/CloudStreams.Core.Application/Queries/Partitions/ListEventPartitionIdsQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Partitions/ListEventPartitionIdsQuery.cs index 153abef8..92eff9ba 100644 --- a/src/core/CloudStreams.Core.Application/Queries/Partitions/ListEventPartitionIdsQuery.cs +++ b/src/core/CloudStreams.Core.Application/Queries/Partitions/ListEventPartitionIdsQuery.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Infrastructure; -using CloudStreams.Core.Infrastructure.Services; -using Hylo.Api.Application; +using CloudStreams.Core.Application.Services; using System.ComponentModel.DataAnnotations; namespace CloudStreams.Core.Application.Queries.Partitions; @@ -21,45 +19,33 @@ namespace CloudStreams.Core.Application.Queries.Partitions; /// /// Represents the used to list the ids of event partitions /// -public class ListEventPartitionIdsQuery - : IQuery> +/// +/// Initializes a new +/// +/// The type of partitions to list the ids of +public class ListEventPartitionIdsQuery(CloudEventPartitionType partitionType) + : Query> { - /// - /// Initializes a new - /// - /// The type of partitions to list the ids of - public ListEventPartitionIdsQuery(CloudEventPartitionType partitionType) - { - this.PartitionType = partitionType; - } - /// /// Gets the type of partitions to list the ids of /// [Required] - public virtual CloudEventPartitionType PartitionType { get; set; } + public virtual CloudEventPartitionType PartitionType { get; set; } = partitionType; } /// /// Represents the service used to handle instances /// -public class ListEventPartitionIdsQueryHandler +public class ListEventPartitionIdsQueryHandler(ICloudEventStore eventStore) : IQueryHandler> { /// - public ListEventPartitionIdsQueryHandler(IEventStoreProvider eventStoreProvider) - { - this._eventStoreProvider = eventStoreProvider; - } - - IEventStoreProvider _eventStoreProvider; - - Task>> MediatR.IRequestHandler>>.Handle(ListEventPartitionIdsQuery query, CancellationToken cancellationToken) + public virtual Task>> HandleAsync(ListEventPartitionIdsQuery query, CancellationToken cancellationToken) { - return Task.FromResult(this.Ok(this._eventStoreProvider.GetEventStore().ListPartitionIdsAsync(query.PartitionType, cancellationToken))); + return Task.FromResult(this.Ok(eventStore.ListPartitionIdsAsync(query.PartitionType, cancellationToken))); } } diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceDefinitionQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceDefinitionQuery.cs new file mode 100644 index 00000000..a1ac5fb4 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceDefinitionQuery.cs @@ -0,0 +1,46 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources.Generic; + +/// +/// Represents the used to get the definition of the specified type +/// +/// The type of the to get the definition of +public class GetResourceDefinitionQuery + : Query + where TResource : class, IResource, new() +{ + + + +} + +/// +/// Represents the service used to handle s +/// +/// The type of the to replace +/// The service used to manage s +public class GetResourceDefinitionQueryHandler(IRepository repository) + : IQueryHandler, IResourceDefinition> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(GetResourceDefinitionQuery query, CancellationToken cancellationToken) + { + var resourceDefinition = await repository.GetDefinitionAsync(cancellationToken).ConfigureAwait(false); + return this.Ok(resourceDefinition); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceQuery.cs new file mode 100644 index 00000000..70feebf8 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourceQuery.cs @@ -0,0 +1,65 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources.Generic; + +/// +/// Represents the used to get an existing +/// +/// The type of the to get +public class GetResourceQuery + : Query + where TResource : class, IResource, new() +{ + + /// + /// Initializes a new + /// + /// The name of the to get + /// The namespace the to get belongs to + public GetResourceQuery(string name, string? @namespace) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + this.Namespace = @namespace; + } + + /// + /// Gets the name of the to get + /// + public string Name { get; } + + /// + /// Gets the namespace the to get belongs to + /// + public string? Namespace { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The type of the to replace +/// The service used to manage s +public class GetResourceQueryHandler(IRepository repository) + : IQueryHandler, TResource> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task> HandleAsync(GetResourceQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.GetAsync(query.Name, query.Namespace, cancellationToken).ConfigureAwait(false)); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourcesQuery.cs new file mode 100644 index 00000000..37594070 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/GetResourcesQuery.cs @@ -0,0 +1,58 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources.Generic; + +/// +/// Represents the used to retrieves s of the specified type +/// +/// +/// Initializes a new +/// +/// The namespace the s to get belong to, if any +/// A containing the s used to filter s by +public class GetResourcesQuery(string? @namespace, IEnumerable? labelSelectors) + : Query> + where TResource : class, IResource, new() +{ + + /// + /// Gets the namespace the s to get belong to, if any + /// + public string? Namespace { get; } = @namespace; + + /// + /// Gets a containing the s used to filter s by + /// + public IEnumerable? LabelSelectors { get; } = labelSelectors; + +} + +/// +/// Represents the service used to handle instances +/// +/// The type of to get +/// The service used to manage s +public class GetResourcesQueryHandler(IRepository repository) + : IQueryHandler, IAsyncEnumerable> + where TResource : class, IResource, new() +{ + + + /// + public virtual Task>> HandleAsync(GetResourcesQuery query, CancellationToken cancellationToken) + { + return Task.FromResult(this.Ok(repository.GetAllAsync(query.Namespace, query.LabelSelectors, cancellationToken))); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/ListResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/ListResourcesQuery.cs new file mode 100644 index 00000000..a32745cf --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/ListResourcesQuery.cs @@ -0,0 +1,70 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources.Generic; + +/// +/// Represents the used to list all s +/// +/// The type of s to list +/// +/// Initializes a new +/// +/// The namespace the s to get belong to, if any +/// A containing the s used to filter s by +/// The maximum amount of results to list +/// The token, if any, used to continue enumerating the results +public class ListResourcesQuery(string? @namespace, IEnumerable? labelSelectors, ulong? maxResults, string? continuationToken) + : Query> + where TResource : class, IResource, new() +{ + + /// + /// Gets the namespace the s to get belong to, if any + /// + public string? Namespace { get; } = @namespace; + + /// + /// Gets a containing the s used to filter s by + /// + public IEnumerable? LabelSelectors { get; } = labelSelectors; + + /// + /// Gets the maximum amount of results to list + /// + public ulong? MaxResults { get; } = maxResults; + + /// + /// Gets the token, if any, used to continue enumerating the results + /// + public string? ContinuationToken { get; } = continuationToken; + +} + +/// +/// Represents the service used to handle instances +/// +/// The type of s to list +/// The service used to manage s +public class ListResourcesQueryHandler(IRepository repository) + : IQueryHandler, Neuroglia.Data.Infrastructure.ResourceOriented.ICollection> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task>> HandleAsync(ListResourcesQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.ListAsync(query.Namespace, query.LabelSelectors, query.MaxResults, query.ContinuationToken, cancellationToken).ConfigureAwait(false)); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/WatchResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/WatchResourcesQuery.cs new file mode 100644 index 00000000..ff61a586 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/Generic/WatchResourcesQuery.cs @@ -0,0 +1,57 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources.Generic; + +/// +/// Represents the used to retrieves s of the specified type +/// +/// +/// Initializes a new +/// +/// The namespace the s to get belong to, if any +/// A containing the s used to filter s by +public class WatchResourcesQuery(string? @namespace, IEnumerable? labelSelectors) + : Query> + where TResource : class, IResource, new() +{ + + /// + /// Gets the namespace the s to get belong to, if any + /// + public string? Namespace { get; } = @namespace; + + /// + /// Gets a containing the s used to filter s by + /// + public IEnumerable? LabelSelectors { get; } = labelSelectors; + +} + +/// +/// Represents the service used to handle instances +/// +/// The type of to get +/// The service used to manage s +public class WatchResourcesQueryHandler(IRepository repository) + : IQueryHandler, IResourceWatch> + where TResource : class, IResource, new() +{ + + /// + public virtual async Task>> HandleAsync(WatchResourcesQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.WatchAsync(query.Namespace, query.LabelSelectors, cancellationToken).ConfigureAwait(false)); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourceQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourceQuery.cs new file mode 100644 index 00000000..3f98c466 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourceQuery.cs @@ -0,0 +1,85 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources; + +/// +/// Represents the used to get an existing +/// +public class GetResourceQuery + : Query +{ + + /// + /// Initializes a new + /// + /// The API group the resource to get belongs to + /// The version of the resource to get + /// The plural name of the type of resource to get + /// The name of the to get + /// The namespace the to get belongs to + public GetResourceQuery(string group, string version, string plural, string name, string? @namespace) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); + this.Group = group; + this.Version = version; + this.Plural = plural; + this.Name = name; + this.Namespace = @namespace; + } + + /// + /// Gets the API group the resource to get belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resource to get + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resource to get + /// + public string Plural { get; } + + /// + /// Gets the name of the to get + /// + public string Name { get; } + + /// + /// Gets the namespace the to get belongs to + /// + public string? Namespace { get; } + +} + +/// +/// Represents the service used to handle s +/// +/// The service used to manage s +public class GetResourceQueryHandler(IRepository repository) + : IQueryHandler +{ + + /// + public virtual async Task> HandleAsync(GetResourceQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.GetAsync(query.Group, query.Version, query.Plural, query.Name, query.Namespace, cancellationToken).ConfigureAwait(false)); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourcesQuery.cs new file mode 100644 index 00000000..53587af9 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/GetResourcesQuery.cs @@ -0,0 +1,84 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources; + +/// +/// Represents the used to retrieves s of the specified type +/// +public class GetResourcesQuery + : Query> +{ + + /// + /// Initializes a new + /// + /// The API group the resources to get belongs to + /// The version of the resources to get + /// The plural name of the type of resources to get + /// The namespace the s to get belong to, if any + /// A containing the s used to filter s by + public GetResourcesQuery(string group, string version, string plural, string? @namespace, List? labelSelectors) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + this.Group = group; + this.Version = version; + this.Plural = plural; + this.Namespace = @namespace; + this.LabelSelectors = labelSelectors; + } + + /// + /// Gets the API group the resources to get belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resources to get + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resources to get + /// + public string Plural { get; } + + /// + /// Gets the namespace the s to get belong to, if any + /// + public string? Namespace { get; } + + /// + /// Gets a containing the s used to filter s by + /// + public List? LabelSelectors { get; } + +} + +/// +/// Represents the service used to handle instances +/// +/// The service used to manage s +public class GetResourcesQueryHandler(IRepository repository) + : IQueryHandler> +{ + + /// + public virtual Task>> HandleAsync(GetResourcesQuery query, CancellationToken cancellationToken) + { + return Task.FromResult(this.Ok(repository.GetAllAsync(query.Group, query.Version, query.Plural, query.Namespace, query.LabelSelectors, cancellationToken))); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/ListResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/ListResourcesQuery.cs new file mode 100644 index 00000000..80950296 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/ListResourcesQuery.cs @@ -0,0 +1,98 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources; + +/// +/// Represents the used to list all s +/// +public class ListResourcesQuery + : Query +{ + + /// + /// Initializes a new + /// + /// The API group the resources to list belongs to + /// The version of the resources to list + /// The plural name of the type of resources to list + /// The namespace the s to list belong to, if any + /// A containing the s used to filter s by + /// The maximum amount of results to list + /// The token, if any, used to continue enumerating the results + public ListResourcesQuery(string group, string version, string plural, string? @namespace, List? labelSelectors, ulong? maxResults, string? continuationToken) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + this.Group = group; + this.Version = version; + this.Plural = plural; + this.Namespace = @namespace; + this.LabelSelectors = labelSelectors; + this.MaxResults = maxResults; + this.ContinuationToken = continuationToken; + } + + /// + /// Gets the API group the resources to list belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resources to list + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resources to list + /// + public string Plural { get; } + + /// + /// Gets the namespace the s to list belong to, if any + /// + public string? Namespace { get; } + + /// + /// Gets a containing the s used to filter s by + /// + public List? LabelSelectors { get; } + + /// + /// Gets the maximum amount of results to list + /// + public ulong? MaxResults { get; } + + /// + /// Gets the token, if any, used to continue enumerating the results + /// + public string? ContinuationToken { get; } + +} + +/// +/// Represents the service used to handle instances +/// +/// The service used to manage s +public class ListResourcesQueryHandler(IRepository repository) + : IQueryHandler +{ + + /// + public virtual async Task> HandleAsync(ListResourcesQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.ListAsync(query.Group, query.Version, query.Plural, query.Namespace, query.LabelSelectors, query.MaxResults, query.ContinuationToken, cancellationToken).ConfigureAwait(false)); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Queries/Resources/WatchResourcesQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Resources/WatchResourcesQuery.cs new file mode 100644 index 00000000..30054d60 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Queries/Resources/WatchResourcesQuery.cs @@ -0,0 +1,84 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Queries.Resources; + +/// +/// Represents the used to retrieves s of the specified type +/// +public class WatchResourcesQuery + : Query +{ + + /// + /// Initializes a new + /// + /// The API group the resources to watch belongs to + /// The version of the resources to watch + /// The plural name of the type of resources to watch + /// The namespace the s to get belong to, if any + /// A containing the s used to filter s by + public WatchResourcesQuery(string group, string version, string plural, string? @namespace, List? labelSelectors) + { + if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group)); + if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); + if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural)); + this.Group = group; + this.Version = version; + this.Plural = plural; + this.Namespace = @namespace; + this.LabelSelectors = labelSelectors; + } + + /// + /// Gets the API group the resources to watch belongs to + /// + public string Group { get; } + + /// + /// Gets the version of the resources to watch + /// + public string Version { get; } + + /// + /// Gets the plural name of the type of resources to watch + /// + public string Plural { get; } + + /// + /// Gets the namespace the s to get belong to, if any + /// + public string? Namespace { get; } + + /// + /// Gets a containing the s used to filter s by + /// + public List? LabelSelectors { get; } + +} + +/// +/// Represents the service used to handle instances +/// +/// The service used to manage s +public class WatchResourcesQueryHandler(IRepository repository) + : IQueryHandler +{ + + /// + public virtual async Task> HandleAsync(WatchResourcesQuery query, CancellationToken cancellationToken) + { + return this.Ok(await repository.WatchAsync(query.Group, query.Version, query.Plural, query.Namespace, query.LabelSelectors, cancellationToken).ConfigureAwait(false)); + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Application/Queries/Streams/GetEventStreamMetadataQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Streams/GetEventStreamMetadataQuery.cs index 23b665af..20c30df0 100644 --- a/src/core/CloudStreams.Core.Application/Queries/Streams/GetEventStreamMetadataQuery.cs +++ b/src/core/CloudStreams.Core.Application/Queries/Streams/GetEventStreamMetadataQuery.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure.Services; -using Hylo.Api.Application; +using CloudStreams.Core.Application.Services; namespace CloudStreams.Core.Application.Queries.Streams; @@ -21,7 +19,7 @@ namespace CloudStreams.Core.Application.Queries.Streams; /// Represents the used to get the metadata of the application's cloud event stream /// public class GetEventStreamMetadataQuery - : IQuery + : Query { @@ -31,21 +29,14 @@ public class GetEventStreamMetadataQuery /// /// Represents the service used to handle instances /// -public class GetEventPartitionMetadataQueryHandler +public class GetEventPartitionMetadataQueryHandler(ICloudEventStore eventStore) : IQueryHandler { /// - public GetEventPartitionMetadataQueryHandler(IEventStoreProvider eventStoreProvider) + public virtual async Task> HandleAsync(GetEventStreamMetadataQuery query, CancellationToken cancellationToken) { - this._eventStoreProvider = eventStoreProvider; - } - - IEventStoreProvider _eventStoreProvider; - - async Task> MediatR.IRequestHandler>.Handle(GetEventStreamMetadataQuery request, CancellationToken cancellationToken) - { - return this.Ok(await this._eventStoreProvider.GetEventStore().GetStreamMetadataAsync(cancellationToken)); + return this.Ok(await eventStore.GetStreamMetadataAsync(cancellationToken)); } } diff --git a/src/core/CloudStreams.Core.Application/Queries/Streams/ReadEventStreamQuery.cs b/src/core/CloudStreams.Core.Application/Queries/Streams/ReadEventStreamQuery.cs index cacaa87e..16161e46 100644 --- a/src/core/CloudStreams.Core.Application/Queries/Streams/ReadEventStreamQuery.cs +++ b/src/core/CloudStreams.Core.Application/Queries/Streams/ReadEventStreamQuery.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,53 +11,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure.Services; -using Hylo.Api.Application; -using System.Net; +using CloudStreams.Core.Application.Services; +using Neuroglia.Data.Infrastructure.EventSourcing; namespace CloudStreams.Core.Application.Queries.Streams; /// /// Represents the used to list stored s /// -public class ReadEventStreamQuery - : IQuery> +/// +/// Initializes a new +/// +/// The object used to configure the query to perform +public class ReadEventStreamQuery(StreamReadOptions options) + : Query> { - - /// - /// Initializes a new - /// - /// The object used to configure the query to perform - public ReadEventStreamQuery(StreamReadOptions options) - { - this.Options = options; - } - + /// /// Gets the object used to configure the query to perform /// - public StreamReadOptions Options { get; } + public StreamReadOptions Options { get; } = options; } /// /// Represents the service used to handle instances /// -public class ReadCloudEventStreamQueryHandler +public class ReadCloudEventStreamQueryHandler(ICloudEventStore eventStore) : IQueryHandler> { - readonly IEventStoreProvider _eventStoreProvider; - - /// - public ReadCloudEventStreamQueryHandler(IEventStoreProvider eventStoreProvider) - { - this._eventStoreProvider = eventStoreProvider; - } - /// - public Task>> Handle(ReadEventStreamQuery query, CancellationToken cancellationToken) + public Task>> HandleAsync(ReadEventStreamQuery query, CancellationToken cancellationToken) { var length = query.Options.Length > StreamReadOptions.MaxLength ? StreamReadOptions.MaxLength : query.Options.Length; if (length < 1) length = 1; @@ -73,10 +58,9 @@ public Task>> Handle(ReadEventStreamQuery q offset = StreamPosition.EndOfStream; break; default: - return Task.FromResult(new ApiResponse>((int)HttpStatusCode.BadRequest) { Errors = new(new KeyValuePair[] { new(nameof(query.Options.Direction).ToLowerInvariant(), new string[] { $"The specified {nameof(StreamReadDirection)} '{query.Options.Direction}' is not supported" }) }) }); + return Task.FromResult((IOperationResult>)new OperationResult>((int)HttpStatusCode.BadRequest)); } } - var eventStore = _eventStoreProvider.GetEventStore(); var events = query.Options.Partition == null ? eventStore.ReadAsync(query.Options.Direction, offset.Value, length, cancellationToken: cancellationToken) : eventStore.ReadPartitionAsync(query.Options.Partition, query.Options.Direction, offset.Value, length, cancellationToken: cancellationToken); diff --git a/src/core/CloudStreams.Core.Application/Services/CloudEventStore.cs b/src/core/CloudStreams.Core.Application/Services/CloudEventStore.cs new file mode 100644 index 00000000..61ad3068 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Services/CloudEventStore.cs @@ -0,0 +1,398 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.Extensions.Hosting; +using Neuroglia.Data.Infrastructure.EventSourcing; +using Neuroglia.Data.Infrastructure.EventSourcing.Services; +using Neuroglia.Data.Infrastructure.ResourceOriented.Properties; +using Neuroglia.Serialization; +using System.Reactive.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json.Nodes; + +namespace CloudStreams.Core.Application.Services; + +/// +/// Represents the default implementation of the interface +/// +/// The underlying service used to source events +/// The service used to manage event-driven projections +/// The service used to serialize/deserialize data to/from JSON +public class CloudEventStore(IEventStore eventStore, IProjectionManager projectionManager, IJsonSerializer serializer) + : IHostedService, ICloudEventStore +{ + + /// + /// Gets the underlying service used to source events + /// + protected IEventStore EventStore => eventStore; + + /// + /// Gets the service used to manage event-driven projections + /// + protected IProjectionManager ProjectionManager => projectionManager; + + /// + /// Gets the service used to serialize/deserialize data to/from JSON + /// + protected IJsonSerializer Serializer { get; } = serializer; + + /// + public virtual Task StartAsync(CancellationToken cancellationToken) => this.SetupProjectionsAsync(cancellationToken); + + /// + public virtual Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public virtual async Task AppendAsync(CloudEventDescriptor e, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(e); + var streamName = Streams.All; + var descriptor = await this.ParseEventDescriptorAsync(e, cancellationToken).ConfigureAwait(false); + var offset = await this.EventStore.AppendAsync(streamName, [descriptor], cancellationToken: cancellationToken).ConfigureAwait(false); + return new(streamName, offset, e.Metadata, e.Data); + } + + /// + public virtual async Task GetStreamMetadataAsync(CancellationToken cancellationToken = default) + { + try + { + var firstEvent = (await this.EventStore.ReadAsync(Streams.All, StreamReadDirection.Forwards, StreamPosition.StartOfStream, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); + var lastEvent = (await this.EventStore.ReadAsync(Streams.All, StreamReadDirection.Backwards, StreamPosition.EndOfStream, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); + return new() + { + FirstEvent = firstEvent.Timestamp, + LastEvent = lastEvent.Timestamp, + Length = lastEvent.Offset + 1 + }; + } + catch (StreamNotFoundException) + { + return new(); + } + } + + /// + public virtual async Task GetPartitionMetadataAsync(PartitionReference partition, CancellationToken cancellationToken = default) + { + try + { + var streamName = partition.GetStreamName(); + var firstEvent = (await this.EventStore.ReadAsync(streamName, StreamReadDirection.Forwards, StreamPosition.StartOfStream, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); + var lastEvent = (await this.EventStore.ReadAsync(streamName, StreamReadDirection.Backwards, StreamPosition.EndOfStream, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); + return new() + { + Id = partition.Id, + Type = partition.Type, + FirstEvent = firstEvent.Timestamp, + LastEvent = lastEvent.Timestamp, + Length = lastEvent.Offset + 1 + }; + } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream '{partition.GetStreamName()}' of the partition of type '{EnumHelper.Stringify(partition.Type)}' with id '{partition.Id}'. Related projection does not exist or has not been properly configured")); + } + } + + /// + public virtual async IAsyncEnumerable ListPartitionIdsAsync(CloudEventPartitionType partitionType, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var typeName = EnumHelper.Stringify(partitionType).Replace('-', '_'); + List? partitionsIds = null; + try + { + partitionsIds = await this.ProjectionManager.GetStateAsync>(Projections.CloudEventPartitionsMetadataPrefix + typeName, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch { } + if (partitionsIds == null) yield break; + await foreach (var id in partitionsIds.ToAsyncEnumerable()) yield return id; + } + + /// + public virtual async IAsyncEnumerable ReadAsync(StreamReadDirection readDirection, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var streamName = Streams.All; + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken); } + catch (StreamNotFoundException) { yield break; } + await foreach (var record in records) yield return this.ReadRecord(record); + } + + /// + public virtual IAsyncEnumerable ReadPartitionAsync(PartitionReference partition, StreamReadDirection readDirection, long offset, ulong? length = null, CancellationToken cancellationToken = default) + { + switch (partition.Type) + { + case CloudEventPartitionType.BySource: + if (!Uri.TryCreate(partition.Id, UriKind.RelativeOrAbsolute, out var source)) throw new Exception(); + return this.ReadBySourceAsync(readDirection, source, offset, length, cancellationToken); + case CloudEventPartitionType.BySubject: + return this.ReadBySubjectAsync(readDirection, partition.Id, offset, length, cancellationToken); + case CloudEventPartitionType.ByType: + return this.ReadByTypeAsync(readDirection, partition.Id, offset, length, cancellationToken); + case CloudEventPartitionType.ByCorrelationId: + return this.ReadByCorrelationIdAsync(readDirection, partition.Id, offset, length, cancellationToken); + case CloudEventPartitionType.ByCausationId: + return this.ReadByCausationIdAsync(readDirection, partition.Id, offset, length, cancellationToken); + default: + throw new NotSupportedException($"The specified {nameof(CloudEventPartitionType)} '{partition.Type}' is not supported"); + } + } + + /// + public virtual async Task> ObserveAsync(long offset = -1, CancellationToken cancellationToken = default) + { + var subscription = await this.EventStore.ObserveAsync( + Streams.All, + offset, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + return subscription.Select(this.ReadRecord); + } + + /// + public virtual async Task> ObservePartitionAsync(PartitionReference partition, long offset = -1, CancellationToken cancellationToken = default) + { + var subscription = await this.EventStore.ObserveAsync( + partition.GetStreamName(), + offset, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + return subscription.Select(this.ReadRecord); + } + + /// + public virtual Task TruncateAsync(ulong beforeVersion, CancellationToken cancellationToken = default) => this.EventStore.TruncateAsync(Streams.All, beforeVersion, cancellationToken); + + /// + /// Creates and configures the projections required by Cloud Streams + /// + /// A + /// A new awaitable + protected virtual async Task SetupProjectionsAsync(CancellationToken cancellationToken = default) + { + try { if (await this.ProjectionManager.GetStatusAsync(Projections.PartitionBySource, cancellationToken: cancellationToken) != null) return; } + catch { } + + try + { + await this.ProjectionManager.CreateAsync(Projections.PartitionBySource, + projection => projection + .FromStream(Streams.All) + .When((state, e) => e.Metadata!.Get("source") != null) + .Then((state, e) => Projection.LinkEventTo("$by-source-" + e.Metadata!.Get("source"), e)), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch { } + + try + { + await this.ProjectionManager.CreateAsync(Projections.PartitionBySubject, + projection => projection + .FromStream(Streams.All) + .When((state, e) => e.Metadata!.Get("subject") != null) + .Then((state, e) => Projection.LinkEventTo("$by-subject-" + e.Metadata!.Get("subject"), e)), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch { } + + try + { + await this.ProjectionManager.CreateAsync(Projections.PartitionByCausationId, + projection => projection + .FromStream(Streams.All) + .When((state, e) => e.Metadata!.Get("$causationId") != null) + .Then((state, e) => Projection.LinkEventTo("$by-causation-" + e.Metadata!.Get("$causationId"), e)), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch { } + + foreach (var partitionType in Enum.GetValues()) + { + var typeName = EnumHelper.Stringify(partitionType).Replace('-', '_'); + var metadataPath = typeName.Replace("by", string.Empty).ToCamelCase(); + if (partitionType == CloudEventPartitionType.ByCorrelationId || partitionType == CloudEventPartitionType.ByCausationId) metadataPath = "$" + metadataPath.Replace("-id", "Id"); + try + { + await this.ProjectionManager.CreateAsync>(Projections.CloudEventPartitionsMetadataPrefix + typeName, + projection => projection + .FromStream(Streams.All) + .Given(() => new List()) + .When((state, e) => e.Metadata!.Get(metadataPath) != null && !state.Contains(e.Metadata!.Get(metadataPath))) + .Then((state, e) => state.Add(e.Metadata!.Get(metadataPath))), + cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch { } + } + } + + /// + /// Reads stored s by source + /// + /// The direction in which to read + /// The source of the s to read + /// The offset starting from which to read events + /// The amount of s to read + /// A + /// A new containing the s read from the store + protected virtual async IAsyncEnumerable ReadBySourceAsync(StreamReadDirection readDirection, Uri source, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(source); + var streamName = Streams.ByCloudEventSource(source); + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken); } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.BySource)}'. Related projection does not exist or has not been properly configured")); + } + await foreach (var record in records) { yield return this.ReadRecord(record); } + } + + /// + /// Reads stored s by subject + /// + /// The direction in which to read + /// The subject of the s to read + /// The offset starting from which to read events + /// The amount of s to read + /// A + /// A new containing the s read from the store + /// + protected virtual async IAsyncEnumerable ReadBySubjectAsync(StreamReadDirection readDirection, string subject, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(subject)) throw new ArgumentNullException(nameof(subject)); + var streamName = Streams.ByCloudEventSubject(subject); + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken); } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.BySubject)}'. Related projection does not exist or has not been properly configured")); + } + await foreach (var record in records) yield return this.ReadRecord(record); + } + + /// + /// Reads stored s by type + /// + /// The direction in which to read + /// The type of the s to read + /// The offset starting from which to read events + /// The amount of s to read + /// A + /// A new containing the s read from the store + /// + protected virtual async IAsyncEnumerable ReadByTypeAsync(StreamReadDirection readDirection, string type, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(type)) throw new ArgumentNullException(nameof(type)); + var streamName = Streams.ByCloudEventType(type); + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken: cancellationToken); } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByType)}'. Related projection does not exist or has not been properly configured")); + } + await foreach (var record in records) + { + yield return this.ReadRecord(record); + } + } + + /// + /// Reads stored s by correlation id + /// + /// The direction in which to read + /// The correlation id of the s to read + /// The offset starting from which to read events + /// The amount of s to read + /// A + /// A new containing the s read from the store + protected virtual async IAsyncEnumerable ReadByCorrelationIdAsync(StreamReadDirection readDirection, string correlationId, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(correlationId)) throw new ArgumentNullException(nameof(correlationId)); + var streamName = Streams.ByCorrelationId(correlationId); + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken: cancellationToken); } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByCorrelationId)}'. Related projection does not exist or has not been properly configured")); + } + await foreach (var record in records) yield return this.ReadRecord(record); + } + + /// + /// Reads stored s by causation id + /// + /// The direction in which to read + /// The causation id of the s to read + /// The offset starting from which to read events + /// The amount of s to read + /// A + /// A new containing the s read from the store + protected virtual async IAsyncEnumerable ReadByCausationIdAsync(StreamReadDirection readDirection, string causationId, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(causationId)) throw new ArgumentNullException(nameof(causationId)); + var streamName = Streams.ByCausationId(causationId); + IAsyncEnumerable records; + try { records = this.EventStore.ReadAsync(streamName, readDirection, offset, length ?? ulong.MaxValue, cancellationToken: cancellationToken); } + catch (StreamNotFoundException) + { + throw new ProblemDetailsException(new ProblemDetails(ProblemTypes.NotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByCausationId)}'. Related projection does not exist or has not been properly configured")); + } + await foreach (var record in records) yield return this.ReadRecord(record); + } + + /// + /// Parses the specified into a new + /// + /// An object that describes the to serialize + /// A + /// The serialized + protected virtual async Task ParseEventDescriptorAsync(CloudEventDescriptor eventDescriptor, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(eventDescriptor); + var e = await this.ParseCloudEventAsync(eventDescriptor, cancellationToken).ConfigureAwait(false); + var type = e.Type!; + var contextAttributes = this.Serializer.Deserialize>(this.Serializer.SerializeToByteArray(eventDescriptor))!; + contextAttributes.Remove(CloudEventAttributes.Data); + return new EventDescriptor(type, eventDescriptor.Data, eventDescriptor.Metadata.ContextAttributes); + } + + /// + /// Parses the specified into a new + /// + /// An object that describes the to serialize + /// A + /// The serialized + protected virtual Task ParseCloudEventAsync(CloudEventDescriptor eventDescriptor, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(eventDescriptor); + var e = (JsonObject)this.Serializer.SerializeToNode(eventDescriptor.Metadata.ContextAttributes)!; + var data = this.Serializer.SerializeToNode(eventDescriptor.Data); + e[CloudEventAttributes.Data] = data; + return Task.FromResult(this.Serializer.Deserialize(this.Serializer.SerializeToByteArray(e))!); + } + + /// + /// Deserializes the specified into a new + /// + /// The to convert + /// A new + protected virtual CloudEventRecord ReadRecord(IEventRecord e) + { + ArgumentNullException.ThrowIfNull(e); + return new(e.StreamId, e.Offset, new() { ContextAttributes = e.Metadata ?? new Dictionary() }, e.Data); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Services/DatabaseInitializer.cs b/src/core/CloudStreams.Core.Application/Services/DatabaseInitializer.cs new file mode 100644 index 00000000..ad5d4a54 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Services/DatabaseInitializer.cs @@ -0,0 +1,37 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace CloudStreams.Core.Application.Services; + +/// +/// Represents the service used to initialize the Cloud Streams resource database +/// +/// +public class DatabaseInitializer(ILoggerFactory loggerFactory, IServiceProvider serviceProvider) + : Neuroglia.Data.Infrastructure.ResourceOriented.Services.DatabaseInitializer(loggerFactory, serviceProvider) +{ + + /// + protected override async Task SeedAsync(CancellationToken cancellationToken) + { + var database = this.ServiceProvider.GetRequiredService(); + foreach (var definition in CloudStreamsDefaults.Resources.Definitions.AsEnumerable()) + { + await database.CreateResourceAsync(definition, cancellationToken: cancellationToken).ConfigureAwait(false); + } + } + +} diff --git a/src/core/CloudStreams.Core.Application/Services/ExceptionHandlingPipelineBehavior.cs b/src/core/CloudStreams.Core.Application/Services/ExceptionHandlingPipelineBehavior.cs deleted file mode 100644 index 9fea1c94..00000000 --- a/src/core/CloudStreams.Core.Application/Services/ExceptionHandlingPipelineBehavior.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Api.Application; -using MediatR; -using System.Net; - -namespace CloudStreams.Core.Application.Services; - -/// -/// Represents the used to catch and transform uncaught s into s -/// -/// The type of request to handle -/// The expected type of response -public class ExceptionHandlingPipelineBehavior - : IPipelineBehavior - where TRequest : Hylo.Api.Application.IRequest - where TResponse : ApiResponse, new() -{ - - /// - public virtual async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) - { - try - { - return await next(); - } - catch(HttpRequestException ex) - { - return new() - { - Status = (int)ex.StatusCode!, - Title = ex.StatusCode?.ToString() ?? string.Empty, - Detail = ex.Message - }; - } - catch (Exception ex) - { - return new() - { - Status = (int)HttpStatusCode.InternalServerError, - Title = ex.GetType().Name.Replace(nameof(Exception), string.Empty), - Detail = ex.Message - }; - } - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStore.cs b/src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudEventStore.cs similarity index 88% rename from src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStore.cs rename to src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudEventStore.cs index 16cec394..cb6e610f 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStore.cs +++ b/src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudEventStore.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,12 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Infrastructure.Services; +using Neuroglia.Data.Infrastructure.EventSourcing; + +namespace CloudStreams.Core.Application.Services; /// -/// Defines the fundamentals of a service used to store s +/// Defines the fundamentals of a service used to source s /// -public interface IEventStore +public interface ICloudEventStore { /// @@ -77,7 +79,7 @@ public interface IEventStore /// The offset starting from which to receive s. Defaults to /// A /// A new used to observe s - Task> SubscribeAsync(long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default); + Task> ObserveAsync(long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default); /// /// Subscribes to s @@ -86,14 +88,14 @@ public interface IEventStore /// The offset starting from which to receive s. Defaults to /// A /// A new used to observe s of the specified partition - Task> SubscribeToPartitionAsync(PartitionReference partition, long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default); - + Task> ObservePartitionAsync(PartitionReference partition, long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default); + /// /// Truncates stored s /// /// The version before which to truncate s /// A /// A new awaitable - Task TruncateAsync(long beforeVersion, CancellationToken cancellationToken = default); + Task TruncateAsync(ulong beforeVersion, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Configuration/ICloudStreamsApplicationBuilder.cs b/src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudStreamsApplicationBuilder.cs similarity index 65% rename from src/core/CloudStreams.Core.Infrastructure/Configuration/ICloudStreamsApplicationBuilder.cs rename to src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudStreamsApplicationBuilder.cs index de02b5f3..d98f178d 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Configuration/ICloudStreamsApplicationBuilder.cs +++ b/src/core/CloudStreams.Core.Application/Services/Interfaces/ICloudStreamsApplicationBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,14 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Infrastructure.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Reflection; -namespace CloudStreams.Core.Infrastructure.Configuration; +namespace CloudStreams.Core.Application.Services; /// /// Defines fundamentals of a service used to build and configure a Cloud Streams API @@ -68,13 +67,6 @@ public interface ICloudStreamsApplicationBuilder /// The configured ICloudStreamsApplicationBuilder RegisterMediationAssembly(); - /// - /// Registers the specified mediation pipeline behavior - /// - /// The type of the mediation pipeline behavior to register - /// The configured - ICloudStreamsApplicationBuilder RegisterMediationPipelineBehavior(Type behaviorType); - /// /// Registers an to scan for fluent validators /// @@ -89,30 +81,6 @@ public interface ICloudStreamsApplicationBuilder /// The configured ICloudStreamsApplicationBuilder RegisterHealthCheck(Action setup); - /// - /// Configures Cloud Streams to use the specified - /// - /// The type of to use - /// The configured - ICloudStreamsApplicationBuilder UseExpressionEvaluatorProvider() - where TProvider : class, IExpressionEvaluatorProvider; - - /// - /// Configures Cloud Streams to use the specified - /// - /// The type of to use - /// The configured - ICloudStreamsApplicationBuilder UseEventStoreProvider() - where TProvider : class, IEventStoreProvider; - - /// - /// Configures Cloud Streams to use the specified - /// - /// The type of to use - /// The configured - ICloudStreamsApplicationBuilder UseSchemaRegistryProvider() - where TRegistry : class, ISchemaRegistryProvider; - /// /// Builds the Cloud Streams API /// diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistry.cs b/src/core/CloudStreams.Core.Application/Services/Interfaces/IJsonSchemaRegistry.cs similarity index 50% rename from src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistry.cs rename to src/core/CloudStreams.Core.Application/Services/Interfaces/IJsonSchemaRegistry.cs index 88f33aec..a201a390 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistry.cs +++ b/src/core/CloudStreams.Core.Application/Services/Interfaces/IJsonSchemaRegistry.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,36 +11,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Infrastructure.Services; +using Json.Schema; + +namespace CloudStreams.Core.Application.Services; /// -/// Defines the fundamentals of a schema registry +/// Defines the fundamentals of a service used to store s /// -public interface ISchemaRegistry +public interface IJsonSchemaRegistry { - /// - /// Registers the specified - /// - /// The to register - /// A - /// The referencing the registered - Task RegisterSchemaAsync(JsonSchema schema, CancellationToken cancellationToken = default); - /// /// Gets the at the specified /// - /// The that references the to get + /// The of the to get /// A /// The at the specified - Task GetSchemaAsync(Uri uri, CancellationToken cancellationToken = default); + Task GetAsync(Uri uri, CancellationToken cancellationToken = default); /// - /// Gets the of the with the specified id + /// Registers the specified /// - /// The id of the to get + /// The to register /// A - /// The of the with the specified id, if any - Task GetSchemaUriByIdAsync(string id, CancellationToken cancellationToken = default); + /// A new awaitable + Task RegisterAsync(JsonSchema schema, CancellationToken cancellationToken = default); } diff --git a/src/core/CloudStreams.Core.Application/Services/MemoryJsonSchemaRegistry.cs b/src/core/CloudStreams.Core.Application/Services/MemoryJsonSchemaRegistry.cs new file mode 100644 index 00000000..4d705577 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Services/MemoryJsonSchemaRegistry.cs @@ -0,0 +1,60 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Json.Schema; +using Neuroglia.Serialization; + +namespace CloudStreams.Core.Application.Services; + +/// +/// Represents the in-memory implementation of the interface +/// +/// The service used to perform HTTP requests +/// The service used to serialize/deserialize objects to/from JSON +public class MemoryJsonSchemaRegistry(HttpClient httpClient, IJsonSerializer serializer) + : IJsonSchemaRegistry +{ + + /// + /// Gets the service used to perform HTTP requests + /// + protected HttpClient HttpClient { get; } = httpClient; + + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + protected IJsonSerializer Serializer { get; } = serializer; + + /// + public virtual async Task GetAsync(Uri uri, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(uri); + var document = (JsonNodeBaseDocument?)SchemaRegistry.Global.Get(uri); + if (document != null) return this.Serializer.Deserialize(this.Serializer.SerializeToText(document))!; + var json = await this.HttpClient.GetStringAsync(uri, cancellationToken).ConfigureAwait(false); + var schema = this.Serializer.Deserialize(json)!; + document = new JsonNodeBaseDocument(this.Serializer.SerializeToNode(schema)!, uri); + SchemaRegistry.Global.Register(uri, document); + return schema; + } + + /// + public virtual async Task RegisterAsync(JsonSchema schema, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(schema); + var document = new JsonNodeBaseDocument(this.Serializer.SerializeToNode(schema)!, schema.BaseUri); + SchemaRegistry.Global.Register(schema.BaseUri, document); + await Task.CompletedTask.ConfigureAwait(false); + } + +} diff --git a/src/core/CloudStreams.Core.Application/Services/ProblemDetailsExceptionHandlingMiddleware.cs b/src/core/CloudStreams.Core.Application/Services/ProblemDetailsExceptionHandlingMiddleware.cs new file mode 100644 index 00000000..0a5c3c92 --- /dev/null +++ b/src/core/CloudStreams.Core.Application/Services/ProblemDetailsExceptionHandlingMiddleware.cs @@ -0,0 +1,64 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Application.Services; + +/// +/// Represents an used to handle s during the execution of an +/// +/// The type of to handle +/// The type of expected +public class ProblemDetailsExceptionHandlingMiddleware + : IMiddleware + where TRequest : IRequest + where TResult : IOperationResult +{ + + /// + public virtual async Task HandleAsync(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken = default) + { + try + { + return await next(); + } + catch (ProblemDetailsException ex) + { + if (!TryCreateErrorResponse(ex.Problem, out var response)) throw; + return response; + } + } + + /// + /// Creates a new error + /// + /// The newly created + /// The to create the new for + /// A new error + protected virtual bool TryCreateErrorResponse(ProblemDetails problem, out TResult result) + { + Type responseType; + if (typeof(IOperationResult).IsAssignableFrom(typeof(TResult))) + { + if (typeof(TResult).IsGenericType) responseType = typeof(OperationResult<>).MakeGenericType(typeof(TResult).GetGenericArguments().First()); + else responseType = typeof(OperationResult); + } + else + { + result = default!; + return false; + } + result = (TResult)Activator.CreateInstance(responseType, problem.Status, problem.Detail, problem.Errors)!; + return true; + } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreStreams.cs b/src/core/CloudStreams.Core.Application/Streams.cs similarity index 81% rename from src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreStreams.cs rename to src/core/CloudStreams.Core.Application/Streams.cs index e9af129b..ff67915d 100644 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/EventStoreStreams.cs +++ b/src/core/CloudStreams.Core.Application/Streams.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,18 +11,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using CloudStreams.Core; -namespace CloudStreams.Core.Infrastructure; +namespace CloudStreams; /// -/// Exposes constants about the EventStore streams used by Cloud Streams +/// Exposes constants about the event sourcing streams used by Cloud Streams /// -public static class EventStoreStreams +public static class Streams { /// - /// Gets the name of the stream that contains all s + /// Gets the name of the stream that contains all s /// public const string All = "cloud-events"; @@ -48,23 +48,23 @@ public static class EventStoreStreams public const string ByCausationIdPrefix = "$by-causation-"; /// - /// Gets the name of the stream that partitions s by source + /// Gets the name of the stream that partitions s by source /// public static string ByCloudEventSource(Uri source) => $"{ByCloudEventSourcePrefix}{source.OriginalString}"; /// - /// Gets the name of the stream that partitions s by subject + /// Gets the name of the stream that partitions s by subject /// public static string ByCloudEventSubject(string subject) => $"{ByCloudEventSubjectPrefix}{subject}"; /// - /// Gets the name of the stream that partitions s by type + /// Gets the name of the stream that partitions s by type /// public static string ByCloudEventType(string type) => $"{ByCloudEventTypePrefix}{type}"; /// - /// Gets the name of the stream that partitions s by correlation id + /// Gets the name of the stream that partitions s by correlation id /// public static string ByCorrelationId(string correlationId) => $"{ByCorrelationIdPrefix}{correlationId}"; /// - /// Gets the name of the stream that partitions s by causation id + /// Gets the name of the stream that partitions s by causation id /// public static string ByCausationId(string causationId) => $"{ByCausationIdPrefix}{causationId}"; @@ -76,7 +76,7 @@ public static class EventStoreStreams /// A boolean indicating whether or not the specified EventStore stream name is the one of a partition of the specified type public static bool IsPartition(string streamName, CloudEventPartitionType partitionType) { - if(string.IsNullOrWhiteSpace(streamName)) throw new ArgumentNullException(nameof(streamName)); + if (string.IsNullOrWhiteSpace(streamName)) throw new ArgumentNullException(nameof(streamName)); return partitionType switch { CloudEventPartitionType.BySource => streamName.StartsWith(ByCloudEventSourcePrefix), @@ -96,9 +96,9 @@ public static bool IsPartition(string streamName, CloudEventPartitionType partit /// The id of the specified partition public static string ExtractPartitionIdFrom(string streamName, CloudEventPartitionType partitionType) { - if(string.IsNullOrWhiteSpace(streamName)) throw new ArgumentNullException(nameof(streamName)); + if (string.IsNullOrWhiteSpace(streamName)) throw new ArgumentNullException(nameof(streamName)); return partitionType switch - { + { CloudEventPartitionType.BySource => streamName[ByCloudEventSourcePrefix.Length..], CloudEventPartitionType.BySubject => streamName[ByCloudEventSubjectPrefix.Length..], CloudEventPartitionType.ByType => streamName[ByCloudEventTypePrefix.Length..], diff --git a/src/core/CloudStreams.Core.Application/Telemetry.cs b/src/core/CloudStreams.Core.Application/Telemetry.cs deleted file mode 100644 index f5643086..00000000 --- a/src/core/CloudStreams.Core.Application/Telemetry.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Diagnostics; - -namespace CloudStreams.Core.Application; - -/// -/// Exposes constants about Cloud Streams application telemetry -/// -public static class Telemetry -{ - - /// - /// Exposes the Cloud Streams application's - /// - public static ActivitySource ActivitySource { get; set; } = null!; - -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Server/Usings.cs b/src/core/CloudStreams.Core.Application/Usings.cs similarity index 61% rename from src/gateway/CloudStreams.Gateway.Api.Server/Usings.cs rename to src/core/CloudStreams.Core.Application/Usings.cs index 0cfd63b4..91e1c92f 100644 --- a/src/gateway/CloudStreams.Gateway.Api.Server/Usings.cs +++ b/src/core/CloudStreams.Core.Application/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using CloudStreams.Core; -global using CloudStreams.Core.Data; -global using CloudStreams.Core.Infrastructure.Configuration; -global using MediatR; -global using Microsoft.AspNetCore.Mvc; +global using CloudStreams.Core.Resources; +global using Neuroglia; +global using Neuroglia.Data; +global using Neuroglia.Data.Infrastructure.ResourceOriented; +global using Neuroglia.Data.Infrastructure.ResourceOriented.Services; +global using Neuroglia.Eventing.CloudEvents; +global using Neuroglia.Mediation; global using System.Net; diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bycausationid.js b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bycausationid.js deleted file mode 100644 index 7b1c36f8..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bycausationid.js +++ /dev/null @@ -1,9 +0,0 @@ -fromStream('cloud-events') - .when({ - $any: (_, evt) => { - if (!evt || !evt.metadataRaw) return; - const metadata = JSON.parse(evt.metadataRaw); - if (!metadata || !metadata.$causationId) return; - linkTo('$by-causation-' + metadata.$causationId, evt); - } - }); \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysource.js b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysource.js deleted file mode 100644 index a5cb0bc6..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysource.js +++ /dev/null @@ -1,9 +0,0 @@ -fromStream('cloud-events') - .when({ - $any: (_, evt) => { - if (!evt || !evt.metadataRaw) return; - const metadata = JSON.parse(evt.metadataRaw); - if (!metadata || !metadata.contextAttributes || !metadata.contextAttributes.source) return; - linkTo('$by-source-' + metadata.contextAttributes.source, evt); - } - }); \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysubject.js b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysubject.js deleted file mode 100644 index 1fce9f2e..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/bysubject.js +++ /dev/null @@ -1,9 +0,0 @@ -fromStream('cloud-events') - .when({ - $any: (_, evt) => { - if (!evt || !evt.metadataRaw) return; - const metadata = JSON.parse(evt.metadataRaw); - if (!metadata || !metadata.contextAttributes || !metadata.contextAttributes.subject) return; - linkTo('$by-subject-' + metadata.contextAttributes.subject, evt); - } - }); \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/partitionids.js.tmpl b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/partitionids.js.tmpl deleted file mode 100644 index 88a34b32..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Assets/Projections/partitionids.js.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -fromStream('cloud-events') - .when({ - $init: () => [], - $any: (stream, evt) => { - if (!evt || !evt.metadataRaw || !evt.metadataRaw) return; - const metadata = JSON.parse(evt.metadataRaw); - const id = metadata.##metadataPath##; - if (!id || stream.includes(id)) return; - stream.push(id); - } - }); \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/CloudStreams.Core.Infrastructure.EventSourcing.EventStore.csproj b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/CloudStreams.Core.Infrastructure.EventSourcing.EventStore.csproj deleted file mode 100644 index 515ddbbb..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/CloudStreams.Core.Infrastructure.EventSourcing.EventStore.csproj +++ /dev/null @@ -1,65 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;event-sourcing; - Contains EventStore implementations of Cloud Streams cloud event sourcing services - logo.png - Apache-2.0 - True - True - true - CloudStreams.Core.Infrastructure - - - - - Never - - - Never - - - Never - - - Never - - - - - - \ - True - - - \ - True - - - - - - - - - - - - - - - - diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/IServiceCollectionExtensions.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/IServiceCollectionExtensions.cs deleted file mode 100644 index c193adcd..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/IServiceCollectionExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure.EventSourcing.EventStore; - -/// -/// Defines extensions for s -/// -public static class IServiceCollectionExtensions -{ - - /// - /// Adds and configure EventStore services - /// - /// The to configure - /// The to use - /// The configured - public static IServiceCollection AddEventStore(this IServiceCollection services, EventStoreClientSettings settings) - { - services.TryAddSingleton(new EventStoreClient(settings)); - services.TryAddSingleton(new EventStorePersistentSubscriptionsClient(settings)); - services.TryAddSingleton(new EventStoreProjectionManagementClient(settings)); - return services; - } - - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/Int64Extensions.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/Int64Extensions.cs deleted file mode 100644 index e73e4acb..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/Int64Extensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure; - -/// -/// Defines extensions for s -/// -public static class Int64Extensions -{ - - /// - /// Converts an into a new value - /// - /// The value to convert - /// A new value - public static FromStream ToSubscriptionPosition(this long value) - { - return value switch - { - StreamPosition.StartOfStream => FromStream.Start, - StreamPosition.EndOfStream => FromStream.End, - _ => FromStream.After(EventStore.Client.StreamPosition.FromInt64(value)) - }; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/StreamReadDirectionExtensions.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/StreamReadDirectionExtensions.cs deleted file mode 100644 index 1df36720..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Extensions/StreamReadDirectionExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure; - -/// -/// Defines extensions for s -/// -public static class StreamReadDirectionExtensions -{ - - /// - /// Converts the into a - /// - /// The to convert - /// The converted - public static Direction ToDirection(this StreamReadDirection readDirection) - { - return readDirection switch - { - StreamReadDirection.Forwards => Direction.Forwards, - StreamReadDirection.Backwards => Direction.Backwards, - _ => throw new NotSupportedException($"The specified stream read direction '{readDirection}' is not supported") - }; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Models/PartitionsMetadataProjectionResult.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Models/PartitionsMetadataProjectionResult.cs deleted file mode 100644 index ed5db6a1..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Models/PartitionsMetadataProjectionResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; -using System.Text.Json.Serialization; - -namespace CloudStreams.Core.Infrastructure.Models; - -/// -/// Represents the results of the partition of a metadata projection -/// -internal class PartitionsMetadataProjectionResult -{ - - /// - /// Gets/Sets existing ids of a - /// - [JsonPropertyName("keys")] - public List Keys { get; set; } = null!; - - /// - /// Gets/Sets the metadata entries of a - /// - [JsonPropertyName("values")] - public Dictionary Values { get; set; } = null!; - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStore.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStore.cs deleted file mode 100644 index a81b34de..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStore.cs +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; -using Hylo.Properties; -using System.Net; -using System.Net.Mime; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Runtime.CompilerServices; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the EventStore based implementation of the interface -/// -public class ESEventStore - : BackgroundService, IEventStore -{ - - /// - /// Initializes a new - /// - /// The service used to create s - /// The service used to interact with the EventStore streams API - /// The service used to interact with the EventStore projections API - public ESEventStore(ILoggerFactory loggerFactory, EventStoreClient streams, EventStoreProjectionManagementClient projections) - { - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.Streams = streams; - this.Projections = projections; - } - - /// - /// Gets the service used to perform logging - /// - protected ILogger Logger { get; } - - /// - /// Gets the service used to interact with the EventStore streams API - /// - protected EventStoreClient Streams { get; set; } - - /// - /// Gets the service used to interact with the EventStore projections API - /// - protected EventStoreProjectionManagementClient Projections { get; set; } - - /// - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - await this.SetupProjectionsAsync(stoppingToken).ConfigureAwait(false); - } - - /// - public virtual async Task AppendAsync(CloudEventDescriptor e, CancellationToken cancellationToken = default) - { - if (e == null) throw new ArgumentNullException(nameof(e)); - var streamName = EventStoreStreams.All; - var eventData = await this.SerializeToEventDataAsync(e, cancellationToken).ConfigureAwait(false); - var writeResult = await this.Streams.AppendToStreamAsync(streamName, StreamState.Any, new EventData[] { eventData }, cancellationToken: cancellationToken).ConfigureAwait(false); - return new(streamName, writeResult.NextExpectedStreamRevision.ToUInt64(), e.Metadata, e.Data); - } - - /// - public virtual async Task GetStreamMetadataAsync(CancellationToken cancellationToken = default) - { - try - { - var firstEvent = (await this.Streams.ReadStreamAsync(Direction.Forwards, EventStoreStreams.All, EventStore.Client.StreamPosition.Start, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); - var lastEvent = (await this.Streams.ReadStreamAsync(Direction.Backwards, EventStoreStreams.All, EventStore.Client.StreamPosition.End, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); - return new() - { - FirstEvent = firstEvent.OriginalEvent.Created, - LastEvent = lastEvent.OriginalEvent.Created, - Length = lastEvent.OriginalEventNumber + 1 - }; - } - catch (StreamNotFoundException) - { - return new(); - } - } - - /// - public virtual async Task GetPartitionMetadataAsync(PartitionReference partition, CancellationToken cancellationToken = default) - { - try - { - var streamName = partition.GetStreamName(); - var firstEvent = (await this.Streams.ReadStreamAsync(Direction.Forwards, streamName, EventStore.Client.StreamPosition.Start, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); - var lastEvent = (await this.Streams.ReadStreamAsync(Direction.Backwards, streamName, EventStore.Client.StreamPosition.End, 1, cancellationToken: cancellationToken).ToListAsync(cancellationToken)).Single(); - return new() - { - Id = partition.Id, - Type = partition.Type, - FirstEvent = firstEvent.OriginalEvent.Created, - LastEvent = lastEvent.OriginalEvent.Created, - Length = lastEvent.OriginalEventNumber + 1 - }; - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream '{partition.GetStreamName()}' of the partition of type '{EnumHelper.Stringify(partition.Type)}' with id '{partition.Id}'. Related projection does not exist or has not been properly configured")); - } - } - - /// - public virtual async IAsyncEnumerable ListPartitionIdsAsync(CloudEventPartitionType partitionType, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var typeName = EnumHelper.Stringify(partitionType).Replace('-', '_'); - List? partitionsIds = null; - try - { - partitionsIds = await this.Projections.GetResultAsync>(EventStoreProjections.CloudEventPartitionsMetadataPrefix + typeName, cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch { } - if (partitionsIds == null) yield break; - await foreach(var id in partitionsIds.ToAsyncEnumerable()) - { - yield return id; - } - } - - /// - public virtual async IAsyncEnumerable ReadAsync(StreamReadDirection readDirection, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var streamName = EventStoreStreams.All; - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - if(await readResult.ReadState.ConfigureAwait(false) == ReadState.StreamNotFound) yield break; - } - catch (StreamNotFoundException) - { - yield break; - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - public virtual IAsyncEnumerable ReadPartitionAsync(PartitionReference partition, StreamReadDirection readDirection, long offset, ulong? length = null, CancellationToken cancellationToken = default) - { - switch (partition.Type) - { - case CloudEventPartitionType.BySource: - if (!Uri.TryCreate(partition.Id, UriKind.RelativeOrAbsolute, out var source)) throw new Exception(); - return this.ReadBySourceAsync(readDirection, source, offset, length, cancellationToken); - case CloudEventPartitionType.BySubject: - return this.ReadBySubjectAsync(readDirection, partition.Id, offset, length, cancellationToken); - case CloudEventPartitionType.ByType: - return this.ReadByTypeAsync(readDirection, partition.Id, offset, length, cancellationToken); - case CloudEventPartitionType.ByCorrelationId: - return this.ReadByCorrelationIdAsync(readDirection, partition.Id, offset, length, cancellationToken); - case CloudEventPartitionType.ByCausationId: - return this.ReadByCausationIdAsync(readDirection, partition.Id, offset, length, cancellationToken); - default: - throw new NotSupportedException($"The specified {nameof(CloudEventPartitionType)} '{partition.Type}' is not supported"); - } - } - - /// - public virtual async Task> SubscribeAsync(long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default) - { - var subject = new Subject(); - var subscription = await this.Streams.SubscribeToStreamAsync( - EventStoreStreams.All, - offset.ToSubscriptionPosition(), - async (sub, e, cancellation) => subject.OnNext(await this.DeserializeResolvedEventAsync(e, cancellation).ConfigureAwait(false)), - cancellationToken: cancellationToken) - .ConfigureAwait(false); - return Observable.Using - ( - () => subscription, - watch => subject - ); - } - - /// - public virtual async Task> SubscribeToPartitionAsync(PartitionReference partition, long offset = StreamPosition.EndOfStream, CancellationToken cancellationToken = default) - { - var subject = new Subject(); - var subscription = await this.Streams.SubscribeToStreamAsync( - partition.GetStreamName(), - offset.ToSubscriptionPosition(), - async (sub, e, cancellation) => subject.OnNext(await this.DeserializeResolvedEventAsync(e, cancellation).ConfigureAwait(false)), - true, - cancellationToken: cancellationToken) - .ConfigureAwait(false); - return Observable.Using - ( - () => subscription, - watch => subject - ); - } - - /// - /// Reads stored s by source - /// - /// The direction in which to read - /// The source of the s to read - /// The offset starting from which to read events - /// The amount of s to read - /// A - /// A new containing the s read from the store - protected virtual async IAsyncEnumerable ReadBySourceAsync(StreamReadDirection readDirection, Uri source, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - var streamName = EventStoreStreams.ByCloudEventSource(source); - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.BySource)}'. Related projection does not exist or has not been properly configured")); - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - /// Reads stored s by subject - /// - /// The direction in which to read - /// The subject of the s to read - /// The offset starting from which to read events - /// The amount of s to read - /// A - /// A new containing the s read from the store - /// - protected virtual async IAsyncEnumerable ReadBySubjectAsync(StreamReadDirection readDirection, string subject, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(subject)) throw new ArgumentNullException(nameof(subject)); - var streamName = EventStoreStreams.ByCloudEventSubject(subject); - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.BySubject)}'. Related projection does not exist or has not been properly configured")); - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - /// Reads stored s by type - /// - /// The direction in which to read - /// The type of the s to read - /// The offset starting from which to read events - /// The amount of s to read - /// A - /// A new containing the s read from the store - /// - protected virtual async IAsyncEnumerable ReadByTypeAsync(StreamReadDirection readDirection, string type, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(type)) throw new ArgumentNullException(nameof(type)); - var streamName = EventStoreStreams.ByCloudEventType(type); - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByType)}'. Related projection does not exist or has not been properly configured")); - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - /// Reads stored s by correlation id - /// - /// The direction in which to read - /// The correlation id of the s to read - /// The offset starting from which to read events - /// The amount of s to read - /// A - /// A new containing the s read from the store - protected virtual async IAsyncEnumerable ReadByCorrelationIdAsync(StreamReadDirection readDirection, string correlationId, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(correlationId)) throw new ArgumentNullException(nameof(correlationId)); - var streamName = EventStoreStreams.ByCorrelationId(correlationId); - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int) HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByCorrelationId)}'. Related projection does not exist or has not been properly configured")); - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - /// Reads stored s by causation id - /// - /// The direction in which to read - /// The causation id of the s to read - /// The offset starting from which to read events - /// The amount of s to read - /// A - /// A new containing the s read from the store - protected virtual async IAsyncEnumerable ReadByCausationIdAsync(StreamReadDirection readDirection, string causationId, long offset, ulong? length = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(causationId)) throw new ArgumentNullException(nameof(causationId)); - var streamName = EventStoreStreams.ByCausationId(causationId); - var resolveLinkTos = true; - var position = offset == StreamPosition.EndOfStream ? EventStore.Client.StreamPosition.End : EventStore.Client.StreamPosition.FromInt64(offset); - EventStoreClient.ReadStreamResult readResult; - try - { - readResult = this.Streams.ReadStreamAsync(readDirection.ToDirection(), streamName, position, (long)(length ?? long.MaxValue), resolveLinkTos, cancellationToken: cancellationToken); - } - catch (StreamNotFoundException) - { - throw new HyloException(new ProblemDetails(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, $"Failed to find the stream that belongs to the partition of type '{EnumHelper.Stringify(CloudEventPartitionType.ByCausationId)}'. Related projection does not exist or has not been properly configured")); - } - await foreach (var resolvedEvent in readResult) - { - yield return await this.DeserializeResolvedEventAsync(resolvedEvent, cancellationToken); - } - } - - /// - public virtual async Task TruncateAsync(long beforeVersion, CancellationToken cancellationToken = default) - { - var streamName = EventStoreStreams.All; - await this.Streams.SetStreamMetadataAsync(streamName, StreamState.Any, new EventStore.Client.StreamMetadata(truncateBefore: EventStore.Client.StreamPosition.FromInt64(beforeVersion)), cancellationToken: cancellationToken); - } - - /// - /// Creates and configures the projections required by Cloud Streams - /// - /// A - /// A new awaitable - protected virtual async Task SetupProjectionsAsync(CancellationToken cancellationToken = default) - { - try - { - if (await this.Projections.GetStatusAsync(EventStoreProjections.PartitionBySource, cancellationToken: cancellationToken) != null) return; - } - catch { } - - await this.Projections.EnableAsync(EventStoreProjections.BuiltInProjections.Streams, cancellationToken: cancellationToken); - await this.Projections.EnableAsync(EventStoreProjections.BuiltInProjections.PartitionByEventType, cancellationToken: cancellationToken); - await this.Projections.EnableAsync(EventStoreProjections.BuiltInProjections.PartitionByCorrelationId, cancellationToken: cancellationToken); - - await this.Projections.AbortAsync(EventStoreProjections.BuiltInProjections.PartitionByCategory, cancellationToken: cancellationToken); - await this.Projections.DisableAsync(EventStoreProjections.BuiltInProjections.PartitionByCategory, cancellationToken: cancellationToken); - - await this.Projections.AbortAsync(EventStoreProjections.BuiltInProjections.StreamByCategory, cancellationToken: cancellationToken); - await this.Projections.DisableAsync(EventStoreProjections.BuiltInProjections.StreamByCategory, cancellationToken: cancellationToken); - - Stream stream; - StreamReader streamReader; - string query; - - stream = typeof(ESEventStore).Assembly.GetManifestResourceStream(string.Join('.', typeof(EventStoreProjections).Namespace, "Assets", "Projections", "bysource.js"))!; - streamReader = new StreamReader(stream); - query = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - streamReader.Dispose(); - await this.Projections.CreateContinuousAsync(EventStoreProjections.PartitionBySource, query, true, cancellationToken: cancellationToken).ConfigureAwait(false); - - stream = typeof(ESEventStore).Assembly.GetManifestResourceStream(string.Join('.', typeof(EventStoreProjections).Namespace, "Assets", "Projections", "bysubject.js"))!; - streamReader = new StreamReader(stream); - query = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - streamReader.Dispose(); - await this.Projections.CreateContinuousAsync(EventStoreProjections.PartitionBySubject, query, true, cancellationToken: cancellationToken).ConfigureAwait(false); - - stream = typeof(ESEventStore).Assembly.GetManifestResourceStream(string.Join('.', typeof(EventStoreProjections).Namespace, "Assets", "Projections", "bycausationid.js"))!; - streamReader = new StreamReader(stream); - query = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - streamReader.Dispose(); - await this.Projections.CreateContinuousAsync(EventStoreProjections.PartitionByCausationId, query, true, cancellationToken: cancellationToken).ConfigureAwait(false); - - stream = typeof(ESEventStore).Assembly.GetManifestResourceStream(string.Join('.', typeof(EventStoreProjections).Namespace, "Assets", "Projections", "partitionids.js.tmpl"))!; - streamReader = new StreamReader(stream); - query = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - streamReader.Dispose(); - foreach (var partitionType in Enum.GetValues()) - { - var typeName = EnumHelper.Stringify(partitionType).Replace('-', '_'); - var metadataPath = typeName.Replace("by", string.Empty).ToCamelCase(); - if (partitionType == CloudEventPartitionType.ByCorrelationId || partitionType == CloudEventPartitionType.ByCausationId) - { - metadataPath = "$" + metadataPath.Replace("-id", "Id"); - } - else - { - metadataPath = "contextAttributes." + metadataPath; - } - await this.Projections.CreateContinuousAsync(EventStoreProjections.CloudEventPartitionsMetadataPrefix + typeName, query.Replace("##metadataPath##", metadataPath), true, cancellationToken: cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Serializes the specified into a new - /// - /// An object that describes the to serialize - /// A - /// The serialized - protected virtual Task SerializeToEventDataAsync(CloudEventDescriptor eventDescriptor, CancellationToken cancellationToken) - { - if (eventDescriptor == null) throw new ArgumentNullException(nameof(eventDescriptor)); - var e = eventDescriptor.ToCloudEvent(); - var id = Uuid.NewUuid(); - var type = e.Type!; - var contextAttributes = Serializer.Json.Deserialize>(Serializer.Json.Serialize(eventDescriptor))!; - contextAttributes.Remove(CloudEventAttributes.Data); - var dataContentType = string.IsNullOrWhiteSpace(e.DataContentType) ? MediaTypeNames.Application.Json : e.DataContentType; - var dataRaw = dataContentType switch - { - MediaTypeNames.Application.Json => Encoding.UTF8.GetBytes(Serializer.Json.Serialize(eventDescriptor.Data)), - "application/x-yaml" or "text/yaml" or "text/x-yaml" => Encoding.UTF8.GetBytes(Serializer.Yaml.Serialize(eventDescriptor.Data)), - MediaTypeNames.Application.Octet => eventDescriptor.Data is byte[] byteArray ? byteArray : eventDescriptor.Data is string base64String ? Convert.FromBase64String(base64String) - : throw new NotSupportedException($"The cloud event payload must be an array of bytes or a base 64 encoded string when data content type has been set to '{MediaTypeNames.Application.Octet}'"), - _ => throw new NotSupportedException($"The specified cloud event data content type '{dataContentType}' is not supported") - }; - var metadataRaw = Encoding.UTF8.GetBytes(Serializer.Json.Serialize(eventDescriptor.Metadata)); - return Task.FromResult(new EventData(id, type, dataRaw, metadataRaw, dataContentType)); - } - - /// - /// Deserializes the specified into a new - /// - /// The to convert - /// A - /// A new - protected virtual async Task DeserializeResolvedEventAsync(ResolvedEvent e, CancellationToken cancellationToken) - { - var data = e.Event.ContentType switch - { - MediaTypeNames.Application.Json => Serializer.Json.Deserialize(e.Event.Data.Span), - "application/x-yaml" or "text/yaml" or "text/x-yaml" => Serializer.Yaml.Deserialize(Encoding.UTF8.GetString(e.Event.Data.Span)), - MediaTypeNames.Application.Octet => e.Event.Data, - _ => throw new NotSupportedException($"The specified cloud event data content type '{e.Event.ContentType}' is not supported") - }; - var metadata = Serializer.Json.Deserialize(e.Event.Metadata.Span)!; - return await Task.FromResult(new CloudEventRecord(e.OriginalStreamId, e.OriginalEventNumber.ToUInt64(), metadata, data)); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProvider.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProvider.cs deleted file mode 100644 index f8cffc79..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProvider.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Infrastructure; -using Hylo.Infrastructure.Services; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the EventStore implementation of the interface -/// -[Plugin(typeof(IEventStoreProvider), typeof(ESEventStoreProviderPluginBootstrapper))] -public class ESEventStoreProvider - : IEventStoreProvider, IDisposable, IAsyncDisposable -{ - - private bool _disposed; - - /// - /// Initializes a new - /// - /// The current - public ESEventStoreProvider(IServiceProvider services) - { - this.Services = services; - } - - /// - /// Gets the current - /// - protected IServiceProvider Services { get; } - - /// - public virtual IEventStore GetEventStore() => this.Services.GetRequiredService(); - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - /// A new awaitable - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - await ValueTask.CompletedTask; - } - - /// - public async ValueTask DisposeAsync() - { - await this.DisposeAsync(true).ConfigureAwait(false); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - protected virtual void Dispose(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProviderPluginBootstrapper.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProviderPluginBootstrapper.cs deleted file mode 100644 index 67a0bb2c..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Services/ESEventStoreProviderPluginBootstrapper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.EventSourcing.EventStore; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.Configuration; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the implementation used to configure the plugin -/// -public class ESEventStoreProviderPluginBootstrapper - : IPluginBootstrapper -{ - - const string ConnectionStringName = "eventstore"; - - /// - /// Initializes a new - /// - /// The current application's services - public ESEventStoreProviderPluginBootstrapper(IServiceProvider applicationServices) - { - this.ApplicationServices = applicationServices; - } - - /// - /// Gets the current application's services - /// - protected IServiceProvider ApplicationServices { get; } - - /// - public virtual void ConfigureServices(IServiceCollection services) - { - var configuration = this.ApplicationServices.GetRequiredService(); - var connectionString = configuration.GetConnectionString(ConnectionStringName); - if (string.IsNullOrWhiteSpace(connectionString)) throw new Exception($"Failed to find the '{ConnectionStringName}' connection string"); - services.AddLogging(); - services.AddEventStore(EventStoreClientSettings.Create(connectionString)); - services.TryAddSingleton(); - services.AddSingleton(provider => provider.GetRequiredService()); - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Usings.cs b/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Usings.cs deleted file mode 100644 index a96846ff..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.EventSourcing.EventStore/Usings.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -global using CloudStreams.Core.Infrastructure.Services; -global using EventStore.Client; -global using Hylo; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; -global using System.Text; -global using System.Text.Json.Nodes; diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ.csproj b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ.csproj deleted file mode 100644 index 51f4a0ce..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;runtime-expressions;jq; - Contains the default JQ implementation of a Cloud Streams runtime expression evaluator - logo.png - Apache-2.0 - True - True - true - - - - - \ - True - - - \ - True - - - - - - - - diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Extensions/ICloudStreamsApiBuilderExtensions.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Extensions/ICloudStreamsApiBuilderExtensions.cs deleted file mode 100644 index cb7e06c2..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Extensions/ICloudStreamsApiBuilderExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Core.Infrastructure.Configuration; - -/// -/// Defines extensions for s -/// -public static class ICloudStreamsApiBuilderExtensions -{ - - /// - /// Configures Cloud Streams to use the JQ implementation of the interface - /// - /// The to configure - /// The configured - public static ICloudStreamsApplicationBuilder UseJQExpressionEvaluator(this ICloudStreamsApplicationBuilder builder) - { - builder.Services.TryAddSingleton(); - return builder; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluator.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluator.cs deleted file mode 100644 index 3399a92e..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluator.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the JQ implementation of the interface -/// -public class JQExpressionEvaluator - : IExpressionEvaluator -{ - - /// - public object? Evaluate(string expression, object input, IDictionary? arguments = null, Type? expectedType = null, CancellationToken cancellationToken = default) - { - if(string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - if(input == null) throw new ArgumentNullException(nameof(input)); - if (expectedType == null) expectedType = typeof(object); - - expression = expression.Trim(); - if (expression.StartsWith("${")) expression = expression[2..^1].Trim(); - if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - - var startInfo = new ProcessStartInfo() - { - FileName = "jq", - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - startInfo.ArgumentList.Add(expression); - if(arguments != null) - { - foreach (var kvp in arguments.ToDictionary(a => a.Key, a => Hylo.Serializer.Json.Serialize(a.Value))) - { - startInfo.ArgumentList.Add("--argjson"); - startInfo.ArgumentList.Add(kvp.Key); - startInfo.ArgumentList.Add(kvp.Value); - } - } - var files = new List(); - var maxLength = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 8000 : 32699; - if (startInfo.ArgumentList.Any(a => a.Length >= maxLength)) - { - startInfo.ArgumentList.Clear(); - var filterFile = Path.GetTempFileName(); - File.WriteAllText(filterFile, expression); - files.Add(filterFile); - startInfo.ArgumentList.Add("-f"); - startInfo.ArgumentList.Add(filterFile); - if (arguments?.Any() == true) - { - foreach (var kvp in arguments) - { - var argFile = Path.GetTempFileName(); - File.WriteAllText(argFile, Hylo.Serializer.Json.Serialize(kvp.Value)); - files.Add(argFile); - startInfo.ArgumentList.Add("--argfile"); - startInfo.ArgumentList.Add(kvp.Key); - startInfo.ArgumentList.Add(argFile); - } - } - } - startInfo.ArgumentList.Add("-c"); - - using var process = new Process() { StartInfo = startInfo }; - var cancellationRegistration = cancellationToken.Register(() => { - try - { - process.Kill(); - } - catch { } - }); - process.Start(); - process.StandardInput.Write(Hylo.Serializer.Json.Serialize(input)); - process.StandardInput.Close(); - var output = process.StandardOutput.ReadToEnd(); - var error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - cancellationRegistration.Unregister(); - cancellationRegistration.Dispose(); - - foreach (var file in files) - { - try { File.Delete(file); } catch { } - } - - if (process.ExitCode != 0) throw new Exception($"An error occured while evaluting the specified expression: {error}"); - if (string.IsNullOrWhiteSpace(output)) return null; - return Hylo.Serializer.Json.Deserialize(output, expectedType); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorPluginBootstrapper.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorPluginBootstrapper.cs deleted file mode 100644 index c006dd44..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorPluginBootstrapper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the implementation used to configure the plugin -/// -public class JQExpressionEvaluatorPluginBootstrapper - : IPluginBootstrapper -{ - - const string ConnectionStringName = "eventstore"; - - /// - /// Initializes a new - /// - /// The current application's services - public JQExpressionEvaluatorPluginBootstrapper(IServiceProvider applicationServices) - { - this.ApplicationServices = applicationServices; - } - - /// - /// Gets the current application's services - /// - protected IServiceProvider ApplicationServices { get; } - - /// - public virtual void ConfigureServices(IServiceCollection services) - { - var configuration = this.ApplicationServices.GetRequiredService(); - var connectionString = configuration.GetConnectionString(ConnectionStringName); - if (string.IsNullOrWhiteSpace(connectionString)) throw new Exception($"Failed to find the '{ConnectionStringName}' connection string"); - services.TryAddSingleton(); - } - - - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorProvider.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorProvider.cs deleted file mode 100644 index e6beea04..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JQ/Services/JQExpressionEvaluatorProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Infrastructure; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the default JQ implementation of the interface -/// -[Plugin(typeof(IExpressionEvaluatorProvider), typeof(JQExpressionEvaluatorPluginBootstrapper))] -public class JQExpressionEvaluatorProvider - : IExpressionEvaluatorProvider, IDisposable, IAsyncDisposable -{ - - private bool _disposed; - - /// - /// Initializes a new - /// - /// The current - public JQExpressionEvaluatorProvider(IServiceProvider services) - { - this.Services = services; - } - - /// - /// Gets the current - /// - protected IServiceProvider Services { get; } - - /// - public virtual IExpressionEvaluator GetExpressionEvaluator() => this.Services.GetRequiredService(); - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - /// A new awaitable - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - await ValueTask.CompletedTask; - } - - /// - public async ValueTask DisposeAsync() - { - await this.DisposeAsync(true).ConfigureAwait(false); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - protected virtual void Dispose(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript.csproj b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript.csproj deleted file mode 100644 index 3e0d5cf2..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;runtime-expressions;js;javascript; - Contains the default JavaScript implementation of a Cloud Streams runtime expression evaluator - logo.png - Apache-2.0 - True - True - true - - - - - \ - True - - - \ - True - - - - - - - - - - - - diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ICloudStreamsApiBuilderExtensions.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ICloudStreamsApiBuilderExtensions.cs deleted file mode 100644 index 95dc3eb0..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ICloudStreamsApiBuilderExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Core.Infrastructure.Configuration; - -/// -/// Defines extensions for s -/// -public static class ICloudStreamsApiBuilderExtensions -{ - - /// - /// Configures Cloud Streams to use the JavaScript implementation of the interface - /// - /// The to configure - /// The configured - public static ICloudStreamsApplicationBuilder UseJavaScriptExpressionEvaluator(this ICloudStreamsApplicationBuilder builder) - { - builder.Services.TryAddSingleton(); - return builder; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ObjectExtensions.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ObjectExtensions.cs deleted file mode 100644 index 096a419a..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Extensions/ObjectExtensions.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Authored by Burtsev Alexey -// https://github.com/Burtsev-Alexey/net-object-deep-copy -// -// Source code is released under the MIT license. -// The MIT License (MIT) -// Copyright(c) 2014 Burtsev Alexey -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -using System.Reflection; - -#pragma warning disable 8600 -#pragma warning disable 8601 -#pragma warning disable 8602 -#pragma warning disable 8603 -#pragma warning disable 8604 -#pragma warning disable 8625 -#pragma warning disable 8765 - -namespace CloudStreams.Core.Infrastructure.Services.InternalExtensions -{ - internal static class ArrayExtensions - { - public static void ForEach(this Array array, Action action) - { - if (array.LongLength == 0) return; - ArrayTraverse walker = new ArrayTraverse(array); - do action(array, walker.Position); - while (walker.Step()); - } - } - - internal class ArrayTraverse - { - public int[] Position; - private int[] maxLengths; - - public ArrayTraverse(Array array) - { - maxLengths = new int[array.Rank]; - for (int i = 0; i < array.Rank; ++i) - { - maxLengths[i] = array.GetLength(i) - 1; - } - Position = new int[array.Rank]; - } - - public bool Step() - { - for (int i = 0; i < Position.Length; ++i) - { - if (Position[i] < maxLengths[i]) - { - Position[i]++; - for (int j = 0; j < i; j++) - { - Position[j] = 0; - } - return true; - } - } - return false; - } - } - internal static class ObjectExtensions - { - private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance); - - public static bool IsPrimitive(this Type type) - { - if (type == typeof(String)) return true; - return (type.IsValueType & type.IsPrimitive); - } - - public static Object Copy(this Object originalObject) - { - return InternalCopy(originalObject, new Dictionary(new ReferenceEqualityComparer())); - } - private static Object InternalCopy(Object originalObject, IDictionary visited) - { - if (originalObject == null) return null; - var typeToReflect = originalObject.GetType(); - if (IsPrimitive(typeToReflect)) return originalObject; - if (visited.ContainsKey(originalObject)) return visited[originalObject]; - if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null; - var cloneObject = CloneMethod.Invoke(originalObject, null); - if (typeToReflect.IsArray) - { - var arrayType = typeToReflect.GetElementType(); - if (IsPrimitive(arrayType) == false) - { - Array clonedArray = (Array)cloneObject; - clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices)); - } - - } - visited.Add(originalObject, cloneObject); - CopyFields(originalObject, visited, cloneObject, typeToReflect); - RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect); - return cloneObject; - } - - private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect) - { - if (typeToReflect.BaseType != null) - { - RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType); - CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate); - } - } - - private static void CopyFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func filter = null) - { - foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) - { - if (filter != null && filter(fieldInfo) == false) continue; - if (IsPrimitive(fieldInfo.FieldType)) continue; - var originalFieldValue = fieldInfo.GetValue(originalObject); - var clonedFieldValue = InternalCopy(originalFieldValue, visited); - fieldInfo.SetValue(cloneObject, clonedFieldValue); - } - } - public static T Copy(this T original) - { - return (T)Copy((Object)original); - } - } - - internal class ReferenceEqualityComparer : EqualityComparer - { - public override bool Equals(object x, object y) - { - return ReferenceEquals(x, y); - } - public override int GetHashCode(object obj) - { - if (obj == null) return 0; - return obj.GetHashCode(); - } - } - -} - -#pragma warning restore 8600 -#pragma warning restore 8601 -#pragma warning restore 8602 -#pragma warning restore 8603 -#pragma warning restore 8604 -#pragma warning restore 8625 -#pragma warning restore 8765 \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluator.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluator.cs deleted file mode 100644 index 340cf25c..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluator.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.Services.InternalExtensions; -using Jint; -using Jint.Runtime.Interop; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the JavaScript implementation of the interface -/// -public class JavaScriptExpressionEvaluator - : IExpressionEvaluator -{ - - /// - public object? Evaluate(string expression, object input, IDictionary? arguments = null, Type? expectedType = null, CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - if (input == null) throw new ArgumentNullException(nameof(input)); - if (expectedType == null) expectedType = typeof(object); - expression = expression.Trim(); - if (expression.StartsWith("${")) expression = expression[2..^1].Trim(); - if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - var jsEngine = new Engine(options => - { - // Limit memory allocations to 1MB - options.LimitMemory(1_000_000) - // Set a timeout to 500ms - .TimeoutInterval(TimeSpan.FromMilliseconds(500)) - // Set limit of 500 executed statements - .MaxStatements(500) - // Set limit of 16 for recursive calls - .LimitRecursion(16) - // Use a cancellation token. - .CancellationToken(cancellationToken) - // customizing object wrapping to set array prototype to objects - .SetWrapObjectHandler((engine, target) => - { - var instance = new ObjectWrapper(engine, target); - if (instance.IsArrayLike) instance.SetPrototypeOf(engine.Realm.Intrinsics.Array.PrototypeObject); - return instance; - }) - ; - }); - jsEngine.SetValue("input", input.Copy()); - if (arguments != null && arguments.Any()) - { - foreach (var argument in arguments) - { - jsEngine.SetValue(argument.Key, argument.Value.Copy()); - } - } - var result = jsEngine.Evaluate(expression).UnwrapIfPromise().ToObject(); - jsEngine.Dispose(); - if (expectedType == typeof(object)) return result; - return Hylo.Serializer.Json.Deserialize(Hylo.Serializer.Json.Serialize(result), expectedType); - } -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorPluginBootstrapper.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorPluginBootstrapper.cs deleted file mode 100644 index 82e08449..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorPluginBootstrapper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the implementation used to configure the plugin -/// -public class JavaScriptExpressionEvaluatorPluginBootstrapper - : IPluginBootstrapper -{ - - const string ConnectionStringName = "eventstore"; - - /// - /// Initializes a new - /// - /// The current application's services - public JavaScriptExpressionEvaluatorPluginBootstrapper(IServiceProvider applicationServices) - { - this.ApplicationServices = applicationServices; - } - - /// - /// Gets the current application's services - /// - protected IServiceProvider ApplicationServices { get; } - - /// - public virtual void ConfigureServices(IServiceCollection services) - { - var configuration = this.ApplicationServices.GetRequiredService(); - var connectionString = configuration.GetConnectionString(ConnectionStringName); - if (string.IsNullOrWhiteSpace(connectionString)) throw new Exception($"Failed to find the '{ConnectionStringName}' connection string"); - services.TryAddSingleton(); - } - - - -} diff --git a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorProvider.cs b/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorProvider.cs deleted file mode 100644 index c0e792fe..00000000 --- a/src/core/CloudStreams.Core.Infrastructure.RuntimeExpressions.JavaScript/Services/JavaScriptExpressionEvaluatorProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Infrastructure; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the default Javascript implementation of the interface -/// -[Plugin(typeof(IExpressionEvaluatorProvider), typeof(JavaScriptExpressionEvaluatorPluginBootstrapper))] -public class JavaScriptExpressionEvaluatorProvider - : IExpressionEvaluatorProvider -{ - - private bool _disposed; - - /// - /// Initializes a new - /// - /// The current - public JavaScriptExpressionEvaluatorProvider(IServiceProvider services) - { - this.Services = services; - } - - /// - /// Gets the current - /// - protected IServiceProvider Services { get; } - - /// - public virtual IExpressionEvaluator GetExpressionEvaluator() => this.Services.GetRequiredService(); - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - /// A new awaitable - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - await ValueTask.CompletedTask; - } - - /// - public async ValueTask DisposeAsync() - { - await this.DisposeAsync(true).ConfigureAwait(false); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - protected virtual void Dispose(bool disposing) - { - if (!disposing || this._disposed) return; - this._disposed = true; - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj b/src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj deleted file mode 100644 index 7cff7b02..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj +++ /dev/null @@ -1,49 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core;infrastructure - Contains definitions and default implementations of Cloud Streams core infrastructure services - logo.png - Apache-2.0 - True - True - - - - - \ - True - - - \ - True - - - - - - - - - - - - - - - - - diff --git a/src/core/CloudStreams.Core.Infrastructure/Extensions/IExpressionEvaluatorExtensions.cs b/src/core/CloudStreams.Core.Infrastructure/Extensions/IExpressionEvaluatorExtensions.cs deleted file mode 100644 index b08d6aae..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Extensions/IExpressionEvaluatorExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.Services; -using System.Text.Json; -using System.Text.RegularExpressions; - -namespace CloudStreams.Core.Infrastructure; - -/// -/// Defines extensions for s -/// -public static class IExpressionEvaluatorExtensions -{ - - /// - /// Evaluates the specified runtime expression - /// - /// The expected type of the evaluation's result - /// The extended - /// The runtime expression to evaluate - /// The data to evaluate the runtime expression against - /// A key/value mapping of the arguments used during evaluation, if any - /// A - /// The evaluation's result - public static TResult? Evaluate(this IExpressionEvaluator evaluator, string expression, object input, IDictionary? arguments = null, CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - if (input == null) throw new ArgumentNullException(nameof(input)); - return (TResult?)evaluator.Evaluate(expression, input, arguments, typeof(TResult), cancellationToken); - } - - /// - /// Evaluates the specified runtime expression based condition - /// - /// The extended - /// The runtime expression to evaluate - /// The data to evaluate the runtime expression against - /// A key/value mapping of the arguments used during evaluation, if any - /// A - /// The evaluation's result - public static bool EvaluateCondition(this IExpressionEvaluator evaluator, string expression, object input, IDictionary? arguments = null, CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(expression)) throw new ArgumentNullException(nameof(expression)); - if (input == null) throw new ArgumentNullException(nameof(input)); - return (bool?)evaluator.Evaluate(expression, input, arguments, typeof(bool), cancellationToken) == true; - } - - /// - /// Mutates the specified object by resolving all the runtime expressions it declares - /// - /// The extended - /// The mutation to perform, that is an object that declares - at any depth - runtime expressions to resolve against the specified data and arguments - /// The data to evaluate the runtime expression against - /// A key/value mapping of the arguments used during evaluation, if any - /// The expected type of the mutated object - /// A - /// The mutated object - public static object? Mutate(this IExpressionEvaluator evaluator, object mutation, object input, IDictionary? arguments = null, Type? expectedType = null, CancellationToken cancellationToken = default) - { - if (mutation == null) throw new ArgumentNullException(nameof(mutation)); - if (input == null) throw new ArgumentNullException(nameof(input)); - if (expectedType == null) expectedType= typeof(object); - if (mutation is string mutationExpression) return evaluator.Evaluate(mutationExpression, input, arguments, expectedType, cancellationToken); - else if(mutation is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.String) return evaluator.Evaluate(Hylo.Serializer.Json.Deserialize(jsonElement)!, input, arguments, expectedType, cancellationToken: cancellationToken); - var json = Hylo.Serializer.Json.Serialize(mutation); - foreach (Match match in Regex.Matches(json, @"""\$\{.+?\}""", RegexOptions.Compiled)) - { - var expression = match.Value[3..^2].Trim(); - var evaluationResult = evaluator.Evaluate(expression, input, arguments, cancellationToken: cancellationToken); - var value = Hylo.Serializer.Json.Serialize(evaluationResult); - if (string.IsNullOrEmpty(value)) value = "null"; - json = json.Replace(match.Value, value); - } - return Hylo.Serializer.Json.Deserialize(json, expectedType); - } - - /// - /// Mutates the specified object by resolving all the runtime expressions it declares - /// - /// The expected type of the mutated object - /// The extended - /// The mutation to perform, that is an object that declares - at any depth - runtime expressions to resolve against the specified data and arguments - /// The data to evaluate the runtime expression against - /// A key/value mapping of the arguments used during evaluation, if any - /// A - /// The mutated object - public static TResult? Mutate(this IExpressionEvaluator evaluator, object mutation, object input, IDictionary? arguments = null, CancellationToken cancellationToken = default) - { - return (TResult?)evaluator.Mutate(mutation, input, arguments, typeof(TResult), cancellationToken); - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Extensions/OperationResultExtensions.cs b/src/core/CloudStreams.Core.Infrastructure/Extensions/OperationResultExtensions.cs deleted file mode 100644 index a8b0b592..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Extensions/OperationResultExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure; - -/// -/// Defines extensions for s -/// -public static class OperationResultExtensions -{ - - /// - /// Determines whether or not the defines a success status - /// - /// The to check - /// A boolean indicating whether or not the defines a success status - public static bool IsSuccessStatusCode(this OperationResult result) => result.Status is >= 200 and < 300; - - /// - /// Converts the to a new - /// - /// The type of content wrapped by the - /// The to convert - /// A new - public static OperationResult OfType(this OperationResult result) - { - if (result == null) throw new ArgumentNullException(nameof(result)); - return new(result.Type!, result.Title!, result.Status, result.Detail, result.Instance, result.Errors?.ToDictionary(e => e.Key, e => e.Value), result.ExtensionData); - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/OperationResult.cs b/src/core/CloudStreams.Core.Infrastructure/OperationResult.cs deleted file mode 100644 index a840fa2c..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/OperationResult.cs +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Hylo.Properties; -using System.Net; -using System.Runtime.Serialization; -using System.Text.Json.Serialization; -using YamlDotNet.Serialization; - -namespace CloudStreams.Core.Infrastructure; - -/// -/// Represents an object used to describe the result of an operation -/// -[DataContract] -public record OperationResult - : ProblemDetails -{ - - /// - /// Initializes a new - /// - public OperationResult() { } - - /// - /// Initialize a new successfull - /// - /// The response's status code - /// The response's content - public OperationResult(int status, object? content = null) - { - if (!status.IsSuccessStatusCode()) throw new NotSupportedException("This constructor can only be used for successfull responses"); - this.Status = status; - this.Content = content; - } - - /// - public OperationResult(Uri type, string title, int status, string? detail = null, Uri? instance = null, IDictionary? errors = null, IDictionary? extensionData = null) - : base(type, title, status, detail, instance, errors, extensionData) - { - - } - - /// - [DataMember(Name = "content", Order = 10), JsonPropertyOrder(10), JsonPropertyName("content"), YamlMember(Order = 10, Alias = "content")] - public virtual object? Content { get; set; } - - /// - /// Creates a new to describe a successfull operation - /// - /// A new - public static OperationResult Ok() => new((int)HttpStatusCode.OK); - - /// - /// Creates a new to describe a successfull operation - /// - /// The type of content - /// The content to wrap - /// A new - public static OperationResult Ok(TContent content) => new((int)HttpStatusCode.OK, content: content); - - /// - /// Creates a new to describe a successfull operation - /// - /// A new - public static OperationResult Accepted() => new((int)HttpStatusCode.Accepted); - - /// - /// Creates a new to describe the successfull creation of the specified content - /// - /// A new - public static OperationResult Created() => new((int)HttpStatusCode.Created); - - /// - /// Creates a new to describe the successfull creation of the specified content - /// - /// The type of content - /// The content to wrap - /// A new - public static OperationResult Created(TContent content) => new((int)HttpStatusCode.Created, content: content); - - /// - /// Creates a new to describe failure to modify a resource - /// - /// A new - public static OperationResult NotModified() - { - return new(ProblemTypes.NotModified, ProblemTitles.NotModified, (int)HttpStatusCode.NotModified, Properties.ProblemDetails.NotModified); - } - - /// - /// Creates a new to describe failure to modify a resource - /// - /// The type of content - /// A new - public static OperationResult NotModified() - { - return new(ProblemTypes.NotModified, ProblemTitles.NotModified, (int)HttpStatusCode.NotModified, Properties.ProblemDetails.NotModified); - } - - /// - /// Creates a new to describe failure due to a forbidden operation - /// - /// A new - public static OperationResult Forbidden() => new((int)HttpStatusCode.Forbidden); //todo: add title and message - - /// - /// Creates a new to inform about a validation failure - /// - /// Describes the validation error - /// A new - public static OperationResult ValidationFailed(string? detail = null) - { - return new(ProblemTypes.ValidationFailed, ProblemTitles.ValidationFailed, (int)HttpStatusCode.BadRequest, detail); - } - - /// - /// Creates a new to inform about a validation failure - /// - /// An object that represents the validation results - /// A new - public static OperationResult ValidationFailed(EvaluationResults evaluationResults) - { - var errors = evaluationResults.ToErrorList()?.ToDictionary(e => e.Key, e => e.Value); - return new(ProblemTypes.ValidationFailed, ProblemTitles.ValidationFailed, (int)HttpStatusCode.BadRequest) - { - Errors = errors == null ? null : new(errors) - }; - } - - /// - /// Creates a new that describes failure due to validation problems - /// - /// The errors that have occured during validation - /// A new - public static OperationResult ValidationFailed(params KeyValuePair[] errors) - { - return new(ProblemTypes.ValidationFailed, ProblemTitles.ValidationFailed, (int)HttpStatusCode.BadRequest) - { - Errors = new(errors.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)) - }; - } - - /// - /// Creates a new that describes failure due to validation problems - /// - /// The expected type of result - /// The errors that have occured during validation - /// A new - public static OperationResult ValidationFailed(params KeyValuePair[] errors) - { - return new(ProblemTypes.ValidationFailed, ProblemTitles.ValidationFailed, (int)HttpStatusCode.BadRequest) - { - Errors = new(errors.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)) - }; - } - - /// - /// Creates a new that describes failure due to validation problems - /// - /// The expected type of result - /// The that describes the validation errors that have occured - /// A new - public static OperationResult ValidationFailed(EvaluationResults evaluationResults) - { - var errors = evaluationResults.ToErrorList()?.ToDictionary(e => e.Key, e => e.Value); - return new(ProblemTypes.ValidationFailed, ProblemTitles.ValidationFailed, (int)HttpStatusCode.BadRequest) - { - Errors = errors == null ? null : new(errors) - }; - } - - /// - /// Creates a new that describes the failure to find an of the specified type - /// - /// The expected type of result - /// The name of the resource that could not be found - /// The namespace the resource that could not be found belongs to - /// A new that describes the failure to find an of the specified type - public static OperationResult ResourceNotFound(string name, string? @namespace = null) - where TResource : class, IResource, new() - { - var resource = new TResource(); - if (string.IsNullOrWhiteSpace(@namespace)) return new(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.ClusterResourceNotFound, resource.GetGroup(), resource.GetVersion(), resource.Kind, name)); - else return new(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.NamespacedResourceNotFound, resource.GetGroup(), resource.GetVersion(), resource.Kind, resource.GetNamespace()!, name)); - } - - /// - /// Creates a new that describes the failure to find an of the specified type - /// - /// The expected type of result - /// A reference to the resource that could not be found - /// A new that describes the failure to find an of the specified type - public static OperationResult ResourceNotFound(IResourceReference reference) - { - if (!string.IsNullOrWhiteSpace(reference.Namespace)) return new(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.NamespacedResourceNotFound, reference.Definition.Group, reference.Definition.Version, reference.Definition.Plural, reference.Name)); - else return new(ProblemTypes.ResourceNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.ClusterResourceNotFound, reference.Definition.Group, reference.Definition.Version, reference.Definition.Plural, reference.Name)); - } - - /// - /// Creates a new that describes the failure to find an for the specified type type - /// - /// The type of the of could not be found - /// A new that describes the failure to find an of the specified type - public static OperationResult ResourceDefinitionNotFound() - where TResource : class, IResource, new() - { - var resource = new TResource(); - return new(ProblemTypes.ResourceDefinitionNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.ResourceDefinitionNotFound, resource.GetGroup(), resource.GetVersion(), resource.Definition.Plural)); - } - - /// - /// Creates a new that describes the failure to find an for the specified type type - /// - /// The type of the of could not be found - /// The expected type of result - /// A new that describes the failure to find an of the specified type - public static OperationResult ResourceDefinitionNotFound() - where TResource : class, IResource, new() - { - var resource = new TResource(); - return new(ProblemTypes.ResourceDefinitionNotFound, ProblemTitles.NotFound, (int)HttpStatusCode.NotFound, StringExtensions.Format(Properties.ProblemDetails.ResourceDefinitionNotFound, resource.GetGroup(), resource.GetVersion(), resource.Definition.Plural)); - } - -} - -/// -/// Describes a response produced to a request -/// -/// The type of content wrapped by the -public record OperationResult - : OperationResult -{ - - /// - public OperationResult() { } - - /// - public OperationResult(int status, object? content = null) : base(status, content) { } - - /// - public OperationResult(Uri type, string title, int status, string? detail = null, Uri? instance = null, IDictionary? errors = null, IDictionary? extensionData = null) - : base(type, title, status, detail, instance, errors, extensionData) - { - - } - - /// - [IgnoreDataMember, JsonIgnore, YamlIgnore] - public new TContent? Content - { - get - { - return (TContent?)base.Content; - } - set - { - base.Content = value; - } - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/DatabaseInitializer.cs b/src/core/CloudStreams.Core.Infrastructure/Services/DatabaseInitializer.cs deleted file mode 100644 index 7ca4bd37..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/DatabaseInitializer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.Logging; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -public class DatabaseInitializer - : Hylo.Infrastructure.Services.DatabaseInitializer -{ - - /// - public DatabaseInitializer(ILoggerFactory loggerFactory, IDatabaseProvider databaseProvider) : base(loggerFactory, databaseProvider) { } - - /// - protected override async Task SeedAsync(CancellationToken cancellationToken) - { - await base.SeedAsync(cancellationToken).ConfigureAwait(false); - await this.SeedResourceDefinitionsAsync(cancellationToken).ConfigureAwait(false); - } - - /// - /// Seeds the definitions of the resources used by CloudStreams - /// - /// A - /// A new awaitable - protected virtual async Task SeedResourceDefinitionsAsync(CancellationToken cancellationToken) - { - foreach(var definition in CloudStreamsDefaults.Resources.Definitions.AsEnumerable()) - { - await this.DatabaseProvider.GetDatabase().CreateResourceAsync(definition, cancellationToken: cancellationToken).ConfigureAwait(false); - } - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStoreProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStoreProvider.cs deleted file mode 100644 index f2d17eb8..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IEventStoreProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Defines the fundamentals of a service used to provide an implementation -/// -public interface IEventStoreProvider -{ - - /// - /// Gets the implemented - /// - /// An implementation of the interface - IEventStore GetEventStore(); - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluator.cs b/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluator.cs deleted file mode 100644 index 9001b8b5..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluator.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Defines the fundamentals of a service used to evaluate runtime expressions -/// -public interface IExpressionEvaluator -{ - - /// - /// Evaluates the specified runtime expression - /// - /// The runtime expression to evaluate - /// The data to evaluate the runtime expression against - /// A key/value mapping of the arguments used during evaluation, if any - /// The expected type of the evaluation's result - /// A - /// The evaluation's result - object? Evaluate(string expression, object input, IDictionary? arguments = null, Type? expectedType = null, CancellationToken cancellationToken = default); - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluatorProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluatorProvider.cs deleted file mode 100644 index 6aa5f093..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IExpressionEvaluatorProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Defines the fundamentals of a service used to provide an implementation of the interface -/// -public interface IExpressionEvaluatorProvider -{ - - /// - /// Gets the provided implementation - /// - /// An implementation - IExpressionEvaluator GetExpressionEvaluator(); - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistryProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistryProvider.cs deleted file mode 100644 index c7e2c5b0..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaRegistryProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Defines the fundamentals of a service used to provide an implementation -/// -public interface ISchemaRegistryProvider -{ - - /// - /// Gets the provided implementation - /// - /// An implementation of the interface - ISchemaRegistry GetSchemaRegistry(); - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/MemoryCacheSchemaRegistry.cs b/src/core/CloudStreams.Core.Infrastructure/Services/MemoryCacheSchemaRegistry.cs deleted file mode 100644 index b183a3c1..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/MemoryCacheSchemaRegistry.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Microsoft.Extensions.Caching.Memory; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents the default based implementation of the interface -/// -public class MemoryCacheSchemaRegistry - : ISchemaRegistry -{ - - /// - /// Initializes a new - /// - /// The current - public MemoryCacheSchemaRegistry(IMemoryCache memoryCache) - { - this.MemoryCache = memoryCache; - } - - /// - /// Gets the current - /// - protected IMemoryCache MemoryCache { get; } - - /// - public virtual Task RegisterSchemaAsync(JsonSchema schema, CancellationToken cancellationToken = default) - { - if(schema == null) throw new ArgumentNullException(nameof(schema)); - var id = schema.Keywords?.OfType().FirstOrDefault()?.Id.OriginalString; - if (!string.IsNullOrWhiteSpace(id)) id = Guid.NewGuid().ToString(); - var uri = schema.BaseUri ?? new Uri($"https://cloud-streams.io/schemas/{id!.ToHyphenCase()}", UriKind.Absolute); - this.MemoryCache.Set(uri, schema); - this.MemoryCache.Set(id!, schema); - return Task.FromResult(uri); - } - - /// - public virtual Task GetSchemaAsync(Uri uri, CancellationToken cancellationToken = default) - { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - this.MemoryCache.TryGetValue(uri.OriginalString, out JsonSchema? schema); - return Task.FromResult(schema); - } - - /// - public virtual Task GetSchemaUriByIdAsync(string id, CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id)); - if (!this.MemoryCache.TryGetValue(id, out JsonSchema? schema) || schema == null) return Task.FromResult((Uri?)null); - var uri = schema.BaseUri ?? new Uri($"https://cloud-streams.io/schemas/{id!.ToHyphenCase()}", UriKind.Absolute); - return Task.FromResult(uri)!; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/PluginEventStoreProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/PluginEventStoreProvider.cs deleted file mode 100644 index 483d1267..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/PluginEventStoreProvider.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Hylo.Infrastructure.Services; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents an -based implementation of the interface -/// -public class PluginEventStoreProvider - : IEventStoreProvider -{ - - /// - /// Initializes a new - /// - /// The service used to manage s - public PluginEventStoreProvider(IPluginManager pluginManager) - { - this.PluginManager = pluginManager; - } - - /// - /// Gets the service used to manage s - /// - protected IPluginManager PluginManager { get; } - - private IEventStore? _eventStore; - /// - public IEventStore GetEventStore() - { - if(this._eventStore != null) return this._eventStore; - var plugin = PluginManager.FindPluginAsync().GetAwaiter().GetResult() ?? throw new NullReferenceException("Failed to find an event store provider plugin"); - this._eventStore = plugin.GetEventStore(); - return this._eventStore; - } - -} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/PluginExpressionEvaluatorProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/PluginExpressionEvaluatorProvider.cs deleted file mode 100644 index 6b7d7c44..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/PluginExpressionEvaluatorProvider.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Hylo.Infrastructure.Services; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents an -based implementation of the interface -/// -public class PluginExpressionEvaluatorProvider - : IExpressionEvaluatorProvider -{ - - /// - /// Initializes a new - /// - /// The service used to manage s - public PluginExpressionEvaluatorProvider(IPluginManager pluginManager) - { - this.PluginManager = pluginManager; - } - - /// - /// Gets the service used to manage s - /// - protected IPluginManager PluginManager { get; } - - private IExpressionEvaluator? _expressionEvaluator; - /// - public IExpressionEvaluator GetExpressionEvaluator() - { - if (this._expressionEvaluator != null) return this._expressionEvaluator; - var plugin = PluginManager.FindPluginAsync().GetAwaiter().GetResult() ?? throw new NullReferenceException("Failed to find an expression evaluator provider plugin"); - this._expressionEvaluator = plugin.GetExpressionEvaluator(); - return this._expressionEvaluator; - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/PluginSchemaRegistryProvider.cs b/src/core/CloudStreams.Core.Infrastructure/Services/PluginSchemaRegistryProvider.cs deleted file mode 100644 index c59025d4..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Services/PluginSchemaRegistryProvider.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Hylo.Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace CloudStreams.Core.Infrastructure.Services; - -/// -/// Represents an -based implementation of the interface -/// -public class PluginSchemaRegistryProvider - : ISchemaRegistryProvider -{ - - /// - /// Initializes a new - /// - /// The current - /// The service used to create s - /// The service used to manage s - public PluginSchemaRegistryProvider(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IPluginManager pluginManager) - { - this.ServiceProvider = serviceProvider; - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.PluginManager = pluginManager; - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// Gets the service used to perform loggi ng - /// - protected ILogger Logger { get; } - - /// - /// Gets the service used to manage s - /// - protected IPluginManager PluginManager { get; } - - private ISchemaRegistry? _schemaRegistry; - /// - public ISchemaRegistry GetSchemaRegistry() - { - if (this._schemaRegistry != null) return this._schemaRegistry; - var plugin = this.PluginManager.FindPluginAsync().GetAwaiter().GetResult(); - if(plugin == null) - { - this.Logger.LogWarning("No schema registry provider plugin found. Falling back to the memory based schema provider"); - this._schemaRegistry = ActivatorUtilities.CreateInstance(this.ServiceProvider, Array.Empty()); - } - else - { - this._schemaRegistry = plugin.GetSchemaRegistry(); - } - return this._schemaRegistry; - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Usings.cs b/src/core/CloudStreams.Core.Infrastructure/Usings.cs deleted file mode 100644 index d690ec48..00000000 --- a/src/core/CloudStreams.Core.Infrastructure/Usings.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -global using CloudStreams.Core.Data; -global using Json.Schema; \ No newline at end of file diff --git a/src/core/CloudStreams.Core/AuthorizationPolicyEffect.cs b/src/core/CloudStreams.Core/AuthorizationPolicyEffect.cs index 9eac6495..b5999e1b 100644 --- a/src/core/CloudStreams.Core/AuthorizationPolicyEffect.cs +++ b/src/core/CloudStreams.Core/AuthorizationPolicyEffect.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core/CloudEventAttributeConflictResolution.cs b/src/core/CloudStreams.Core/CloudEventAttributeConflictResolution.cs index 6d5b6a6c..c68c15d1 100644 --- a/src/core/CloudStreams.Core/CloudEventAttributeConflictResolution.cs +++ b/src/core/CloudStreams.Core/CloudEventAttributeConflictResolution.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -20,12 +20,12 @@ public static class CloudEventAttributeConflictResolution { /// - /// Indicates that the value of the existing attribute should be overwriten + /// Indicates that the value of the existing attribute should be overwritten /// public const string Overwrite = "overwrite"; /// - /// Indicates that the value be writen to a fallback attribute when the target attribute exists + /// Indicates that the value be written to a fallback attribute when the target attribute exists /// public const string Fallback = "fallback"; -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/CloudEventAttributes.cs b/src/core/CloudStreams.Core/CloudEventAttributes.cs deleted file mode 100644 index c84e31a1..00000000 --- a/src/core/CloudStreams.Core/CloudEventAttributes.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; - -namespace CloudStreams.Core; - -/// -/// Exposes constants about attributes -/// -public static class CloudEventAttributes -{ - - /// - /// Identifies the event - /// - public const string Id = "id"; - /// - /// Identifies the context in which an event happened - /// - public const string SpecVersion = "specversion"; - /// - /// Timestamp of when the occurrence happened - /// - public const string Time = "time"; - /// - /// Identifies the context in which an event happened - /// - public const string Source = "source"; - /// - /// Describes the type of event related to the originating occurrence - /// - public const string Type = "type"; - /// - /// Describes the subject of the event in the context of the event producer (identified by source) - /// - public const string Subject = "subject"; - /// - /// Content type of data value - /// - public const string DataContentType = "datacontenttype"; - /// - /// Identifies the schema that data adheres to - /// - public const string DataSchema = "dataschema"; - /// - /// The event payload. Only used when events are formatted using the structured mode - /// - public const string Data = "data"; - /// - /// The event payload, encoded in base 64. Only used when events are formatted using the binary mode - /// - public const string DataBase64 = "data_base64"; - - /// - /// Gets an that contains all required attributes - /// - /// A new that contains all required attributes - public static IEnumerable GetRequiredAttributes() - { - yield return Id; - yield return SpecVersion; - yield return Source; - yield return Type; - } - -} diff --git a/src/core/CloudStreams.Core/CloudEventAuthorizationRuleType.cs b/src/core/CloudStreams.Core/CloudEventAuthorizationRuleType.cs index 65c07efe..7f73aa67 100644 --- a/src/core/CloudStreams.Core/CloudEventAuthorizationRuleType.cs +++ b/src/core/CloudStreams.Core/CloudEventAuthorizationRuleType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ public static class CloudEventAuthorizationRuleType /// public const string Payload = "payload"; /// - /// Indicates a policy that grants or refuses accesss based on the time of day + /// Indicates a policy that grants or refuses access based on the time of day /// public const string TimeOfDay = "timeOfDay"; /// @@ -36,4 +36,4 @@ public static class CloudEventAuthorizationRuleType /// public const string Temporary = "temporary"; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventContentMode.cs b/src/core/CloudStreams.Core/CloudEventContentMode.cs deleted file mode 100644 index 4370a6db..00000000 --- a/src/core/CloudStreams.Core/CloudEventContentMode.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Serialization.Json; - -namespace CloudStreams.Core; - -/// -/// Enumerates all supported cloud event content modes -/// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] -public enum CloudEventContentMode -{ - /// - /// Indicates the cloud event binary content mode - /// - [EnumMember(Value = "binary")] - Binary, - /// - /// Indicates the cloud event structured content mode - /// - [EnumMember(Value = "structured")] - Structured, - /// - /// Indicates the cloud event structured content mode - /// - [EnumMember(Value = "batched")] - Batched -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventDescriptor.cs b/src/core/CloudStreams.Core/CloudEventDescriptor.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/CloudEventDescriptor.cs rename to src/core/CloudStreams.Core/CloudEventDescriptor.cs index 1864a913..5c5996af 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventDescriptor.cs +++ b/src/core/CloudStreams.Core/CloudEventDescriptor.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core; /// /// Represents an object used to describe a cloud event @@ -49,4 +49,4 @@ public CloudEventDescriptor(CloudEventMetadata metadata, object? data = null) [DataMember(Order = 2, Name = "data"), JsonPropertyName("data"), YamlMember(Alias = "data")] public virtual object? Data { get; set; } = null!; -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/CloudEventExtensionAttributes.cs b/src/core/CloudStreams.Core/CloudEventExtensionAttributes.cs index ed3cb5f8..4cf54e3c 100644 --- a/src/core/CloudStreams.Core/CloudEventExtensionAttributes.cs +++ b/src/core/CloudStreams.Core/CloudEventExtensionAttributes.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -24,4 +24,4 @@ public static class CloudEventExtensionAttributes /// public const string Sequence = "sequence"; -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/CloudEventFilterType.cs b/src/core/CloudStreams.Core/CloudEventFilterType.cs index 7ca1bbf0..270bff9d 100644 --- a/src/core/CloudStreams.Core/CloudEventFilterType.cs +++ b/src/core/CloudStreams.Core/CloudEventFilterType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,26 +11,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Serialization; -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all cloud event filter types /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum CloudEventFilterType { /// /// Indicates a context attributes based filter /// [EnumMember(Value = "attributes")] - Attributes, + Attributes = 1, /// /// Indicates an expression-based filter /// [EnumMember(Value = "expression")] - Expression -} + Expression = 2 +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventMediaTypeNames.cs b/src/core/CloudStreams.Core/CloudEventMediaTypeNames.cs deleted file mode 100644 index ffc12089..00000000 --- a/src/core/CloudStreams.Core/CloudEventMediaTypeNames.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core; - -/// -/// Enumerates all supported cloud event media types names -/// -public static class CloudEventMediaTypeNames -{ - - /// - /// Gets the 'application/cloudevents' media type, which assumes an encoding in JSON - /// - public const string CloudEvents = "application/cloudevents"; - /// - /// Gets the 'application/cloudevents+json' media type - /// - public const string CloudEventsJson = CloudEvents + "+json"; - /// - /// Gets the 'application/cloudevents+yaml' media type - /// - public const string CloudEventsYaml = CloudEvents + "+yaml"; - -} diff --git a/src/core/CloudStreams.Core/Data/CloudEventMetadata.cs b/src/core/CloudStreams.Core/CloudEventMetadata.cs similarity index 85% rename from src/core/CloudStreams.Core/Data/CloudEventMetadata.cs rename to src/core/CloudStreams.Core/CloudEventMetadata.cs index 0cc9145c..41102c0b 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventMetadata.cs +++ b/src/core/CloudStreams.Core/CloudEventMetadata.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core; /// /// Represents an object that provides information about a cloud event @@ -32,10 +32,10 @@ public CloudEventMetadata() { } /// A key/value mapping of the described cloud event's context attributes public CloudEventMetadata(IDictionary contextAttributes) { - if (contextAttributes == null) throw new ArgumentNullException(nameof(contextAttributes)); + ArgumentNullException.ThrowIfNull(contextAttributes); if (!CloudEventAttributes.GetRequiredAttributes().All(a => contextAttributes.TryGetValue(a, out _))) throw new ArgumentException("The specified mapping does not contains all the cloud event context attributes defined as required by the spec"); - if(contextAttributes.All(a => a.Key.IsAlphanumeric() && a.Key.IsLowercased() && a.Key.Length >= 3 && a.Key.Length < 20)) throw new ArgumentException("Cloud event context attribute names must be lowercased, must contain only alphanumeric characters, and must have a maximum length of 20 characters"); - this.ContextAttributes = contextAttributes; + if (contextAttributes.All(a => a.Key.IsAlphanumeric() && a.Key.IsLowercased() && a.Key.Length >= 3 && a.Key.Length < 20)) throw new ArgumentException("Cloud event context attribute names must be lowercased, must contain only alphanumeric characters, and must have a maximum length of 20 characters"); + ContextAttributes = contextAttributes; } /// diff --git a/src/core/CloudStreams.Core/CloudEventMetadataProperties.cs b/src/core/CloudStreams.Core/CloudEventMetadataProperties.cs deleted file mode 100644 index 80de29bf..00000000 --- a/src/core/CloudStreams.Core/CloudEventMetadataProperties.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core; - -/// -/// Exposes well known cloud event metadata properties -/// -public static class CloudEventMetadataProperties -{ - - /// - /// Gets the name of the metadata property that holds the event's correlation id - /// - public const string CorrelationId = "$correlationId"; - /// - /// Gets the name of the metadata property that holds the event's causation id - /// - public const string CausationId = "$causationId"; - -} diff --git a/src/core/CloudStreams.Core/CloudEventMetadataPropertyResolutionStrategy.cs b/src/core/CloudStreams.Core/CloudEventMetadataPropertyResolutionStrategy.cs index 97cfa8e3..d6cfe8da 100644 --- a/src/core/CloudStreams.Core/CloudEventMetadataPropertyResolutionStrategy.cs +++ b/src/core/CloudStreams.Core/CloudEventMetadataPropertyResolutionStrategy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all strategies for resolving cloud event metadata properties /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum CloudEventMetadataPropertyResolutionStrategy { /// diff --git a/src/core/CloudStreams.Core/CloudEventMutationType.cs b/src/core/CloudStreams.Core/CloudEventMutationType.cs index ebc38592..cea17144 100644 --- a/src/core/CloudStreams.Core/CloudEventMutationType.cs +++ b/src/core/CloudStreams.Core/CloudEventMutationType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all types of cloud event mutations /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum CloudEventMutationType { /// @@ -32,4 +32,4 @@ public enum CloudEventMutationType /// [EnumMember(Value = "webhook")] Webhook = 2 -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventPartitionType.cs b/src/core/CloudStreams.Core/CloudEventPartitionType.cs index 1b2a9d07..3b36c5d4 100644 --- a/src/core/CloudStreams.Core/CloudEventPartitionType.cs +++ b/src/core/CloudStreams.Core/CloudEventPartitionType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all supported cloud event stream partition types /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum CloudEventPartitionType { /// @@ -47,4 +47,4 @@ public enum CloudEventPartitionType /// [EnumMember(Value = "by-causation-id")] ByCausationId = 6 -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/Data/CloudEventRecord.cs b/src/core/CloudStreams.Core/CloudEventRecord.cs similarity index 91% rename from src/core/CloudStreams.Core/Data/CloudEventRecord.cs rename to src/core/CloudStreams.Core/CloudEventRecord.cs index 857dc9c1..7b451874 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventRecord.cs +++ b/src/core/CloudStreams.Core/CloudEventRecord.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core; /// /// Represents an object used to describe a recorded cloud event @@ -36,7 +36,7 @@ public CloudEventRecord() { } public CloudEventRecord(string streamId, ulong sequence, CloudEventMetadata metadata, object? data) : base(metadata, data) { - if(string.IsNullOrWhiteSpace(streamId)) throw new ArgumentNullException(nameof(streamId)); + if (string.IsNullOrWhiteSpace(streamId)) throw new ArgumentNullException(nameof(streamId)); this.StreamId = streamId; this.Sequence = sequence; } @@ -53,4 +53,4 @@ public CloudEventRecord(string streamId, ulong sequence, CloudEventMetadata meta [DataMember(Order = 2, Name = "sequence"), JsonPropertyName("sequence"), YamlMember(Alias = "sequence")] public virtual ulong Sequence { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventSequencingStrategy.cs b/src/core/CloudStreams.Core/CloudEventSequencingStrategy.cs index 510601f1..bb1682a6 100644 --- a/src/core/CloudStreams.Core/CloudEventSequencingStrategy.cs +++ b/src/core/CloudStreams.Core/CloudEventSequencingStrategy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -28,4 +28,4 @@ public static class CloudEventSequencingStrategy /// public const string Attribute = "attribute"; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventSpecVersion.cs b/src/core/CloudStreams.Core/CloudEventSpecVersion.cs deleted file mode 100644 index aa14fc18..00000000 --- a/src/core/CloudStreams.Core/CloudEventSpecVersion.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core; - -/// -/// Enumerates all supported version of the Cloud Event spec -/// -public static class CloudEventSpecVersion -{ - - /// - /// Specifies version 1.0 - /// - public const string v1 = "1.0"; - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/CloudEventStreamPosition.cs b/src/core/CloudStreams.Core/CloudEventStreamPosition.cs deleted file mode 100644 index 2e24ef83..00000000 --- a/src/core/CloudStreams.Core/CloudEventStreamPosition.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core; - -/// -/// Provides mechanisms to handle positioning/offsetting in cloud event streams -/// -public static class StreamPosition -{ - - /// - /// Specifies the start of the stream - /// - public const long StartOfStream = 0; - - /// - /// Specifies the end of the stream - /// - public const long EndOfStream = -1; - -} diff --git a/src/core/CloudStreams.Core/CloudEventValidationStrategy.cs b/src/core/CloudStreams.Core/CloudEventValidationStrategy.cs index 8a05a535..b44cbdf5 100644 --- a/src/core/CloudStreams.Core/CloudEventValidationStrategy.cs +++ b/src/core/CloudStreams.Core/CloudEventValidationStrategy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core/CloudStreams.Core.csproj b/src/core/CloudStreams.Core/CloudStreams.Core.csproj index ed08e23f..9e83cbf7 100644 --- a/src/core/CloudStreams.Core/CloudStreams.Core.csproj +++ b/src/core/CloudStreams.Core/CloudStreams.Core.csproj @@ -1,77 +1,38 @@ - + - net7.0 + net8.0 enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;core; - Contains Cloud Streams core resources and concepts - logo.png - Apache-2.0 - True - True + true - - True - \ - - - True - \ - - - - - - - - - - - True - True - ProblemDetails.resx - - - True - True - ProblemTitles.resx - + + - - PublicResXFileCodeGenerator - ProblemDetails.Designer.cs - - - PublicResXFileCodeGenerator - ProblemTitles.Designer.cs - + + + - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + + diff --git a/src/core/CloudStreams.Core/CloudStreamsDefaults.cs b/src/core/CloudStreams.Core/CloudStreamsDefaults.cs index ac91b431..b2af1013 100644 --- a/src/core/CloudStreams.Core/CloudStreamsDefaults.cs +++ b/src/core/CloudStreams.Core/CloudStreamsDefaults.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,6 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +using CloudStreams.Core.Resources; +using System.Diagnostics; + namespace CloudStreams.Core; /// @@ -39,17 +42,17 @@ public static class Definitions /// /// Gets the definition of Broker resources /// - public static ResourceDefinition Broker { get; } = LoadResourceDefinition(nameof(Broker)); + public static ResourceDefinition Broker { get; } = new BrokerResourceDefinition(); /// /// Gets the definition of Gateway resources /// - public static ResourceDefinition Gateway { get; } = LoadResourceDefinition(nameof(Gateway)); + public static ResourceDefinition Gateway { get; } = new GatewayResourceDefinition(); /// /// Gets the definition of Subscription resources /// - public static ResourceDefinition Subscription { get; } = LoadResourceDefinition(nameof(Subscription)); + public static ResourceDefinition Subscription { get; } = new SubscriptionResourceDefinition(); /// /// Gets a new containing Cloud Streams default resource definitions @@ -62,16 +65,21 @@ public static IEnumerable AsEnumerable() yield return Subscription; } - static ResourceDefinition LoadResourceDefinition(string name) - { - var filePath = Path.Combine(AppContext.BaseDirectory, "Assets", "Definitions", $"{name.ToHyphenCase()}.yaml"); - var yaml = File.ReadAllText(filePath); - var resourceDefinition = Hylo.Serializer.Json.Deserialize(Hylo.Serializer.Json.Serialize(Hylo.Serializer.Yaml.Deserialize>(yaml)!))!; - return resourceDefinition; - } - } } + /// + /// Exposes constants about Cloud Streams application telemetry + /// + public static class Telemetry + { + + /// + /// Exposes the Cloud Streams application's + /// + public static ActivitySource ActivitySource { get; set; } = null!; + + } + } diff --git a/src/core/CloudStreams.Core/CloudStreamsException.cs b/src/core/CloudStreams.Core/CloudStreamsException.cs deleted file mode 100644 index 2b647deb..00000000 --- a/src/core/CloudStreams.Core/CloudStreamsException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams; - -/// -/// Represents an thrown by a Cloud Streams application -/// -public class CloudStreamsException - : Exception -{ - - /// - /// Initializes a new - /// - /// An object used to describe a problem that has occured on the CloudStreams API - public CloudStreamsException(ProblemDetails? problemDetails = null) - { - this.ProblemDetails = problemDetails; - } - - /// - /// Gets an object used to describe a problem that has occured on the CloudStreams API - /// - public ProblemDetails? ProblemDetails { get; } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEvent.cs b/src/core/CloudStreams.Core/Data/CloudEvent.cs deleted file mode 100644 index 76629e6e..00000000 --- a/src/core/CloudStreams.Core/Data/CloudEvent.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Net.Mime; - -namespace CloudStreams.Core.Data; - -/// -/// Represents a Cloud Event envelope -/// -[DataContract] -public record CloudEvent - : IExtensible -{ - - /// - /// Gets/sets a string that uniquely identifies the cloud event in the scope of its source - /// - [Required, JsonRequired] - [DataMember(Order = 1, Name = "id", IsRequired = true), JsonPropertyOrder(1), JsonPropertyName("id"), YamlMember(Order = 1, Alias = "id")] - public virtual string Id { get; set; } = null!; - - /// - /// Gets/sets the version of the CloudEvents specification which the event uses. Defaults to - /// - [DefaultValue(CloudEventSpecVersion.v1)] - [DataMember(Order = 2, Name = "specversion", IsRequired = true), JsonPropertyOrder(2), JsonPropertyName("specversion"), YamlMember(Order = 2, Alias = "specversion")] - public virtual string SpecVersion { get; set; } = CloudEventSpecVersion.v1; - - /// - /// Gets/sets the date and time at which the event has been produced - /// - [DataMember(Order = 3, Name = "time"), JsonPropertyOrder(3), JsonPropertyName("time"), YamlMember(Order = 3, Alias = "time")] - public virtual DateTimeOffset? Time { get; set; } - - /// - /// Gets/sets the cloud event's type - /// - [Required, JsonRequired] - [DataMember(Order = 4, Name = "source", IsRequired = true), JsonPropertyOrder(4), JsonPropertyName("source"), YamlMember(Order = 4, Alias = "source")] - public virtual Uri Source { get; set; } = null!; - - /// - /// Gets/sets the cloud event's type - /// - [Required, JsonRequired] - [DataMember(Order = 5, Name = "type", IsRequired = true), JsonPropertyOrder(5), JsonPropertyName("type"), YamlMember(Order = 5, Alias = "type")] - public virtual string Type { get; set; } = null!; - - /// - /// Gets/sets a value that describes the subject of the event in the context of the event producer. Used as correlation id by default. - /// - [DataMember(Order = 6, Name = "subject"), JsonPropertyOrder(6), JsonPropertyName("subject"), YamlMember(Order = 6, Alias = "subject")] - public virtual string? Subject { get; set; } - - /// - /// Gets/sets the cloud event's data content type. Defaults to - /// - [DefaultValue(MediaTypeNames.Application.Json)] - [DataMember(Order = 7, Name = "datacontenttype"), JsonPropertyOrder(7), JsonPropertyName("datacontenttype"), YamlMember(Order = 7, Alias = "datacontenttype")] - public virtual string? DataContentType { get; set; } = MediaTypeNames.Application.Json; - - /// - /// Gets/sets an that references the versioned schema of the event's data - /// - [DataMember(Order = 8, Name = "dataschema"), JsonPropertyOrder(8), JsonPropertyName("dataschema"), YamlMember(Order = 8, Alias = "dataschema")] - public virtual Uri? DataSchema { get; set; } - - /// - /// Gets/sets the event's data, if any. Only used if the event has been formatted using the structured mode - /// - [DataMember(Order = 9, Name = "data"), JsonPropertyOrder(9), JsonPropertyName("data"), YamlMember(Order = 9, Alias = "data")] - public virtual object? Data { get; set; } - - /// - /// Gets/sets the event's binary data, encoded in base 64. Only used if the event has been formatted using the binary mode - /// - [DataMember(Order = 10, Name = "data_base64"), JsonPropertyOrder(10), JsonPropertyName("data_base64"), YamlMember(Order = 10, Alias = "data_base64")] - public virtual string? DataBase64 { get; set; } - - /// - /// Gets/sets an that contains the event's extension attributes - /// - [DataMember(Order = 11, Name = "extensionAttributes"), JsonExtensionData] - public virtual IDictionary? ExtensionAttributes { get; set; } - - /// - /// Gets/sets the specified attribute - /// - /// The name of the attribute to set - /// The attribute's value - public virtual object? this[string attributeName] => this.GetAttribute(attributeName); - - IDictionary? IExtensible.ExtensionData => this.ExtensionAttributes; - - /// - /// Gets the specified attribute - /// - /// The name of the attribute to get - /// The value of the specified attribute - public virtual object? GetAttribute(string name) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - switch (name) - { - case CloudEventAttributes.Id: return this.Id; - case CloudEventAttributes.SpecVersion: return this.SpecVersion; - case CloudEventAttributes.Time: return this.Time; - case CloudEventAttributes.Source: return this.Source; - case CloudEventAttributes.Type: return this.Type; - case CloudEventAttributes.Subject: return this.Subject; - case CloudEventAttributes.DataContentType: return this.DataContentType; - case CloudEventAttributes.DataSchema: return this.DataSchema; - case CloudEventAttributes.Data: return this.Data; - case CloudEventAttributes.DataBase64: return this.DataBase64; - default: - if (this.ExtensionAttributes?.TryGetValue(name, out var value) == true) return value; - else return null; - } - } - - /// - public override string ToString() => this.Id; - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/Gateway.cs b/src/core/CloudStreams.Core/Data/Gateway.cs deleted file mode 100644 index 95ddb729..00000000 --- a/src/core/CloudStreams.Core/Data/Gateway.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Data; - -/// -/// Represents a cloud event gateway, reponsible for authorizing, validating and persisting inbound events -/// -[DataContract] -public record Gateway - : Resource -{ - - const string ResourceGroup = CloudStreamsDefaults.ResourceGroup; - - const string ResourceVersion = "v1"; - - const string ResourcePlural = "gateways"; - - const string ResourceKind = "Gateway"; - - /// - /// Gets the 's resource type - /// - public static readonly ResourceDefinitionInfo ResourceDefinition = new(ResourceGroup, ResourceVersion, ResourcePlural, ResourceKind); - - /// - public Gateway() : base(ResourceDefinition) { } - - /// - public Gateway(ResourceMetadata metadata, GatewaySpec spec) : base(ResourceDefinition, metadata, spec) { } - - /// - [Required, DefaultValue(CloudStreamsDefaults.ResourceGroup + "/" + ResourceVersion)] - [DataMember(Order = 1, Name = "apiVersion", IsRequired = true), JsonPropertyOrder(1), JsonPropertyName("apiVersion"), YamlMember(Order = 1, Alias = "apiVersion")] - public override string ApiVersion { get => base.ApiVersion; set => base.ApiVersion = value; } - - /// - [Required, DefaultValue(ResourceKind)] - [DataMember(Order = 2, Name = "kind", IsRequired = true), JsonPropertyOrder(2), JsonPropertyName("kind"), YamlMember(Order = 2, Alias = "kind")] - public override string Kind { get => base.Kind; set => base.Kind = value; } - -} diff --git a/src/core/CloudStreams.Core/Extensions/CloudEventDescriptorExtensions.cs b/src/core/CloudStreams.Core/Extensions/CloudEventDescriptorExtensions.cs deleted file mode 100644 index 5a6cb1aa..00000000 --- a/src/core/CloudStreams.Core/Extensions/CloudEventDescriptorExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; -using System.Text.Json.Nodes; - -namespace CloudStreams.Core; - -/// -/// Defines extensions for s -/// -public static class CloudEventDescriptorExtensions -{ - - /// - /// Converts the into the it describes - /// - /// The to convert - /// The described by the converted - public static CloudEvent ToCloudEvent(this CloudEventDescriptor descriptor) - { - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - if(descriptor.Data is byte[] byteArray) - { - //lets assume the data is actually JSON - - } - var e = (JsonObject)Hylo.Serializer.Json.SerializeToNode(descriptor.Metadata.ContextAttributes)!; - var data = Hylo.Serializer.Json.SerializeToNode(descriptor.Data); - e[CloudEventAttributes.Data] = data; - return Hylo.Serializer.Json.Deserialize(e)!; - } - -} diff --git a/src/core/CloudStreams.Core/Extensions/CloudEventExtensions.cs b/src/core/CloudStreams.Core/Extensions/CloudEventExtensions.cs index b609265e..fe4fcecd 100644 --- a/src/core/CloudStreams.Core/Extensions/CloudEventExtensions.cs +++ b/src/core/CloudStreams.Core/Extensions/CloudEventExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,30 +11,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using Neuroglia.Serialization; using System.Text; +using System.Text.Json; namespace CloudStreams.Core; /// -/// Defines extensions for +/// Defines extensions for s /// public static class CloudEventExtensions { - /// - /// Attempts to get the attribute with the specified name - /// - /// The to get the attribute of - /// The name of the context attribute to get - /// The value of the 's attribute, if any - /// A boolean indicating whether or not the containing the specified attribute - public static bool TryGetAttribute(this CloudEvent e, string attributeName, out object? value) - { - value = e.GetAttribute(attributeName); - return value != null; - } - /// /// Gets the 's sequence /// @@ -47,7 +35,7 @@ public static bool TryGetAttribute(this CloudEvent e, string attributeName, out { string str => ulong.Parse(str), ulong num => num, - JsonElement jsonElem => Hylo.Serializer.Json.Deserialize(jsonElem), + JsonElement jsonElem => Neuroglia.Serialization.Json.JsonSerializer.Default.Deserialize(jsonElem), _ => null }; } @@ -56,33 +44,12 @@ public static bool TryGetAttribute(this CloudEvent e, string attributeName, out /// Converts the into a new /// /// The to convert to a new + /// The service used to serialize the to a new /// The 's representation - public static HttpContent ToHttpContent(this CloudEvent e) + public static HttpContent ToHttpContent(this CloudEvent e, IJsonSerializer? serializer = null) { - return new StringContent(Hylo.Serializer.Json.Serialize(e), Encoding.UTF8, CloudEventMediaTypeNames.CloudEventsJson); - } - - /// - /// Gets the 's context attributes - /// - /// The to get the context attributes of - /// A boolean indicating whether or not to include extension attributes - /// A new - public static IEnumerable> GetContextAttributes(this CloudEvent e, bool includeExtensionAttributes = true) - { - yield return new(CloudEventAttributes.Id, e.Id); - yield return new(CloudEventAttributes.SpecVersion, e.SpecVersion); - if (e.Time.HasValue) yield return new(CloudEventAttributes.Time, e.Time); - yield return new(CloudEventAttributes.Source, e.Source); - yield return new(CloudEventAttributes.Type, e.Type); - if (!string.IsNullOrWhiteSpace(e.Subject)) yield return new(CloudEventAttributes.Subject, e.Subject); - if (!string.IsNullOrWhiteSpace(e.DataContentType)) yield return new(CloudEventAttributes.DataContentType, e.DataContentType); - if (e.DataSchema != null) yield return new(CloudEventAttributes.DataSchema, e.DataSchema); - if (!includeExtensionAttributes || e.ExtensionAttributes == null) yield break; - foreach (var extensionAttribute in e.ExtensionAttributes) - { - yield return extensionAttribute; - } + serializer ??= Neuroglia.Serialization.Json.JsonSerializer.Default; + return new StringContent(serializer.SerializeToText(e), Encoding.UTF8, CloudEventContentType.Json); } } diff --git a/src/core/CloudStreams.Core/Extensions/CloudEventIngestionConfigurationExtensions.cs b/src/core/CloudStreams.Core/Extensions/CloudEventIngestionConfigurationExtensions.cs index 4a00cdb5..0b386d4a 100644 --- a/src/core/CloudStreams.Core/Extensions/CloudEventIngestionConfigurationExtensions.cs +++ b/src/core/CloudStreams.Core/Extensions/CloudEventIngestionConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using CloudStreams.Core.Resources; using System.Text.RegularExpressions; namespace CloudStreams.Core; @@ -30,10 +30,11 @@ public static class CloudEventIngestionConfigurationExtensions /// A boolean indicating whether or not the applies to the specified public static bool AppliesTo(this CloudEventIngestionConfiguration configuration, CloudEvent e) { - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(configuration); + ArgumentNullException.ThrowIfNull(e); + return (configuration.Source.Trim() == "*" || Regex.IsMatch(e.Source.OriginalString, configuration.Source)) && (configuration.Type.Trim() == "*" || Regex.IsMatch(e.Type, configuration.Type)); } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Extensions/EvaluationResultsExtensions.cs b/src/core/CloudStreams.Core/Extensions/EvaluationResultsExtensions.cs deleted file mode 100644 index 10e26e16..00000000 --- a/src/core/CloudStreams.Core/Extensions/EvaluationResultsExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Json.Schema; - -namespace CloudStreams.Core; - -/// -/// Defines extensions for -/// -public static class EvaluationResultsExtensions -{ - - /// - /// Converts the to an of errors, if any - /// - /// The to convert - /// A new containing the 's errors, if any - public static IEnumerable>? ToErrorList(this EvaluationResults evaluationResults) - { - if (evaluationResults.IsValid) return null; - var errors = new List>(); - if (evaluationResults.Errors?.Any() == true) errors = evaluationResults.Errors.Select(e => new KeyValuePair(evaluationResults.InstanceLocation.ToString(), new string[] { e.Value })).ToList(); - if (!evaluationResults.Details.Any()) return null; - foreach (var detail in evaluationResults.Details) - { - var childErrors = detail.ToErrorList(); - if (childErrors == null) continue; - foreach (var error in childErrors) - { - errors.Add(error); - } - } - return errors; - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Extensions/ExceptionExtensions.cs b/src/core/CloudStreams.Core/Extensions/ExceptionExtensions.cs index e026bc7f..be4094bb 100644 --- a/src/core/CloudStreams.Core/Extensions/ExceptionExtensions.cs +++ b/src/core/CloudStreams.Core/Extensions/ExceptionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -26,8 +26,8 @@ public static class ExceptionExtensions /// New that describe the public static ProblemDetails ToProblemDetails(this Exception exception) { - if (exception == null) throw new ArgumentNullException(nameof(exception)); - if (exception is CloudStreamsException csex && csex.ProblemDetails != null) return csex.ProblemDetails; + ArgumentNullException.ThrowIfNull(exception); + if (exception is ProblemDetailsException problemDetailsExceptions && problemDetailsExceptions.Problem != null) return problemDetailsExceptions.Problem; var statusCode = exception switch { HttpRequestException httpEx => httpEx.StatusCode.HasValue ? (int)httpEx.StatusCode : 500, diff --git a/src/core/CloudStreams.Core/Extensions/RetryBackoffDurationExtensions.cs b/src/core/CloudStreams.Core/Extensions/RetryBackoffDurationExtensions.cs index 495a1b16..0538848d 100644 --- a/src/core/CloudStreams.Core/Extensions/RetryBackoffDurationExtensions.cs +++ b/src/core/CloudStreams.Core/Extensions/RetryBackoffDurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using CloudStreams.Core.Resources; namespace CloudStreams.Core; @@ -29,12 +29,13 @@ public static class RetryBackoffDurationExtensions /// A new that represents the backoff duration for the specified retry attempt public static TimeSpan ForAttempt(this RetryBackoffDuration duration, int attemptNumber) { - if(duration == null) throw new ArgumentNullException(nameof(duration)); + ArgumentNullException.ThrowIfNull(duration); + var timeSpan = duration.Period.ToTimeSpan(); return duration.Type switch { - RetryBackoffDurationType.Constant => duration.Period, - RetryBackoffDurationType.Exponential => Math.Pow(attemptNumber, duration.Exponent!.Value) * duration.Period, - RetryBackoffDurationType.Incremental => attemptNumber * duration.Period, + RetryBackoffDurationType.Constant => timeSpan, + RetryBackoffDurationType.Exponential => Math.Pow(attemptNumber, duration.Exponent!.Value) * timeSpan, + RetryBackoffDurationType.Incremental => attemptNumber * timeSpan, _ => throw new NotSupportedException($"The specified {nameof(RetryBackoffDurationType)} '{duration.Type}' is not supported") }; } diff --git a/src/core/CloudStreams.Core/Extensions/SubscriptionExtensions.cs b/src/core/CloudStreams.Core/Extensions/SubscriptionExtensions.cs index 238270a8..de87e00f 100644 --- a/src/core/CloudStreams.Core/Extensions/SubscriptionExtensions.cs +++ b/src/core/CloudStreams.Core/Extensions/SubscriptionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using CloudStreams.Core.Resources; +using Neuroglia.Data.Infrastructure.EventSourcing; namespace CloudStreams.Core; diff --git a/src/core/CloudStreams.Core/HealthStatus.cs b/src/core/CloudStreams.Core/HealthStatus.cs index 344aa55d..7d486b38 100644 --- a/src/core/CloudStreams.Core/HealthStatus.cs +++ b/src/core/CloudStreams.Core/HealthStatus.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core/Iso8601TimeSpan.cs b/src/core/CloudStreams.Core/Iso8601TimeSpan.cs deleted file mode 100644 index a47a3df7..00000000 --- a/src/core/CloudStreams.Core/Iso8601TimeSpan.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Json.Schema; -using System.Xml; - -namespace CloudStreams; - -/// -/// Represents an helper class for handling ISO 8601 timespans -/// -public static class Iso8601TimeSpan -{ - - /// - /// Parses the specified input - /// - /// The input string to parse - /// The parsed - public static TimeSpan Parse(string input) => Duration.Parse(input).ToTimeSpan(); - - /// - /// Formats the specified - /// - /// The to format - /// The parsed - public static string Format(TimeSpan timeSpan) => XmlConvert.ToString(timeSpan); - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/PartitionMetadata.cs b/src/core/CloudStreams.Core/PartitionMetadata.cs similarity index 93% rename from src/core/CloudStreams.Core/Data/PartitionMetadata.cs rename to src/core/CloudStreams.Core/PartitionMetadata.cs index fa2d4abf..9a5c0bca 100644 --- a/src/core/CloudStreams.Core/Data/PartitionMetadata.cs +++ b/src/core/CloudStreams.Core/PartitionMetadata.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core; /// /// Represents an object used to describe a cloud event partition @@ -43,7 +43,7 @@ public record PartitionMetadata /// /// Gets/sets the date and time at which the last event has been partitioned /// - [DataMember(Order = 4, Name = "lastEvent"), JsonPropertyName("lastEvent"), YamlMember(Alias = "lastEvent")] + [DataMember(Order = 4, Name = "lastEvent"), JsonPropertyName("lastEvent"), YamlMember(Alias = "lastEvent")] public virtual DateTimeOffset LastEvent { get; set; } /// diff --git a/src/core/CloudStreams.Core/ProblemTypes.cs b/src/core/CloudStreams.Core/ProblemTypes.cs deleted file mode 100644 index 7465538d..00000000 --- a/src/core/CloudStreams.Core/ProblemTypes.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core; - -/// -/// Exposes constants about problem types -/// -public static class ProblemTypes -{ - - static readonly Uri BaseUri = new("https://cloud-streams.io/problems/"); - - /// - /// Gets the uri that reference a problem due to a resource not being modified as expected - /// - public static readonly Uri NotModified = new(BaseUri, "not-modified"); - /// - /// Gets the uri that reference a problem due to failed validation - /// - public static readonly Uri ValidationFailed = new(BaseUri, "validation-failed"); - /// - /// Gets the uri that reference a problem due to the failure to retrieve the definition of a resource - /// - public static readonly Uri ResourceDefinitionNotFound = new(BaseUri, "resources/definitions/not-found"); - /// - /// Gets the uri that reference a problem due to the failure to retrieve a specific resource - /// - public static readonly Uri ResourceNotFound = new(BaseUri, "resources/not-found"); - /// - /// Gets the uri that reference a problem that occured during a patch - /// - public static readonly Uri ResourcePatchFailed = new(BaseUri, "resources/patch-failed"); - -} diff --git a/src/core/CloudStreams.Core/Properties/ProblemDetails.Designer.cs b/src/core/CloudStreams.Core/Properties/ProblemDetails.Designer.cs deleted file mode 100644 index c9a80a9b..00000000 --- a/src/core/CloudStreams.Core/Properties/ProblemDetails.Designer.cs +++ /dev/null @@ -1,189 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace CloudStreams.Core.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class ProblemDetails { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ProblemDetails() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CloudStreams.Core.Properties.ProblemDetails", typeof(ProblemDetails).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Cloud event context attribute names must be formed by lower case, alphanumeric characters only. - /// - public static string CloudEventAttributeNameMustBeAlphanumeric { - get { - return ResourceManager.GetString("CloudEventAttributeNameMustBeAlphanumeric", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A cluster resource cannot define a namespace. - /// - public static string ClusterResourceCannotDefineNamespace { - get { - return ResourceManager.GetString("ClusterResourceCannotDefineNamespace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find clustered resource '{group}/{version}/{plural}/{name}'. - /// - public static string ClusterResourceNotFound { - get { - return ResourceManager.GetString("ClusterResourceNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The data schema '{dataSchemaUri}' does not exist or cannot be found. - /// - public static string DataSchemaNotFound { - get { - return ResourceManager.GetString("DataSchemaNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation policy for source '{sourceUri}' requires the cloud event's 'dataSchema' attribute to be set. - /// - public static string MissingDataSchema { - get { - return ResourceManager.GetString("MissingDataSchema", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find namespaced resource '{group}/{version}/{plural}/{namespace}/{name}'. - /// - public static string NamespacedResourceNotFound { - get { - return ResourceManager.GetString("NamespacedResourceNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find namespace '{namespace}'. - /// - public static string NamespaceNotFound { - get { - return ResourceManager.GetString("NamespaceNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified resource was not modified by the update attempt. - /// - public static string NotModified { - get { - return ResourceManager.GetString("NotModified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected version: '{expectedVersion}'. Actual version: '{actualVersion}'. - /// - public static string OptimisticConcurrencyFailed { - get { - return ResourceManager.GetString("OptimisticConcurrencyFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Admission failed for operation '{operation}' on resource of type '{group}/{version}/{plural}'. - /// - public static string ResourceAdmissionFailed { - get { - return ResourceManager.GetString("ResourceAdmissionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find resource definition '{group}/{version}/{plural}'. - /// - public static string ResourceDefinitionNotFound { - get { - return ResourceManager.GetString("ResourceDefinitionNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find the storage version of the resource definition '{group}.{plural}'. - /// - public static string ResourceDefinitionStorageVersionNotFound { - get { - return ResourceManager.GetString("ResourceDefinitionStorageVersionNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to find version '{version}' of the resource definition '{group}.{plural}'. - /// - public static string ResourceDefinitionVersionNotFound { - get { - return ResourceManager.GetString("ResourceDefinitionVersionNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to convert resource '{group}/{version}/{plural}' to version '{version}'. - /// - public static string ResourceVersionConversionFailed { - get { - return ResourceManager.GetString("ResourceVersionConversionFailed", resourceCulture); - } - } - } -} diff --git a/src/core/CloudStreams.Core/Properties/ProblemDetails.resx b/src/core/CloudStreams.Core/Properties/ProblemDetails.resx deleted file mode 100644 index e8a68c8c..00000000 --- a/src/core/CloudStreams.Core/Properties/ProblemDetails.resx +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cloud event context attribute names must be formed by lower case, alphanumeric characters only - - - A cluster resource cannot define a namespace - - - Failed to find clustered resource '{group}/{version}/{plural}/{name}' - - - The data schema '{dataSchemaUri}' does not exist or cannot be found - - - The validation policy for source '{sourceUri}' requires the cloud event's 'dataSchema' attribute to be set - - - Failed to find namespaced resource '{group}/{version}/{plural}/{namespace}/{name}' - - - Failed to find namespace '{namespace}' - - - The specified resource was not modified by the update attempt - - - Expected version: '{expectedVersion}'. Actual version: '{actualVersion}' - - - Admission failed for operation '{operation}' on resource of type '{group}/{version}/{plural}' - - - Failed to find resource definition '{group}/{version}/{plural}' - - - Failed to find the storage version of the resource definition '{group}.{plural}' - - - Failed to find version '{version}' of the resource definition '{group}.{plural}' - - - Failed to convert resource '{group}/{version}/{plural}' to version '{version}' - - \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Properties/ProblemTitles.Designer.cs b/src/core/CloudStreams.Core/Properties/ProblemTitles.Designer.cs deleted file mode 100644 index cc45b9ff..00000000 --- a/src/core/CloudStreams.Core/Properties/ProblemTitles.Designer.cs +++ /dev/null @@ -1,135 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace CloudStreams.Core.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class ProblemTitles { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal ProblemTitles() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CloudStreams.Core.Properties.ProblemTitles", typeof(ProblemTitles).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Invalid Resource Format. - /// - public static string InvalidResourceFormat { - get { - return ResourceManager.GetString("InvalidResourceFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not Found. - /// - public static string NotFound { - get { - return ResourceManager.GetString("NotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not Modified. - /// - public static string NotModified { - get { - return ResourceManager.GetString("NotModified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Optimistic Concurrency Error. - /// - public static string OptimisticConcurrencyError { - get { - return ResourceManager.GetString("OptimisticConcurrencyError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Resource Admission Failed. - /// - public static string ResourceAdmissionFailed { - get { - return ResourceManager.GetString("ResourceAdmissionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Resource Version Conversion Failed. - /// - public static string ResourceVersionConversionFailed { - get { - return ResourceManager.GetString("ResourceVersionConversionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Validation Failed. - /// - public static string ValidationFailed { - get { - return ResourceManager.GetString("ValidationFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrong API. - /// - public static string WrongApi { - get { - return ResourceManager.GetString("WrongApi", resourceCulture); - } - } - } -} diff --git a/src/core/CloudStreams.Core/Properties/ProblemTitles.resx b/src/core/CloudStreams.Core/Properties/ProblemTitles.resx deleted file mode 100644 index d053ae17..00000000 --- a/src/core/CloudStreams.Core/Properties/ProblemTitles.resx +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Invalid Resource Format - - - Not Found - - - Not Modified - - - Optimistic Concurrency Error - - - Resource Admission Failed - - - Resource Version Conversion Failed - - - Validation Failed - - - Wrong API - - \ No newline at end of file diff --git a/src/core/CloudStreams.Core/PropertyPath.cs b/src/core/CloudStreams.Core/PropertyPath.cs deleted file mode 100644 index 6d3ab025..00000000 --- a/src/core/CloudStreams.Core/PropertyPath.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Linq.Expressions; -using System.Reflection; - -namespace CloudStreams.Core; - -/// -/// Represents a parsed property path, which typically is a chain of property names separated with a dot -/// -public class PropertyPath -{ - - /// - /// Initializes a new - /// - /// The string that contains the raw property path - public PropertyPath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException(nameof(path)); - if (!IsValidPropertyPath(path)) - throw new ArgumentException("The specified value '" + path + "' is not a valid " + nameof(PropertyPath) + " string", nameof(path)); - this.OriginalPath = path; - this.Values = new List(); - foreach (string value in this.OriginalPath.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)) - { - this.Values.Add(value); - } - } - - /// - /// Gets a string that represents the 's original path - /// - public string OriginalPath { get; private set; } - - /// - /// Gets a list of the values the is made of - /// - public List Values { get; private set; } - - /// - /// Gets a that expresses the member-access to the specified property - /// - /// The that represents the instance on which to attempt the member-access - /// A that expresses the member-access to the specified property - public MemberExpression ToExpression(Expression target) - { - if (this.Values.Count < 1) throw new InvalidOperationException("The " + nameof(PropertyPath) + " is empty"); - var propertyName = this.Values[0]; - var property = target.Type.GetProperty(propertyName, BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - if (property == null) throw new MissingMemberException("Failed to retrieve a property with name '" + propertyName + "' in type '" + target.Type.Name + "'"); - var memberExpression = Expression.MakeMemberAccess(target, property); - target = memberExpression; - for (int index = 1; index < this.Values.Count; index++) - { - propertyName = this.Values[index]; - property = target.Type.GetProperty(propertyName, BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - if (property == null) - throw new MissingMemberException("Failed to retrieve a property with name '" + propertyName + "' in type '" + target.Type.Name + "'"); - memberExpression = Expression.MakeMemberAccess(target, property); - target = memberExpression; - } - return memberExpression; - } - - /// - /// Parse the specified input into a new - /// - /// The input to parse - /// A new based on the specified input - public static PropertyPath Parse(string input) => new(input); - - /// - /// Attempts to parse the specified input into a new - /// - /// The input to parse - /// The resulting , if any - /// A boolean indicating whether or not a could be parse from the specified input - public static bool TryParse(string input, out PropertyPath? propertyPath) - { - try - { - propertyPath = Parse(input); - return true; - } - catch - { - propertyPath = null; - return false; - } - } - - /// - /// Gets a boolean indicating whether or not the specified input is a valid - /// - /// The property path to evaluate - /// A boolean indicating whether or not the specified input is a valid - public static bool IsValidPropertyPath(string propertyPath) - { - foreach (char c in propertyPath) - { - if (!char.IsLetter(c) - && !char.IsNumber(c) - && c != '.') - return false; - } - return true; - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/Broker.cs b/src/core/CloudStreams.Core/Resources/Broker.cs similarity index 51% rename from src/core/CloudStreams.Core/Data/Broker.cs rename to src/core/CloudStreams.Core/Resources/Broker.cs index 9359e8be..c80082ea 100644 --- a/src/core/CloudStreams.Core/Data/Broker.cs +++ b/src/core/CloudStreams.Core/Resources/Broker.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents a cloud event broker @@ -21,18 +21,10 @@ public record Broker : Resource { - const string ResourceGroup = CloudStreamsDefaults.ResourceGroup; - - const string ResourceVersion = "v1"; - - const string ResourcePlural = "brokers"; - - const string ResourceKind = "Broker"; - /// /// Gets the 's resource type /// - public static readonly ResourceDefinitionInfo ResourceDefinition = new(ResourceGroup, ResourceVersion, ResourcePlural, ResourceKind); + public static readonly ResourceDefinitionInfo ResourceDefinition = new BrokerResourceDefinition()!; /// public Broker() : base(ResourceDefinition) { } @@ -40,14 +32,4 @@ public Broker() : base(ResourceDefinition) { } /// public Broker(ResourceMetadata metadata, BrokerSpec spec, BrokerStatus? status = null) : base(ResourceDefinition, metadata, spec, status) { } - /// - [Required, DefaultValue(CloudStreamsDefaults.ResourceGroup + "/" + ResourceVersion)] - [DataMember(Order = 1, Name = "apiVersion", IsRequired = true), JsonPropertyOrder(1), JsonPropertyName("apiVersion"), YamlMember(Order = 1, Alias = "apiVersion")] - public override string ApiVersion { get => base.ApiVersion; set => base.ApiVersion = value; } - - /// - [Required, DefaultValue(ResourceKind)] - [DataMember(Order = 2, Name = "kind", IsRequired = true), JsonPropertyOrder(2), JsonPropertyName("kind"), YamlMember(Order = 2, Alias = "kind")] - public override string Kind { get => base.Kind; set => base.Kind = value; } - } diff --git a/src/core/CloudStreams.Core/Assets/Definitions/broker.yaml b/src/core/CloudStreams.Core/Resources/Broker.yaml similarity index 100% rename from src/core/CloudStreams.Core/Assets/Definitions/broker.yaml rename to src/core/CloudStreams.Core/Resources/Broker.yaml diff --git a/src/core/CloudStreams.Core/Data/BrokerDispatchConfiguration.cs b/src/core/CloudStreams.Core/Resources/BrokerDispatchConfiguration.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/BrokerDispatchConfiguration.cs rename to src/core/CloudStreams.Core/Resources/BrokerDispatchConfiguration.cs index d4d1db9f..eead97b0 100644 --- a/src/core/CloudStreams.Core/Data/BrokerDispatchConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/BrokerDispatchConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the way a broker should dispatch cloud events diff --git a/src/core/CloudStreams.Core/Resources/BrokerResourceDefinition.cs b/src/core/CloudStreams.Core/Resources/BrokerResourceDefinition.cs new file mode 100644 index 00000000..75d4dee2 --- /dev/null +++ b/src/core/CloudStreams.Core/Resources/BrokerResourceDefinition.cs @@ -0,0 +1,61 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Resources; + +/// +/// Represents the definition of a +/// +[DataContract] +public record BrokerResourceDefinition + : ResourceDefinition +{ + + /// + /// Gets the definition of s + /// + public static new ResourceDefinition Instance { get; set; } + /// + /// Gets/sets the group resource definitions belong to + /// + public static new string ResourceGroup { get; set; } = DefaultResourceGroup; + /// + /// Gets/sets the resource version of resource definitions + /// + public static new string ResourceVersion { get; set; } + /// + /// Gets/sets the plural name of resource definitions + /// + public static new string ResourcePlural { get; set; } + /// + /// Gets/sets the kind of resource definitions + /// + public static new string ResourceKind { get; set; } + + static BrokerResourceDefinition() + { + using var stream = typeof(Broker).Assembly.GetManifestResourceStream($"{typeof(Broker).Namespace}.{nameof(Broker)}.yaml")!; + using var streamReader = new StreamReader(stream); + Instance = YamlSerializer.Default.Deserialize(streamReader.ReadToEnd())!; + ResourceGroup = Instance.Spec.Group; + ResourceVersion = Instance.Spec.Versions.Last().Name; + ResourcePlural = Instance.Spec.Names.Plural; + ResourceKind = Instance.Spec.Names.Kind; + } + + /// + /// Initializes a new + /// + public BrokerResourceDefinition() : base(Instance) { } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/BrokerSpec.cs b/src/core/CloudStreams.Core/Resources/BrokerSpec.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/BrokerSpec.cs rename to src/core/CloudStreams.Core/Resources/BrokerSpec.cs index d847b948..d6465cb7 100644 --- a/src/core/CloudStreams.Core/Data/BrokerSpec.cs +++ b/src/core/CloudStreams.Core/Resources/BrokerSpec.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event broker diff --git a/src/core/CloudStreams.Core/Data/BrokerStatus.cs b/src/core/CloudStreams.Core/Resources/BrokerStatus.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/BrokerStatus.cs rename to src/core/CloudStreams.Core/Resources/BrokerStatus.cs index 999e8ef9..ad9c9151 100644 --- a/src/core/CloudStreams.Core/Data/BrokerStatus.cs +++ b/src/core/CloudStreams.Core/Resources/BrokerStatus.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe the status of a cloud event broker @@ -44,4 +44,4 @@ public record BrokerStatus [DataMember(Order = 4, Name = "stream"), JsonPropertyOrder(4), JsonPropertyName("stream"), YamlMember(Order = 4, Alias = "stream")] public virtual CloudEventStreamStatus? Stream { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CircuitBreakerPolicy.cs b/src/core/CloudStreams.Core/Resources/CircuitBreakerPolicy.cs similarity index 84% rename from src/core/CloudStreams.Core/Data/CircuitBreakerPolicy.cs rename to src/core/CloudStreams.Core/Resources/CircuitBreakerPolicy.cs index 4191fc1d..c4f85eee 100644 --- a/src/core/CloudStreams.Core/Data/CircuitBreakerPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/CircuitBreakerPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Serialization.Json.Converters; - -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a circuit breaker @@ -32,8 +30,10 @@ public CircuitBreakerPolicy() { } /// /// The maximum attempts after which to break the circuit /// The duration the circuit remains broker - public CircuitBreakerPolicy(int breakAfter, TimeSpan breakDuration) + public CircuitBreakerPolicy(int breakAfter, Duration breakDuration) { + ArgumentNullException.ThrowIfNull(breakDuration); + this.BreakAfter = breakAfter; this.BreakDuration = breakDuration; } @@ -48,8 +48,8 @@ public CircuitBreakerPolicy(int breakAfter, TimeSpan breakDuration) /// /// Gets/sets the duration the circuit remains broken /// - [Required, JsonConverter(typeof(Iso8601TimeSpanConverter))] + [Required] [DataMember(Order = 2, Name = "breakDuration", IsRequired = true), JsonPropertyOrder(2), JsonPropertyName("breakDuration"), YamlMember(Order = 2, Alias = "breakDuration")] - public virtual TimeSpan BreakDuration { get; set; } + public virtual Duration BreakDuration { get; set; } = null!; } \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventAttributeFilter.cs b/src/core/CloudStreams.Core/Resources/CloudEventAttributeFilter.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/CloudEventAttributeFilter.cs rename to src/core/CloudStreams.Core/Resources/CloudEventAttributeFilter.cs index 6b6854a2..66cc240c 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventAttributeFilter.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventAttributeFilter.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event context attribute filter. diff --git a/src/core/CloudStreams.Core/Data/CloudEventAuthorizationPolicy.cs b/src/core/CloudStreams.Core/Resources/CloudEventAuthorizationPolicy.cs similarity index 92% rename from src/core/CloudStreams.Core/Data/CloudEventAuthorizationPolicy.cs rename to src/core/CloudStreams.Core/Resources/CloudEventAuthorizationPolicy.cs index a30dc73c..04e357a2 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventAuthorizationPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventAuthorizationPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe a set of rules that apply to a specific cloud event source @@ -32,6 +32,6 @@ public record CloudEventAuthorizationPolicy /// Gets/sets a list containing the rules the policy is made out of /// [DataMember(Order = 2, Name = "rules"), JsonPropertyName("rules"), YamlMember(Alias = "rules")] - public virtual List? Rules { get; set; } = new(); + public virtual List? Rules { get; set; } = []; -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/Data/CloudEventAuthorizationRule.cs b/src/core/CloudStreams.Core/Resources/CloudEventAuthorizationRule.cs similarity index 97% rename from src/core/CloudStreams.Core/Data/CloudEventAuthorizationRule.cs rename to src/core/CloudStreams.Core/Resources/CloudEventAuthorizationRule.cs index e269f3de..0f2211ed 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventAuthorizationRule.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventAuthorizationRule.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an authorization rule that applies to events produced by a specific source @@ -75,4 +75,4 @@ public record CloudEventAuthorizationRule [DataMember(Order = 7, Name = "maxSize"), JsonPropertyName("maxSize"), YamlMember(Alias = "maxSize")] public virtual long? MaxSize { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventFilter.cs b/src/core/CloudStreams.Core/Resources/CloudEventFilter.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/CloudEventFilter.cs rename to src/core/CloudStreams.Core/Resources/CloudEventFilter.cs index 292ba56b..9220ffaa 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventFilter.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventFilter.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event filter @@ -42,4 +42,4 @@ public record CloudEventFilter [DataMember(Order = 2, Name = "expression"), JsonPropertyName("expression"), YamlMember(Alias = "expression")] public virtual string? Expression { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventIngestionConfiguration.cs b/src/core/CloudStreams.Core/Resources/CloudEventIngestionConfiguration.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/CloudEventIngestionConfiguration.cs rename to src/core/CloudStreams.Core/Resources/CloudEventIngestionConfiguration.cs index dc46b093..82ce4d7b 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventIngestionConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventIngestionConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the ingestion of specific cloud events diff --git a/src/core/CloudStreams.Core/Data/CloudEventMetadataPropertyResolver.cs b/src/core/CloudStreams.Core/Resources/CloudEventMetadataPropertyResolver.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/CloudEventMetadataPropertyResolver.cs rename to src/core/CloudStreams.Core/Resources/CloudEventMetadataPropertyResolver.cs index e531480e..d40a2be9 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventMetadataPropertyResolver.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventMetadataPropertyResolver.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure how to resolve the value of a cloud event metadata property @@ -55,7 +55,6 @@ public CloudEventMetadataPropertyResolver(string name, string expression) /// /// Gets/sets the name of the cloud event metadata property to resolve /// - /// See [Required, MinLength(3)] [DataMember(Order = 1, Name = "name"), JsonPropertyName("name"), YamlMember(Alias = "name")] public virtual string Name { get; set; } = null!; diff --git a/src/core/CloudStreams.Core/Data/CloudEventMetadataResolutionConfiguration.cs b/src/core/CloudStreams.Core/Resources/CloudEventMetadataResolutionConfiguration.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/CloudEventMetadataResolutionConfiguration.cs rename to src/core/CloudStreams.Core/Resources/CloudEventMetadataResolutionConfiguration.cs index 2ec8cd2a..8b0fd55b 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventMetadataResolutionConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventMetadataResolutionConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the way the metadata of ingested cloud events should be resolved diff --git a/src/core/CloudStreams.Core/Data/CloudEventMutation.cs b/src/core/CloudStreams.Core/Resources/CloudEventMutation.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/CloudEventMutation.cs rename to src/core/CloudStreams.Core/Resources/CloudEventMutation.cs index d3756d87..9d00084d 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventMutation.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventMutation.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure how to mutate consumed cloud events diff --git a/src/core/CloudStreams.Core/Data/CloudEventSequencingConfiguration.cs b/src/core/CloudStreams.Core/Resources/CloudEventSequencingConfiguration.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/CloudEventSequencingConfiguration.cs rename to src/core/CloudStreams.Core/Resources/CloudEventSequencingConfiguration.cs index c8e1ff10..92fd4d1f 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventSequencingConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventSequencingConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the way a broker should sequence dispatched cloud events @@ -57,4 +57,4 @@ public record CloudEventSequencingConfiguration [DataMember(Order = 4, Name = "fallbackAttributeName"), JsonPropertyOrder(4), JsonPropertyName("fallbackAttributeName"), YamlMember(Order = 4, Alias = "fallbackAttributeName")] public virtual string? FallbackAttributeName { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventSourceDefinition.cs b/src/core/CloudStreams.Core/Resources/CloudEventSourceDefinition.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/CloudEventSourceDefinition.cs rename to src/core/CloudStreams.Core/Resources/CloudEventSourceDefinition.cs index 7c77ed0b..25d75e97 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventSourceDefinition.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventSourceDefinition.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event source @@ -39,4 +39,4 @@ public record CloudEventSourceDefinition [DataMember(Order = 3, Name = "validation"), JsonPropertyName("validation"), YamlMember(Alias = "validation")] public virtual CloudEventValidationPolicy? Validation { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/CloudEventStream.cs b/src/core/CloudStreams.Core/Resources/CloudEventStream.cs similarity index 91% rename from src/core/CloudStreams.Core/Data/CloudEventStream.cs rename to src/core/CloudStreams.Core/Resources/CloudEventStream.cs index f47b2db2..25b59023 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventStream.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventStream.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event stream diff --git a/src/core/CloudStreams.Core/Data/CloudEventStreamStatus.cs b/src/core/CloudStreams.Core/Resources/CloudEventStreamStatus.cs similarity index 84% rename from src/core/CloudStreams.Core/Data/CloudEventStreamStatus.cs rename to src/core/CloudStreams.Core/Resources/CloudEventStreamStatus.cs index 09a8fbd8..bd6b5b41 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventStreamStatus.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventStreamStatus.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe the status of a cloud event stream @@ -27,7 +27,7 @@ public record CloudEventStreamStatus public virtual ulong? AckedOffset { get; set; } /// - /// Gets/sets an object that describes the last fault that occured while streaming events to subscribers. Streaming is interrupted when fault is set, requiring a user to manually resume streaming + /// Gets/sets an object that describes the last fault that occurred while streaming events to subscribers. Streaming is interrupted when fault is set, requiring a user to manually resume streaming /// [DataMember(Order = 2, Name = "fault"), JsonPropertyName("fault"), YamlMember(Alias = "fault")] public virtual ProblemDetails? Fault { get; set; } diff --git a/src/core/CloudStreams.Core/Data/CloudEventValidationPolicy.cs b/src/core/CloudStreams.Core/Resources/CloudEventValidationPolicy.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/CloudEventValidationPolicy.cs rename to src/core/CloudStreams.Core/Resources/CloudEventValidationPolicy.cs index bd2f5282..ef2dd329 100644 --- a/src/core/CloudStreams.Core/Data/CloudEventValidationPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/CloudEventValidationPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure cloud event validation diff --git a/src/core/CloudStreams.Core/Data/DataSchemaValidationPolicy.cs b/src/core/CloudStreams.Core/Resources/DataSchemaValidationPolicy.cs similarity index 93% rename from src/core/CloudStreams.Core/Data/DataSchemaValidationPolicy.cs rename to src/core/CloudStreams.Core/Resources/DataSchemaValidationPolicy.cs index 7bbdb6ea..04919d8f 100644 --- a/src/core/CloudStreams.Core/Data/DataSchemaValidationPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/DataSchemaValidationPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure schema-based validation of incoming cloud events' data @@ -33,4 +33,4 @@ public record DataSchemaValidationPolicy [DataMember(Order = 2, Name = "autoGenerate"), JsonPropertyName("autoGenerate"), YamlMember(Alias = "autoGenerate")] public virtual bool AutoGenerate { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Resources/Duration.cs b/src/core/CloudStreams.Core/Resources/Duration.cs new file mode 100644 index 00000000..5569d01a --- /dev/null +++ b/src/core/CloudStreams.Core/Resources/Duration.cs @@ -0,0 +1,120 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Resources; + +/// +/// Represents an object used to configure a duration +/// +[DataContract] +public record Duration +{ + + /// + /// Gets/sets the numbers of days, if any + /// + public virtual uint? Days { get; set; } + + /// + /// Gets/sets the numbers of hours, if any + /// + public virtual uint? Hours { get; set; } + + /// + /// Gets/sets the numbers of minutes, if any + /// + public virtual uint? Minutes { get; set; } + + /// + /// Gets/sets the numbers of seconds, if any + /// + public virtual uint? Seconds { get; set; } + + /// + /// Gets/sets the numbers of milliseconds, if any + /// + public virtual uint? Milliseconds { get; set; } + + /// + /// Converts the to a new + /// + /// A new + public virtual TimeSpan ToTimeSpan() => new((int)(this.Days ?? 0), (int)(this.Hours ?? 0), (int)(this.Minutes ?? 0), (int)(this.Seconds ?? 0), (int)(this.Milliseconds ?? 0)); + + /// + /// Gets a zero value + /// + public static readonly Duration Zero = new(); + + /// + /// Creates a new object representing the specified number of days. + /// + /// The number of days. + /// A new object with the specified number of days. + public static Duration FromDays(uint days) => new() { Days = days }; + + /// + /// Creates a new object representing the specified number of hours. + /// + /// The number of hours. + /// A new object with the specified number of hours. + public static Duration FromHours(uint hours) => new() { Hours = hours }; + + /// + /// Creates a new object representing the specified number of minutes. + /// + /// The number of minutes. + /// A new object with the specified number of minutes. + public static Duration FromMinutes(uint minutes) => new() { Minutes = minutes }; + + /// + /// Creates a new object representing the specified number of seconds. + /// + /// The number of seconds. + /// A new object with the specified number of seconds. + public static Duration FromSeconds(uint seconds) => new() { Seconds = seconds }; + + /// + /// Creates a new object representing the specified number of milliseconds. + /// + /// The number of milliseconds. + /// A new object with the specified number of milliseconds. + public static Duration FromMilliseconds(uint milliseconds) => new() { Milliseconds = milliseconds }; + + /// + /// Creates a new representing the specified . + /// + /// The to convert. + /// A new representing the specified . + public static Duration FromTimeSpan(TimeSpan timeSpan) => new() + { + Days = (uint)timeSpan.Days, + Hours = (uint)timeSpan.Hours, + Minutes = (uint)timeSpan.Minutes, + Seconds = (uint)timeSpan.Seconds, + Milliseconds = (uint)timeSpan.Milliseconds + }; + + /// + /// Converts the specified into a new + /// + /// The to convert + public static implicit operator TimeSpan?(Duration? duration) => duration?.ToTimeSpan(); + + /// + /// Converts the specified into a new + /// + /// The to convert + public static implicit operator Duration?(TimeSpan? timeSpan) => timeSpan == null ? null : FromTimeSpan(timeSpan.Value); + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Resources/Gateway.cs b/src/core/CloudStreams.Core/Resources/Gateway.cs new file mode 100644 index 00000000..efe57351 --- /dev/null +++ b/src/core/CloudStreams.Core/Resources/Gateway.cs @@ -0,0 +1,35 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Resources; + +/// +/// Represents a cloud event gateway, responsible for authorizing, validating and persisting inbound events +/// +[DataContract] +public record Gateway + : Resource +{ + + /// + /// Gets the 's resource type + /// + public static readonly ResourceDefinitionInfo ResourceDefinition = new GatewayResourceDefinition()!; + + /// + public Gateway() : base(ResourceDefinition) { } + + /// + public Gateway(ResourceMetadata metadata, GatewaySpec spec) : base(ResourceDefinition, metadata, spec) { } + +} diff --git a/src/core/CloudStreams.Core/Assets/Definitions/gateway.yaml b/src/core/CloudStreams.Core/Resources/Gateway.yaml similarity index 99% rename from src/core/CloudStreams.Core/Assets/Definitions/gateway.yaml rename to src/core/CloudStreams.Core/Resources/Gateway.yaml index b3ae3d69..14b4e032 100644 --- a/src/core/CloudStreams.Core/Assets/Definitions/gateway.yaml +++ b/src/core/CloudStreams.Core/Resources/Gateway.yaml @@ -1,4 +1,4 @@ -apiVersion: apiextensions.k8s.io/v1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: gateways.cloud-streams.io diff --git a/src/core/CloudStreams.Core/Resources/GatewayResourceDefinition.cs b/src/core/CloudStreams.Core/Resources/GatewayResourceDefinition.cs new file mode 100644 index 00000000..e189c2f5 --- /dev/null +++ b/src/core/CloudStreams.Core/Resources/GatewayResourceDefinition.cs @@ -0,0 +1,61 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Resources; + +/// +/// Represents the definition of a +/// +[DataContract] +public record GatewayResourceDefinition + : ResourceDefinition +{ + + /// + /// Gets the definition of s + /// + public static new ResourceDefinition Instance { get; set; } + /// + /// Gets/sets the group resource definitions belong to + /// + public static new string ResourceGroup { get; set; } = DefaultResourceGroup; + /// + /// Gets/sets the resource version of resource definitions + /// + public static new string ResourceVersion { get; set; } + /// + /// Gets/sets the plural name of resource definitions + /// + public static new string ResourcePlural { get; set; } + /// + /// Gets/sets the kind of resource definitions + /// + public static new string ResourceKind { get; set; } + + static GatewayResourceDefinition() + { + using var stream = typeof(Gateway).Assembly.GetManifestResourceStream($"{typeof(Gateway).Namespace}.{nameof(Gateway)}.yaml")!; + using var streamReader = new StreamReader(stream); + Instance = YamlSerializer.Default.Deserialize(streamReader.ReadToEnd())!; + ResourceGroup = Instance.Spec.Group; + ResourceVersion = Instance.Spec.Versions.Last().Name; + ResourcePlural = Instance.Spec.Names.Plural; + ResourceKind = Instance.Spec.Names.Kind; + } + + /// + /// Initializes a new + /// + public GatewayResourceDefinition() : base(Instance) { } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/GatewaySpec.cs b/src/core/CloudStreams.Core/Resources/GatewaySpec.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/GatewaySpec.cs rename to src/core/CloudStreams.Core/Resources/GatewaySpec.cs index 9ea48b1f..45acf516 100644 --- a/src/core/CloudStreams.Core/Data/GatewaySpec.cs +++ b/src/core/CloudStreams.Core/Resources/GatewaySpec.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event gateway diff --git a/src/core/CloudStreams.Core/Data/GatewayStatus.cs b/src/core/CloudStreams.Core/Resources/GatewayStatus.cs similarity index 93% rename from src/core/CloudStreams.Core/Data/GatewayStatus.cs rename to src/core/CloudStreams.Core/Resources/GatewayStatus.cs index 3acc15f0..51f18121 100644 --- a/src/core/CloudStreams.Core/Data/GatewayStatus.cs +++ b/src/core/CloudStreams.Core/Resources/GatewayStatus.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe the status of a gateway @@ -32,4 +32,4 @@ public record GatewayStatus [DataMember(Order = 2, Name = "lastHealthCheckAt"), JsonPropertyOrder(2), JsonPropertyName("lastHealthCheckAt"), YamlMember(Order = 2, Alias = "lastHealthCheckAt")] public virtual DateTimeOffset? LastHealthCheckAt { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/HealthCheckResponse.cs b/src/core/CloudStreams.Core/Resources/HealthCheckResponse.cs similarity index 91% rename from src/core/CloudStreams.Core/Data/HealthCheckResponse.cs rename to src/core/CloudStreams.Core/Resources/HealthCheckResponse.cs index 72291b7a..e9b3f06e 100644 --- a/src/core/CloudStreams.Core/Data/HealthCheckResponse.cs +++ b/src/core/CloudStreams.Core/Resources/HealthCheckResponse.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe an health check's response @@ -32,7 +32,7 @@ public HealthCheckResponse() { } /// A list containing objects that describe the checks that have been performed public HealthCheckResponse(string status, IEnumerable? checks = null) { - if(string.IsNullOrWhiteSpace(status)) throw new ArgumentNullException(nameof(status)); + if (string.IsNullOrWhiteSpace(status)) throw new ArgumentNullException(nameof(status)); this.Status = status; this.Checks = checks == null ? null : new(checks); } diff --git a/src/core/CloudStreams.Core/Data/HealthCheckResult.cs b/src/core/CloudStreams.Core/Resources/HealthCheckResult.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/HealthCheckResult.cs rename to src/core/CloudStreams.Core/Resources/HealthCheckResult.cs index 75920d55..f2c89cab 100644 --- a/src/core/CloudStreams.Core/Data/HealthCheckResult.cs +++ b/src/core/CloudStreams.Core/Resources/HealthCheckResult.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe the result of an health check diff --git a/src/core/CloudStreams.Core/Data/HttpClientRetryPolicy.cs b/src/core/CloudStreams.Core/Resources/HttpClientRetryPolicy.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/HttpClientRetryPolicy.cs rename to src/core/CloudStreams.Core/Resources/HttpClientRetryPolicy.cs index ff8e0467..d79d5ff9 100644 --- a/src/core/CloudStreams.Core/Data/HttpClientRetryPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/HttpClientRetryPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the retry policy for an http client diff --git a/src/core/CloudStreams.Core/Data/HttpRequestConfiguration.cs b/src/core/CloudStreams.Core/Resources/HttpRequestConfiguration.cs similarity index 91% rename from src/core/CloudStreams.Core/Data/HttpRequestConfiguration.cs rename to src/core/CloudStreams.Core/Resources/HttpRequestConfiguration.cs index 3ec258bb..38c09d62 100644 --- a/src/core/CloudStreams.Core/Data/HttpRequestConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/HttpRequestConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure an HTTP request @@ -35,7 +35,7 @@ public HttpRequestConfiguration() { } public HttpRequestConfiguration(string method, string path, IDictionary? headers = null, IDictionary? body = null) { if (string.IsNullOrWhiteSpace(method)) throw new ArgumentNullException(nameof(method)); - if(string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); this.Method = method; this.Path = path; this.Headers = headers; @@ -59,7 +59,7 @@ public HttpRequestConfiguration(string method, string path, IDictionary /// Gets/sets the headers of the HTTP request to perform /// - [DataMember(Order = 3, Name = "headers"), JsonPropertyOrder(3), JsonPropertyName("headers"), YamlMember(Order =3, Alias = "headers")] + [DataMember(Order = 3, Name = "headers"), JsonPropertyOrder(3), JsonPropertyName("headers"), YamlMember(Order = 3, Alias = "headers")] public virtual IDictionary? Headers { get; set; } /// @@ -68,4 +68,4 @@ public HttpRequestConfiguration(string method, string path, IDictionary? Body { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/PartitionReference.cs b/src/core/CloudStreams.Core/Resources/PartitionReference.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/PartitionReference.cs rename to src/core/CloudStreams.Core/Resources/PartitionReference.cs index 82e998e2..6c7350bd 100644 --- a/src/core/CloudStreams.Core/Data/PartitionReference.cs +++ b/src/core/CloudStreams.Core/Resources/PartitionReference.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to reference a cloud event partition @@ -49,4 +49,4 @@ public PartitionReference(CloudEventPartitionType type, string id) [DataMember(Order = 2, Name = "id"), JsonPropertyName("id"), YamlMember(Alias = "id")] public virtual string Id { get; set; } = null!; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/RetryBackoffDuration.cs b/src/core/CloudStreams.Core/Resources/RetryBackoffDuration.cs similarity index 83% rename from src/core/CloudStreams.Core/Data/RetryBackoffDuration.cs rename to src/core/CloudStreams.Core/Resources/RetryBackoffDuration.cs index 54e31f59..6d0f7db8 100644 --- a/src/core/CloudStreams.Core/Data/RetryBackoffDuration.cs +++ b/src/core/CloudStreams.Core/Resources/RetryBackoffDuration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Serialization.Json.Converters; - -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a retry backoff duration @@ -32,9 +30,9 @@ public record RetryBackoffDuration /// /// Gets/sets the period of time to wait between retry attempts /// - [Required, JsonConverter(typeof(Iso8601TimeSpanConverter))] + [Required] [DataMember(Order = 2, Name = "period", IsRequired = true), JsonPropertyName("period"), YamlMember(Alias = "period")] - public virtual TimeSpan Period { get; set; } + public virtual Duration Period { get; set; } = null!; /// /// Gets/sets a value representing the power to which the specified period of time is to be raised to obtain the time to wait between each retry attempts @@ -46,15 +44,15 @@ public record RetryBackoffDuration /// Creates a new constant /// /// The constant period of time to wait between retry attempts - /// A new contant - public static RetryBackoffDuration Constant(TimeSpan period) => new() { Type = RetryBackoffDurationType.Constant, Period = period }; + /// A new constant + public static RetryBackoffDuration Constant(Duration period) => new() { Type = RetryBackoffDurationType.Constant, Period = period }; /// /// Creates a new multiplier-based /// /// The constant period of time to wait between retry attempts /// A new multiplier-based - public static RetryBackoffDuration Incremental(TimeSpan period) => new() { Type = RetryBackoffDurationType.Incremental, Period = period}; + public static RetryBackoffDuration Incremental(Duration period) => new() { Type = RetryBackoffDurationType.Incremental, Period = period }; /// /// Creates a new exponential @@ -62,6 +60,6 @@ public record RetryBackoffDuration /// The constant period of time to wait between retry attempts /// The value representing the power to which the specified period of time is to be raised to obtain the time to wait between each retry attempts /// A new exponential - public static RetryBackoffDuration Exponential(TimeSpan period, double exponent) => new() { Type = RetryBackoffDurationType.Exponential, Period = period, Exponent = exponent }; + public static RetryBackoffDuration Exponential(Duration period, double exponent) => new() { Type = RetryBackoffDurationType.Exponential, Period = period, Exponent = exponent }; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/RetryPolicy.cs b/src/core/CloudStreams.Core/Resources/RetryPolicy.cs similarity index 83% rename from src/core/CloudStreams.Core/Data/RetryPolicy.cs rename to src/core/CloudStreams.Core/Resources/RetryPolicy.cs index 920fe929..1f5cffc7 100644 --- a/src/core/CloudStreams.Core/Data/RetryPolicy.cs +++ b/src/core/CloudStreams.Core/Resources/RetryPolicy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a retry policy @@ -20,6 +20,9 @@ namespace CloudStreams.Core.Data; public record RetryPolicy { + static readonly RetryBackoffDuration DefaultRetryBackoffDuration = RetryBackoffDuration.Constant(Duration.FromSeconds(3)); + const int DefaultMaxAttempts = 5; + /// /// Initializes a new /// @@ -32,7 +35,7 @@ public RetryPolicy() { } /// public RetryPolicy(RetryBackoffDuration backoffDuration, int? maxAttempts = null) { - this.BackoffDuration = backoffDuration ?? throw new ArgumentNullException(nameof(backoffDuration)); + this.BackoffDuration = backoffDuration ?? DefaultRetryBackoffDuration; this.MaxAttempts = maxAttempts; } @@ -49,4 +52,4 @@ public RetryPolicy(RetryBackoffDuration backoffDuration, int? maxAttempts = null [DataMember(Order = 2, Name = "maxAttempts"), JsonPropertyName("maxAttempts"), YamlMember(Alias = "maxAttempts")] public virtual int? MaxAttempts { get; set; } -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/Data/ServiceConfiguration.cs b/src/core/CloudStreams.Core/Resources/ServiceConfiguration.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/ServiceConfiguration.cs rename to src/core/CloudStreams.Core/Resources/ServiceConfiguration.cs index 6fc0fce2..c2668ed6 100644 --- a/src/core/CloudStreams.Core/Data/ServiceConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/ServiceConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a service diff --git a/src/core/CloudStreams.Core/Data/ServiceHealthCheckConfiguration.cs b/src/core/CloudStreams.Core/Resources/ServiceHealthCheckConfiguration.cs similarity index 87% rename from src/core/CloudStreams.Core/Data/ServiceHealthCheckConfiguration.cs rename to src/core/CloudStreams.Core/Resources/ServiceHealthCheckConfiguration.cs index c90c3abb..ad0728ab 100644 --- a/src/core/CloudStreams.Core/Data/ServiceHealthCheckConfiguration.cs +++ b/src/core/CloudStreams.Core/Resources/ServiceHealthCheckConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Serialization.Json.Converters; - -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the health checks of a service @@ -32,7 +30,7 @@ public ServiceHealthCheckConfiguration() { } /// /// An object used to configure the HTTP-based health check request /// The amount of time to wait between every health check request - public ServiceHealthCheckConfiguration(HttpRequestConfiguration request, TimeSpan? interval = null) + public ServiceHealthCheckConfiguration(HttpRequestConfiguration request, Duration? interval = null) { this.Request = request; this.Interval = interval; @@ -48,8 +46,7 @@ public ServiceHealthCheckConfiguration(HttpRequestConfiguration request, TimeSpa /// /// Gets/sets the amount of time to wait between every health check request /// - [JsonConverter(typeof(Iso8601TimeSpanConverter))] [DataMember(Order = 2, Name = "interval"), JsonPropertyOrder(2), JsonPropertyName("interval"), YamlMember(Order = 2, Alias = "interval")] - public virtual TimeSpan? Interval { get; set; } + public virtual Duration? Interval { get; set; } } diff --git a/src/core/CloudStreams.Core/Data/Subscriber.cs b/src/core/CloudStreams.Core/Resources/Subscriber.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/Subscriber.cs rename to src/core/CloudStreams.Core/Resources/Subscriber.cs index ccac7c9e..a8620afe 100644 --- a/src/core/CloudStreams.Core/Data/Subscriber.cs +++ b/src/core/CloudStreams.Core/Resources/Subscriber.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure the consumer of cloud events produced by a subscription diff --git a/src/core/CloudStreams.Core/Data/Subscription.cs b/src/core/CloudStreams.Core/Resources/Subscription.cs similarity index 52% rename from src/core/CloudStreams.Core/Data/Subscription.cs rename to src/core/CloudStreams.Core/Resources/Subscription.cs index 0f17eaec..90e0e456 100644 --- a/src/core/CloudStreams.Core/Data/Subscription.cs +++ b/src/core/CloudStreams.Core/Resources/Subscription.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents a cloud event subscription @@ -21,18 +21,10 @@ public record Subscription : Resource { - const string ResourceGroup = CloudStreamsDefaults.ResourceGroup; - - const string ResourceVersion = "v1"; - - const string ResourcePlural = "subscriptions"; - - const string ResourceKind = "Subscription"; - /// /// Gets the 's resource type /// - public static readonly ResourceDefinitionInfo ResourceDefinition = new(ResourceGroup, ResourceVersion, ResourcePlural, ResourceKind); + public static readonly ResourceDefinitionInfo ResourceDefinition = new SubscriptionResourceDefinition()!; /// public Subscription() : base(ResourceDefinition) { } @@ -40,14 +32,4 @@ public Subscription() : base(ResourceDefinition) { } /// public Subscription(ResourceMetadata metadata, SubscriptionSpec spec, SubscriptionStatus? status = null) : base(ResourceDefinition, metadata, spec, status) { } - /// - [Required, DefaultValue(CloudStreamsDefaults.ResourceGroup + "/" + ResourceVersion)] - [DataMember(Order = 1, Name = "apiVersion", IsRequired = true), JsonPropertyOrder(1), JsonPropertyName("apiVersion"), YamlMember(Order = 1, Alias = "apiVersion")] - public override string ApiVersion { get => base.ApiVersion; set => base.ApiVersion = value; } - - /// - [Required, DefaultValue(ResourceKind)] - [DataMember(Order = 2, Name = "kind", IsRequired = true), JsonPropertyOrder(2), JsonPropertyName("kind"), YamlMember(Order = 2, Alias = "kind")] - public override string Kind { get => base.Kind; set => base.Kind = value; } - } diff --git a/src/core/CloudStreams.Core/Assets/Definitions/subscription.yaml b/src/core/CloudStreams.Core/Resources/Subscription.yaml similarity index 100% rename from src/core/CloudStreams.Core/Assets/Definitions/subscription.yaml rename to src/core/CloudStreams.Core/Resources/Subscription.yaml diff --git a/src/core/CloudStreams.Core/Resources/SubscriptionResourceDefinition.cs b/src/core/CloudStreams.Core/Resources/SubscriptionResourceDefinition.cs new file mode 100644 index 00000000..7154c4a7 --- /dev/null +++ b/src/core/CloudStreams.Core/Resources/SubscriptionResourceDefinition.cs @@ -0,0 +1,61 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace CloudStreams.Core.Resources; + +/// +/// Represents the definition of a +/// +[DataContract] +public record SubscriptionResourceDefinition + : ResourceDefinition +{ + + /// + /// Gets the definition of s + /// + public static new ResourceDefinition Instance { get; set; } + /// + /// Gets/sets the group resource definitions belong to + /// + public static new string ResourceGroup { get; set; } = DefaultResourceGroup; + /// + /// Gets/sets the resource version of resource definitions + /// + public static new string ResourceVersion { get; set; } + /// + /// Gets/sets the plural name of resource definitions + /// + public static new string ResourcePlural { get; set; } + /// + /// Gets/sets the kind of resource definitions + /// + public static new string ResourceKind { get; set; } + + static SubscriptionResourceDefinition() + { + using var stream = typeof(Subscription).Assembly.GetManifestResourceStream($"{typeof(Subscription).Namespace}.{nameof(Subscription)}.yaml")!; + using var streamReader = new StreamReader(stream); + Instance = YamlSerializer.Default.Deserialize(streamReader.ReadToEnd())!; + ResourceGroup = Instance.Spec.Group; + ResourceVersion = Instance.Spec.Versions.Last().Name; + ResourcePlural = Instance.Spec.Names.Plural; + ResourceKind = Instance.Spec.Names.Kind; + } + + /// + /// Initializes a new + /// + public SubscriptionResourceDefinition() : base(Instance) { } + +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/SubscriptionSpec.cs b/src/core/CloudStreams.Core/Resources/SubscriptionSpec.cs similarity index 96% rename from src/core/CloudStreams.Core/Data/SubscriptionSpec.cs rename to src/core/CloudStreams.Core/Resources/SubscriptionSpec.cs index 2da1fdc1..ade9b3ad 100644 --- a/src/core/CloudStreams.Core/Data/SubscriptionSpec.cs +++ b/src/core/CloudStreams.Core/Resources/SubscriptionSpec.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a cloud event subscription @@ -57,4 +57,4 @@ public SubscriptionSpec() { } [DataMember(Order = 5, Name = "subscriber", IsRequired = true), JsonPropertyName("subscriber"), YamlMember(Alias = "subscriber")] public virtual Subscriber Subscriber { get; set; } = null!; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/SubscriptionStatus.cs b/src/core/CloudStreams.Core/Resources/SubscriptionStatus.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/SubscriptionStatus.cs rename to src/core/CloudStreams.Core/Resources/SubscriptionStatus.cs index 99203b60..02ac5dd9 100644 --- a/src/core/CloudStreams.Core/Data/SubscriptionStatus.cs +++ b/src/core/CloudStreams.Core/Resources/SubscriptionStatus.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to describe the status of a cloud event subscription @@ -39,4 +39,4 @@ public record SubscriptionStatus [DataMember(Order = 3, Name = "stream"), JsonPropertyName("stream"), YamlMember(Alias = "stream")] public virtual CloudEventStreamStatus? Stream { get; set; } -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/Webhook.cs b/src/core/CloudStreams.Core/Resources/Webhook.cs similarity index 91% rename from src/core/CloudStreams.Core/Data/Webhook.cs rename to src/core/CloudStreams.Core/Resources/Webhook.cs index b3d85b4f..853cde67 100644 --- a/src/core/CloudStreams.Core/Data/Webhook.cs +++ b/src/core/CloudStreams.Core/Resources/Webhook.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core.Resources; /// /// Represents an object used to configure a webhook @@ -27,4 +27,4 @@ public record Webhook [DataMember(Order = 1, Name = "serviceUri", IsRequired = true), JsonPropertyName("serviceUri"), YamlMember(Alias = "serviceUri")] public virtual Uri ServiceUri { get; set; } = null!; -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/RetryBackoffDurationType.cs b/src/core/CloudStreams.Core/RetryBackoffDurationType.cs index bd3d416a..64944eff 100644 --- a/src/core/CloudStreams.Core/RetryBackoffDurationType.cs +++ b/src/core/CloudStreams.Core/RetryBackoffDurationType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,30 +11,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all supported retry backoff duration types /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum RetryBackoffDurationType { /// /// Indicates a constant duration /// [EnumMember(Value = "constant")] - Constant, + Constant = 1, /// /// Indicates a duration that increments at every retry attempt in a constant fashion /// [EnumMember(Value = "incremental")] - Incremental, + Incremental = 2, /// /// Indicates an exponential duration /// [EnumMember(Value = "exponential")] - Exponential -} \ No newline at end of file + Exponential = 4 +} diff --git a/src/core/CloudStreams.Core/RuleBasedDecisionStrategy.cs b/src/core/CloudStreams.Core/RuleBasedDecisionStrategy.cs index 86c54d3f..8b0f8155 100644 --- a/src/core/CloudStreams.Core/RuleBasedDecisionStrategy.cs +++ b/src/core/CloudStreams.Core/RuleBasedDecisionStrategy.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core/Serialization/Json/Converters/DateTimeParserConverter.cs b/src/core/CloudStreams.Core/Serialization/Json/Converters/DateTimeParserConverter.cs deleted file mode 100644 index 6eb911b8..00000000 --- a/src/core/CloudStreams.Core/Serialization/Json/Converters/DateTimeParserConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Serialization.Json.Converters; - -/// -/// Represents a used to read and write s -/// -public class DateTimeParserConverter - : JsonConverter -{ - - /// - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => DateTime.Parse(reader.GetString() ?? string.Empty); - - /// - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Serialization/Json/Converters/Iso8601TimeSpanConverter.cs b/src/core/CloudStreams.Core/Serialization/Json/Converters/Iso8601TimeSpanConverter.cs deleted file mode 100644 index faed0096..00000000 --- a/src/core/CloudStreams.Core/Serialization/Json/Converters/Iso8601TimeSpanConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace CloudStreams.Core.Serialization.Json.Converters; - -/// -/// Represents a used to read and write s from and to the ISO 8601 format -/// -public class Iso8601TimeSpanConverter - : JsonConverter -{ - - /// - public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var timespanStr = reader.GetString(); - if (string.IsNullOrEmpty(timespanStr)) return default; - return Iso8601TimeSpan.Parse(timespanStr); - } - - /// - public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) - { - writer.WriteStringValue(Iso8601TimeSpan.Format(value)); - } - -} diff --git a/src/core/CloudStreams.Core/Serialization/StringEnumTypeConverter.cs b/src/core/CloudStreams.Core/Serialization/StringEnumTypeConverter.cs deleted file mode 100644 index 69b5c3b8..00000000 --- a/src/core/CloudStreams.Core/Serialization/StringEnumTypeConverter.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -namespace CloudStreams.Core.Serialization; - -/// -/// Represents an used to convert enum using the values specified by s -/// -public class StringEnumMemberConverter - : EnumConverter -{ - - /// - public StringEnumMemberConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type type) : base(type) { } - - /// - public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) - { - if (value is string strValue) - { - try - { - foreach (var name in Enum.GetNames(EnumType)) - { - var field = EnumType.GetField(name); - if (field != null) - { - var enumMember = (EnumMemberAttribute)(field.GetCustomAttributes(typeof(EnumMemberAttribute), true).Single()); - if (strValue.Equals(enumMember.Value, StringComparison.OrdinalIgnoreCase)) return Enum.Parse(EnumType, name, true); - } - } - } - catch (Exception e) - { - throw new FormatException((string)value, e); - } - } - return base.ConvertFrom(context, culture, value); - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/StreamMetadata.cs b/src/core/CloudStreams.Core/StreamMetadata.cs similarity index 94% rename from src/core/CloudStreams.Core/Data/StreamMetadata.cs rename to src/core/CloudStreams.Core/StreamMetadata.cs index 8a5eeab4..71fe06ce 100644 --- a/src/core/CloudStreams.Core/Data/StreamMetadata.cs +++ b/src/core/CloudStreams.Core/StreamMetadata.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +namespace CloudStreams.Core; /// /// Represents an object used to describe a cloud event stream diff --git a/src/core/CloudStreams.Core/StreamReadDirection.cs b/src/core/CloudStreams.Core/StreamReadDirection.cs deleted file mode 100644 index ca1d9805..00000000 --- a/src/core/CloudStreams.Core/StreamReadDirection.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo.Serialization.Json; - -namespace CloudStreams.Core; - -/// -/// Enumerates all supported read directions for streams -/// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] -public enum StreamReadDirection -{ - /// - /// Specifies a forward direction - /// - [EnumMember(Value = "forwards")] - Forwards, - /// - /// Specifies a backward direction - /// - [EnumMember(Value = "backwards")] - Backwards -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Data/StreamReadOptions.cs b/src/core/CloudStreams.Core/StreamReadOptions.cs similarity index 95% rename from src/core/CloudStreams.Core/Data/StreamReadOptions.cs rename to src/core/CloudStreams.Core/StreamReadOptions.cs index 2365c773..e7391268 100644 --- a/src/core/CloudStreams.Core/Data/StreamReadOptions.cs +++ b/src/core/CloudStreams.Core/StreamReadOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Data; +using CloudStreams.Core.Resources; +using Neuroglia.Data.Infrastructure.EventSourcing; + +namespace CloudStreams.Core; /// /// Represents an object used to configure the options of a query used to read a cloud event stream @@ -88,4 +91,4 @@ public StreamReadOptions(StreamReadDirection direction = StreamReadDirection.For [DataMember(Order = 5, Name = "outputFormat"), JsonPropertyName("outputFormat"), YamlMember(Alias = "outputFormat")] public virtual StreamReadOutputFormat Format { get; set; } = StreamReadOutputFormat.Event; -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core/StreamReadOutputFormat.cs b/src/core/CloudStreams.Core/StreamReadOutputFormat.cs index e77e1521..11b757b6 100644 --- a/src/core/CloudStreams.Core/StreamReadOutputFormat.cs +++ b/src/core/CloudStreams.Core/StreamReadOutputFormat.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// /// Enumerates all supported read output formats /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum StreamReadOutputFormat { /// diff --git a/src/core/CloudStreams.Core/SubscriptionStatusPhase.cs b/src/core/CloudStreams.Core/SubscriptionStatusPhase.cs index 3c368709..aac7a28a 100644 --- a/src/core/CloudStreams.Core/SubscriptionStatusPhase.cs +++ b/src/core/CloudStreams.Core/SubscriptionStatusPhase.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo.Serialization.Json; +using Neuroglia.Serialization.Json.Converters; namespace CloudStreams.Core; /// -/// Enumerates resoruce label selection operators +/// Enumerates resource label selection operators /// -[TypeConverter(typeof(StringEnumMemberConverter))] -[JsonConverter(typeof(JsonStringEnumConverterFactory))] +[TypeConverter(typeof(EnumMemberTypeConverter))] +[JsonConverter(typeof(StringEnumConverter))] public enum SubscriptionStatusPhase { /// diff --git a/src/core/CloudStreams.Core/Usings.cs b/src/core/CloudStreams.Core/Usings.cs index b45b62d0..56e533ed 100644 --- a/src/core/CloudStreams.Core/Usings.cs +++ b/src/core/CloudStreams.Core/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using Hylo; +global using Neuroglia; +global using Neuroglia.Data.Infrastructure.ResourceOriented; +global using Neuroglia.Eventing.CloudEvents; +global using Neuroglia.Serialization.Yaml; +global using System.ComponentModel; global using System.ComponentModel.DataAnnotations; global using System.Runtime.Serialization; -global using System.Text.Json; global using System.Text.Json.Serialization; global using YamlDotNet.Serialization; -global using System.ComponentModel; -global using CloudStreams.Core.Serialization; \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Utilities/AssemblyLocator.cs b/src/core/CloudStreams.Core/Utilities/AssemblyLocator.cs deleted file mode 100644 index f16faaad..00000000 --- a/src/core/CloudStreams.Core/Utilities/AssemblyLocator.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Reflection; - -namespace CloudStreams; - -/// -/// Acts as a helper class for locating instances -/// -public static class AssemblyLocator -{ - - private static readonly object Lock = new(); - - private static readonly List LoadedAssemblies = new(); - - static AssemblyLocator() - { - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - LoadedAssemblies.Add(assembly); - } - AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; - } - - /// - /// Get all loaded assemlies - /// - /// An of all loaded assemblies - public static IEnumerable GetAssemblies() - { - return LoadedAssemblies.AsEnumerable(); - } - - private static void OnAssemblyLoad(object? sender, AssemblyLoadEventArgs e) - { - lock (Lock) - { - LoadedAssemblies.Add(e.LoadedAssembly); - } - } - -} \ No newline at end of file diff --git a/src/core/CloudStreams.Core/Utilities/TypeCacheUtil.cs b/src/core/CloudStreams.Core/Utilities/TypeCacheUtil.cs deleted file mode 100644 index 96fba086..00000000 --- a/src/core/CloudStreams.Core/Utilities/TypeCacheUtil.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Microsoft.Extensions.Caching.Memory; -using System.Reflection; - -namespace CloudStreams; - -/// -/// Acts as an helper to find a filter types -/// -public static class TypeCacheUtil -{ - - private static readonly MemoryCache Cache = new(new MemoryCacheOptions() { }); - - /// - /// Find types filtered by a given predicate - /// - /// The cache key used to store the results - /// The predicate that filters the types - /// An array containing the assemblies to scan - /// The filtered types - public static IEnumerable FindFilteredTypes(string cacheKey, Func predicate, params Assembly[] assemblies) - { - if (Cache.TryGetValue(cacheKey, out var cachedValue) && cachedValue != null) return (IEnumerable)cachedValue; - var types = assemblies.ToList().SelectMany(a => - { - try - { - return a.DefinedTypes; - } - catch - { - return Array.Empty().AsEnumerable(); - } - }); - var result = new List(types.Count()); - foreach (Type type in types) - { - if (predicate(type)) - { - result.Add(type); - } - } - return result; - } - - /// - /// Find types filtered by a given predicate - /// - /// The cache key used to store the results - /// The predicate that filters the types - /// The filtered types - public static IEnumerable FindFilteredTypes(string cacheKey, Func predicate) => FindFilteredTypes(cacheKey, predicate, AssemblyLocator.GetAssemblies().ToArray()); - -} \ No newline at end of file diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/ActionContext.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/ActionContext.cs index e7c57efb..00912a44 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/ActionContext.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/ActionContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/EffectAttribute.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/EffectAttribute.cs index 29905b72..5f6f7efe 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/EffectAttribute.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/EffectAttribute.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/FeatureAttribute.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/FeatureAttribute.cs index a2cc0264..b4305cd5 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/FeatureAttribute.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/FeatureAttribute.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/ReducerAttribute.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/ReducerAttribute.cs index 4014d055..9945bb3d 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/ReducerAttribute.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Attributes/ReducerAttribute.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/CloudStreams.Dashboard.StateManagement.csproj b/src/dashboard/CloudStreams.Dashboard.StateManagement/CloudStreams.Dashboard.StateManagement.csproj index ea2e9d87..3b372b72 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/CloudStreams.Dashboard.StateManagement.csproj +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/CloudStreams.Dashboard.StateManagement.csproj @@ -1,26 +1,27 @@ - net7.0 + net8.0 enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True Apache-2.0 Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git + true - - - + + + diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/ComponentStore.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/ComponentStore.cs index e7caf8b3..3afa2b7f 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/ComponentStore.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/ComponentStore.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptions.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptions.cs index 8169c442..77e20104 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptions.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptionsBuilder.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptionsBuilder.cs index ee97d163..c47f8d99 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptionsBuilder.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/FluxOptionsBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/IFluxOptionsBuilder.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/IFluxOptionsBuilder.cs index 5aabfec3..fc4ce399 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/IFluxOptionsBuilder.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Configuration/IFluxOptionsBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/DispatchDelegate.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/DispatchDelegate.cs index c2b46bd0..6c491c47 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/DispatchDelegate.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/DispatchDelegate.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Dispatcher.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Dispatcher.cs index 1e23f862..6c083b5c 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Dispatcher.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Dispatcher.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Effect.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Effect.cs index 42e207b9..556e15bc 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Effect.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Effect.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/EffectContext.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/EffectContext.cs index 9e21c44f..a4943a70 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/EffectContext.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/EffectContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IFluxOptionsBuilderExtensions.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IFluxOptionsBuilderExtensions.cs index a2e62577..a1b9eb47 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IFluxOptionsBuilderExtensions.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IFluxOptionsBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IServiceCollectionExtensions.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IServiceCollectionExtensions.cs index 8961ba7c..c2e1dd7a 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IServiceCollectionExtensions.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IStoreExtensions.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IStoreExtensions.cs index a0fb5b3f..264e74a6 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IStoreExtensions.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Extensions/IStoreExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Feature.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Feature.cs index 979d7bef..b2e87dbf 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Feature.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Feature.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; +using Neuroglia; using System.Reactive.Subjects; namespace CloudStreams.Dashboard.StateManagement; @@ -55,21 +55,21 @@ public TState State /// /// Gets a containing the type/s mappings /// - protected Dictionary>> Reducers { get; } = new(); + protected Dictionary>> Reducers { get; } = []; /// public virtual void AddReducer(IReducer reducer) { - if (reducer == null) throw new ArgumentNullException(nameof(reducer)); + ArgumentNullException.ThrowIfNull(reducer); var genericReducerType = reducer.GetType().GetGenericType(typeof(IReducer<,>)) ?? throw new Exception($"The specified {nameof(IReducer)} '{reducer.GetType()}' does not implement the '{typeof(IReducer<,>)}' interface"); var actionType = genericReducerType.GetGenericArguments()[1]; if (this.Reducers.TryGetValue(actionType, out var reducers)) reducers.Add(reducer); - else this.Reducers.Add(actionType, new() { reducer }); + else this.Reducers.Add(actionType, [reducer]); } void IFeature.AddReducer(IReducer reducer) { - if (reducer == null) throw new ArgumentNullException(nameof(reducer)); + ArgumentNullException.ThrowIfNull(reducer); this.AddReducer((IReducer)reducer); } @@ -79,15 +79,15 @@ void IFeature.AddReducer(IReducer reducer) /// public virtual bool ShouldReduceStateFor(object action) { - if (action == null) throw new ArgumentNullException(nameof(action)); + ArgumentNullException.ThrowIfNull(action); return this.Reducers.ContainsKey(action.GetType()); } /// public virtual async Task ReduceStateAsync(IActionContext context, Func reducerPipelineBuilder) { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (reducerPipelineBuilder == null) throw new ArgumentNullException(nameof(reducerPipelineBuilder)); + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(reducerPipelineBuilder); var pipeline = reducerPipelineBuilder(ApplyReducersAsync); this.State = (TState)await pipeline(context); } @@ -99,8 +99,9 @@ public virtual async Task ReduceStateAsync(IActionContext context, FuncThe reduced 's state protected virtual async Task ApplyReducersAsync(IActionContext context) { - if (context == null) throw new ArgumentNullException(nameof(context)); - return (await Task.Run(() => + return (context == null + ? throw new ArgumentNullException(nameof(context)) + : (object?)await Task.Run(() => { var newState = this.State; if (this.Reducers.TryGetValue(context.Action.GetType(), out var reducers)) diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IActionContext.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IActionContext.cs index eae58661..23606564 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IActionContext.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IActionContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IComponentStore.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IComponentStore.cs index ba393a3e..e1d684e9 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IComponentStore.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IComponentStore.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IDispatcher.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IDispatcher.cs index c268ef7b..34e7226b 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IDispatcher.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IDispatcher.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffect.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffect.cs index 19bde4c6..e95684e6 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffect.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffect.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffectContext.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffectContext.cs index b4cc9ab2..32f66fbb 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffectContext.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IEffectContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IFeature.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IFeature.cs index 15c28b35..e5e59d84 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IFeature.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IFeature.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IMiddleware.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IMiddleware.cs index 2f47a66f..3747e65d 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IMiddleware.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IReducer.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IReducer.cs index e5ec39a9..5e17eaac 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IReducer.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IReducer.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IState.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IState.cs index 141717d6..b7c81709 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IState.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IState.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStore.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStore.cs index 1a97e658..6a96acd9 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStore.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStore.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStoreFactory.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStoreFactory.cs index 09b5e5ad..e9ddff96 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStoreFactory.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Interfaces/IStoreFactory.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Reducer.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Reducer.cs index 0c6d9c0b..a42f6f9e 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Reducer.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Reducer.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/State.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/State.cs index 0f9598a0..bec973da 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/State.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/State.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; +using Neuroglia; using System.Reactive.Subjects; namespace CloudStreams.Dashboard.StateManagement; @@ -30,8 +30,7 @@ public class State /// The 's value public State(TState value) { - if(value == null) - throw new ArgumentNullException(nameof(value)); + if(value == null) throw new ArgumentNullException(nameof(value)); this.Value = value; } @@ -48,21 +47,21 @@ public State(TState value) /// /// Gets a containing the type/s mappings /// - protected Dictionary>> Reducers { get; } = new(); + protected Dictionary>> Reducers { get; } = []; /// public virtual void AddReducer(IReducer reducer) { - if(reducer == null) throw new ArgumentNullException(nameof(reducer)); + ArgumentNullException.ThrowIfNull(reducer); var reducerGenericType = reducer.GetType().GetGenericType(typeof(IReducer<,>)) ?? throw new Exception($"The specified {nameof(IReducer)} '{reducer.GetType()}' does not implement the '{typeof(IReducer<,>)}' interface"); var actionType = reducerGenericType.GetGenericArguments()[1]; if (this.Reducers.TryGetValue(actionType, out var reducers)) reducers.Add(reducer); - else this.Reducers.Add(actionType, new List>() { reducer }); + else this.Reducers.Add(actionType, [reducer]); } void IState.AddReducer(IReducer reducer) { - if (reducer == null) throw new ArgumentNullException(nameof(reducer)); + ArgumentNullException.ThrowIfNull(reducer); this.AddReducer((IReducer)reducer); } @@ -72,12 +71,9 @@ void IState.AddReducer(IReducer reducer) /// public virtual bool TryDispatch(object action) { - if (action == null) throw new ArgumentNullException(nameof(action)); + ArgumentNullException.ThrowIfNull(action); if (!this.Reducers.TryGetValue(action.GetType(), out var reducers))return false; - foreach(var reducer in reducers) - { - this.Value = reducer.Reduce(this.Value, action); - } + foreach(var reducer in reducers) this.Value = reducer.Reduce(this.Value, action); this.Stream.OnNext(this.Value!); return true; } diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Store.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Store.cs index 964af556..a8327c9d 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Store.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Store.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Neuroglia.Reactive; using System.Reactive.Subjects; namespace CloudStreams.Dashboard.StateManagement; @@ -62,39 +62,30 @@ public Store(IServiceProvider serviceProvider, ILogger logger, IDispatche /// /// Gets a containing the 's s /// - protected List Features { get; } = new(); + protected List Features { get; } = []; /// /// Gets a containing the types of the 's s /// - protected List Middlewares { get; } = new(); + protected List Middlewares { get; } = []; /// /// Gets a containing the 's s /// - protected List Effects { get; } = new(); + protected List Effects { get; } = []; /// - public virtual object State - { - get - { - return this.Features.ToDictionary(f => f.State.GetType().Name, f => f.State); - } - } + public virtual object State => this.Features.ToDictionary(f => f.State.GetType().Name, f => f.State); IDisposable IObservable.Subscribe(IObserver observer) { - if (observer == null) - throw new ArgumentNullException(nameof(observer)); - return this.Stream.Subscribe(observer); + return observer == null ? throw new ArgumentNullException(nameof(observer)) : this.Stream.Subscribe(observer); } /// public virtual void AddFeature(IFeature feature) { - if(feature == null) - throw new ArgumentNullException(nameof(feature)); + ArgumentNullException.ThrowIfNull(feature); this.Features.Add(feature); feature.Subscribe(this.OnNextState); } @@ -108,8 +99,7 @@ public virtual void AddMiddleware(Type middlewareType) /// public virtual void AddEffect(IEffect effect) { - if (effect == null) - throw new ArgumentNullException(nameof(effect)); + ArgumentNullException.ThrowIfNull(effect); this.Effects.Add(effect); } @@ -130,7 +120,7 @@ protected virtual async Task DispatchAsync(object action) var pipelineBuilder = (DispatchDelegate reducer) => this.Middlewares .AsEnumerable() .Reverse() - .Aggregate(reducer, (next, type) => this.InstanciateMiddleware(type, next).InvokeAsync); + .Aggregate(reducer, (next, type) => this.InstantiateMiddleware(type, next).InvokeAsync); var context = new ActionContext(this.ServiceProvider, this, action); foreach (var feature in this.Features .Where(f => f.ShouldReduceStateFor(action))) @@ -141,7 +131,7 @@ protected virtual async Task DispatchAsync(object action) } catch(Exception ex) { - this.Logger.LogError("An error occured while dispatching an action of type '{actionType}': {ex}", action.GetType().Name, ex.ToString()); + this.Logger.LogError("An error occurred while dispatching an action of type '{actionType}': {ex}", action.GetType().Name, ex.ToString()); throw; } } @@ -149,19 +139,17 @@ protected virtual async Task DispatchAsync(object action) /// /// Creates a new instance of the specified /// - /// The type of to instanciate + /// The type of to instantiate /// The next in the pipeline /// A new - protected virtual IMiddleware InstanciateMiddleware(Type type, DispatchDelegate next) + protected virtual IMiddleware InstantiateMiddleware(Type type, DispatchDelegate next) { - var constructor = type.GetConstructor(Array.Empty()); - if (constructor != null) - return (IMiddleware)constructor.Invoke(Array.Empty()); + var constructor = type.GetConstructor([]); + if (constructor != null) return (IMiddleware)constructor.Invoke([]); var parameters = new List(1); constructor = type.GetConstructors().First(); - if (constructor.GetParameters().Any(p => p.ParameterType == typeof(DispatchDelegate))) - parameters.Add(next); - return (IMiddleware)ActivatorUtilities.CreateInstance(this.ServiceProvider, type, parameters.ToArray()); + if (constructor.GetParameters().Any(p => p.ParameterType == typeof(DispatchDelegate))) parameters.Add(next); + return (IMiddleware)ActivatorUtilities.CreateInstance(this.ServiceProvider, type, [.. parameters]); } /// @@ -171,10 +159,7 @@ protected virtual IMiddleware InstanciateMiddleware(Type type, DispatchDelegate protected virtual void OnApplyEffects(object action) { var applyEffectTasks = new List(); - foreach (var effect in this.Effects) - { - applyEffectTasks.Add(effect.ApplyAsync(action, new EffectContext(this.ServiceProvider, this.Dispatcher))); - } + foreach (var effect in this.Effects) applyEffectTasks.Add(effect.ApplyAsync(action, new EffectContext(this.ServiceProvider, this.Dispatcher))); var exceptions = new List(); Task.Run(async () => { @@ -186,8 +171,7 @@ protected virtual void OnApplyEffects(object action) { exceptions.Add(ex); } - if(exceptions.Any()) - throw new AggregateException(exceptions); + if(exceptions.Count != 0) throw new AggregateException(exceptions); }); } diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/StoreFactory.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/StoreFactory.cs index b0b5d581..37bf2eae 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/StoreFactory.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/StoreFactory.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,202 +12,173 @@ // limitations under the License. using CloudStreams.Dashboard.StateManagement.Configuration; -using Hylo; -using Hylo.EnumerableExtensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Neuroglia; using System.Linq.Expressions; using System.Reflection; -namespace CloudStreams.Dashboard.StateManagement +namespace CloudStreams.Dashboard.StateManagement; + +/// +/// Represents the default implementation of the interface +/// +/// +/// Initializes a new +/// +/// The current +/// The current +public class StoreFactory(IServiceProvider serviceProvider, IOptions fluxOptions) + : IStoreFactory { + static readonly MethodInfo AddFeatureMethod = typeof(IStoreExtensions).GetMethods().First(m => m.Name == nameof(IStoreExtensions.AddFeature) && m.GetParameters().Length == 2); + /// - /// Represents the default implementation of the interface + /// Gets the current /// - public class StoreFactory - : IStoreFactory - { - - private static readonly MethodInfo AddFeatureMethod = typeof(IStoreExtensions).GetMethods().First(m => m.Name == nameof(IStoreExtensions.AddFeature) && m.GetParameters().Length == 2); - - /// - /// Initializes a new - /// - /// The current - /// The current - public StoreFactory(IServiceProvider serviceProvider, IOptions fluxOptions) - { - this.ServiceProvider = serviceProvider; - this.FluxOptions = fluxOptions.Value; - } - - /// - /// Gets the current - /// - protected IServiceProvider ServiceProvider { get; } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; - /// - /// Gets the current - /// - protected FluxOptions FluxOptions { get; } + /// + /// Gets the current + /// + protected FluxOptions FluxOptions { get; } = fluxOptions.Value; - /// - public virtual IStore CreateStore() - { - var store = (IStore)ActivatorUtilities.CreateInstance(this.ServiceProvider, this.FluxOptions.StoreType); - this.ConfigureStoreFeatures(store); - this.ConfigureStoreEffects(store); - this.ConfigureStoreMiddlewares(store); - return store; - } + /// + public virtual IStore CreateStore() + { + var store = (IStore)ActivatorUtilities.CreateInstance(this.ServiceProvider, this.FluxOptions.StoreType); + this.ConfigureStoreFeatures(store); + this.ConfigureStoreEffects(store); + this.ConfigureStoreMiddlewares(store); + return store; + } - /// - /// Finds and configures s for the specified - /// - /// The to add s for - protected virtual void ConfigureStoreFeatures(IStore store) + /// + /// Finds and configures s for the specified + /// + /// The to add s for + protected virtual void ConfigureStoreFeatures(IStore store) + { + ArgumentNullException.ThrowIfNull(store); + foreach (var feature in this.FluxOptions.Features) store.AddFeature(feature); + if (!this.FluxOptions.AutoRegisterFeatures) return; + var reducersPerState = this.FindAndMapReducersPerState(); + foreach (var stateType in TypeCacheUtil.FindFilteredTypes("nflux-features", + t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType && t.TryGetCustomAttribute(out _))) { - if (store == null)throw new ArgumentNullException(nameof(store)); - foreach(var feature in this.FluxOptions.Features) - { - store.AddFeature(feature); - } - if (!this.FluxOptions.AutoRegisterFeatures) - return; - var reducersPerState = this.FindAndMapReducersPerState(); - foreach (var stateType in TypeCacheUtil.FindFilteredTypes("nflux-features", - t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType && t.TryGetCustomAttribute(out _))) - { - if (!reducersPerState.TryGetValue(stateType, out var reducersPerFeature)) - continue; - AddFeatureMethod.MakeGenericMethod(stateType).Invoke(null, new object[] { store, reducersPerFeature.OfType(typeof(IReducer<>).MakeGenericType(stateType)).ToArray() }); - } + if (!reducersPerState.TryGetValue(stateType, out var reducersPerFeature)) continue; + AddFeatureMethod.MakeGenericMethod(stateType).Invoke(null, [store, reducersPerFeature.OfType(typeof(IReducer<>).MakeGenericType(stateType)).OfType().ToArray()]); } + } - /// - /// Finds and configures s for the specified - /// - /// The to add s for - protected virtual void ConfigureStoreEffects(IStore store) + /// + /// Finds and configures s for the specified + /// + /// The to add s for + protected virtual void ConfigureStoreEffects(IStore store) + { + ArgumentNullException.ThrowIfNull(store); + foreach (var effect in this.FluxOptions.Effects) store.AddEffect(effect); + if (!this.FluxOptions.AutoRegisterEffects)return; + foreach (var effectDeclaringType in TypeCacheUtil.FindFilteredTypes("nflux-effects", + t => t.TryGetCustomAttribute< EffectAttribute>(out _) || t.GetMethods().Any(m => m.TryGetCustomAttribute(out _)), this.FluxOptions.AssembliesToScan?.ToArray()!)) { - if (store == null)throw new ArgumentNullException(nameof(store)); - foreach (var effect in this.FluxOptions.Effects) + foreach (var effectMethod in effectDeclaringType.GetMethods() + .Where(m => (effectDeclaringType.TryGetCustomAttribute(out _) && m.IsStatic && m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType == typeof(IEffectContext)) || m.TryGetCustomAttribute(out _))) { + if(effectMethod.ReturnType != typeof(Task)) throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must return a task to be used as a Flux effect"); + if (!effectMethod.IsStatic) throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must be static to be used as a Flux effect"); + if (effectMethod.GetParameters().Length != 2) throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must declare exactly 2 parameters to be used as a Flux effect"); + var actionType = effectMethod.GetParameters()[0].ParameterType; + var effectType = typeof(Effect<>).MakeGenericType(actionType); + var effectLambda = this.BuildEffectLambda(actionType, effectMethod); + var effectFunction = effectLambda.Compile(); + var effect = (IEffect)ActivatorUtilities.CreateInstance(this.ServiceProvider, effectType, effectFunction); store.AddEffect(effect); } - if (!this.FluxOptions.AutoRegisterEffects)return; - foreach (var effectDeclaringType in TypeCacheUtil.FindFilteredTypes("nflux-effects", - t => t.TryGetCustomAttribute< EffectAttribute>(out _) || t.GetMethods().Any(m => m.TryGetCustomAttribute(out _)), this.FluxOptions.AssembliesToScan?.ToArray()!)) - { - foreach (var effectMethod in effectDeclaringType.GetMethods() - .Where(m => (effectDeclaringType.TryGetCustomAttribute(out _) && m.IsStatic && m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType == typeof(IEffectContext)) || m.TryGetCustomAttribute(out _))) - { - if(effectMethod.ReturnType != typeof(Task)) - throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must return a task to be used as a Flux effect"); - if (!effectMethod.IsStatic) - throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must be static to be used as a Flux effect"); - if (effectMethod.GetParameters().Length != 2) - throw new Exception($"The method '{effectMethod.Name}' in type '{effectMethod.DeclaringType!.FullName}' must declare exactly 2 parameters to be used as a Flux effect"); - var actionType = effectMethod.GetParameters()[0].ParameterType; - var effectType = typeof(Effect<>).MakeGenericType(actionType); - var effectLambda = this.BuildEffectLambda(actionType, effectMethod); - var effectFunction = effectLambda.Compile(); - var effect = (IEffect)ActivatorUtilities.CreateInstance(this.ServiceProvider, effectType, effectFunction); - store.AddEffect(effect); - } - } } + } - /// - /// Finds and configures s for the specified - /// - /// The to add s for - protected virtual void ConfigureStoreMiddlewares(IStore store) + /// + /// Finds and configures s for the specified + /// + /// The to add s for + protected virtual void ConfigureStoreMiddlewares(IStore store) + { + ArgumentNullException.ThrowIfNull(store); + foreach (var middlewareType in this.FluxOptions.Middlewares) store.AddMiddleware(middlewareType); + if (!this.FluxOptions.AutoRegisterMiddlewares) return; + foreach (var middlewareType in TypeCacheUtil.FindFilteredTypes("nflux-middlewares", + t => t.IsClass && !t.IsInterface && !t.IsAbstract && !t.IsGenericType && typeof(IMiddleware).IsAssignableFrom(t), this.FluxOptions.AssembliesToScan?.ToArray()!)) { - if (store == null) throw new ArgumentNullException(nameof(store)); - foreach (var middlewareType in this.FluxOptions.Middlewares) - { - store.AddMiddleware(middlewareType); - } - if (!this.FluxOptions.AutoRegisterMiddlewares) return; - foreach (var middlewareType in TypeCacheUtil.FindFilteredTypes("nflux-middlewares", - t => t.IsClass && !t.IsInterface && !t.IsAbstract && !t.IsGenericType && typeof(IMiddleware).IsAssignableFrom(t), this.FluxOptions.AssembliesToScan?.ToArray()!)) - { - store.AddMiddleware(middlewareType); - } + store.AddMiddleware(middlewareType); } + } - /// - /// Finds and maps scanned s per state type - /// - /// A new containing scanned s mapped by type - protected virtual IDictionary> FindAndMapReducersPerState() + /// + /// Finds and maps scanned s per state type + /// + /// A new containing scanned s mapped by type + protected virtual IDictionary> FindAndMapReducersPerState() + { + var reducersPerState = new Dictionary>(); + foreach (var reducerDeclaringType in TypeCacheUtil.FindFilteredTypes("nflux-reducers", + t => t.TryGetCustomAttribute(out _) || t.GetMethods().Any(m => m.TryGetCustomAttribute(out _)), this.FluxOptions.AssembliesToScan?.ToArray()!)) { - var reducersPerState = new Dictionary>(); - foreach (var reducerDeclaringType in TypeCacheUtil.FindFilteredTypes("nflux-reducers", - t => t.TryGetCustomAttribute(out _) || t.GetMethods().Any(m => m.TryGetCustomAttribute(out _)), this.FluxOptions.AssembliesToScan?.ToArray()!)) + foreach (var reducerMethod in reducerDeclaringType.GetMethods() + .Where(m => (reducerDeclaringType.TryGetCustomAttribute(out _) && m.IsStatic && m.GetParameters().Length == 2 && m.ReturnType == m.GetParameters()[0].ParameterType) || m.TryGetCustomAttribute(out _))) { - foreach (var reducerMethod in reducerDeclaringType.GetMethods() - .Where(m => (reducerDeclaringType.TryGetCustomAttribute(out _) && m.IsStatic && m.GetParameters().Length == 2 && m.ReturnType == m.GetParameters()[0].ParameterType) || m.TryGetCustomAttribute(out _))) - { - if (!reducerMethod.IsStatic) - throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must be static to be used as a Flux reducer"); - if (reducerMethod.GetParameters().Length != 2) - throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must declare exactly 2 parameters to be used as a Flux reducer"); - if (reducerMethod.ReturnType != reducerMethod.GetParameters()[0].ParameterType) - throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must return a type matching its first parameter's to be used as a Flux reducer"); - var stateType = reducerMethod.GetParameters()[0].ParameterType; - var actionType = reducerMethod.GetParameters()[1].ParameterType; - var reducerType = typeof(Reducer<,>).MakeGenericType(stateType, actionType); - var reducerLambda = this.BuildReducerLambda(stateType, actionType, reducerMethod); - var reducerFunction = reducerLambda.Compile(); - var reducer = (IReducer)ActivatorUtilities.CreateInstance(this.ServiceProvider, reducerType, reducerFunction); - if (reducersPerState.TryGetValue(stateType, out var reducers)) - reducers.Add(reducer); - else - reducersPerState.Add(stateType, new() { reducer }); - } + if (!reducerMethod.IsStatic) throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must be static to be used as a Flux reducer"); + if (reducerMethod.GetParameters().Length != 2) throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must declare exactly 2 parameters to be used as a Flux reducer"); + if (reducerMethod.ReturnType != reducerMethod.GetParameters()[0].ParameterType) throw new Exception($"The method '{reducerMethod.Name}' in type '{reducerMethod.DeclaringType!.FullName}' must return a type matching its first parameter's to be used as a Flux reducer"); + var stateType = reducerMethod.GetParameters()[0].ParameterType; + var actionType = reducerMethod.GetParameters()[1].ParameterType; + var reducerType = typeof(Reducer<,>).MakeGenericType(stateType, actionType); + var reducerLambda = this.BuildReducerLambda(stateType, actionType, reducerMethod); + var reducerFunction = reducerLambda.Compile(); + var reducer = (IReducer)ActivatorUtilities.CreateInstance(this.ServiceProvider, reducerType, reducerFunction); + if (reducersPerState.TryGetValue(stateType, out var reducers)) reducers.Add(reducer); + else reducersPerState.Add(stateType, [reducer]); } - return reducersPerState; - } - - /// - /// Builds a new reducer for the specified state type, action type and reducer method - /// - /// The type of state to create the reducer for - /// The type of action to create the reducer for - /// The reducer method - /// A new reducer - protected virtual LambdaExpression BuildReducerLambda(Type stateType, Type actionType, MethodInfo reducerMethod) - { - if(stateType == null) throw new ArgumentNullException(nameof(stateType)); - if (actionType == null) throw new ArgumentNullException(nameof(actionType)); - if (reducerMethod == null) throw new ArgumentNullException(nameof(reducerMethod)); - var stateParam = Expression.Parameter(stateType, "state"); - var actionParam = Expression.Parameter(actionType, "action"); - var methodCall = Expression.Call(null, reducerMethod, stateParam, actionParam); - var lambda = Expression.Lambda(methodCall, stateParam, actionParam); - return lambda; } + return reducersPerState; + } - /// - /// Builds a new effect for the specified action type and reducer method - /// - /// The type of action to create the effect for - /// The effect method - /// A new effect - protected virtual LambdaExpression BuildEffectLambda(Type actionType, MethodInfo effectMethod) - { - if (actionType == null) throw new ArgumentNullException(nameof(actionType)); - if (effectMethod == null) throw new ArgumentNullException(nameof(effectMethod)); - var actionParam = Expression.Parameter(actionType, "action"); - var contextParam = Expression.Parameter(typeof(IEffectContext), "context"); - var methodCall = Expression.Call(null, effectMethod, actionParam, contextParam); - var lambda = Expression.Lambda(methodCall, actionParam, contextParam); - return lambda; - } + /// + /// Builds a new reducer for the specified state type, action type and reducer method + /// + /// The type of state to create the reducer for + /// The type of action to create the reducer for + /// The reducer method + /// A new reducer + protected virtual LambdaExpression BuildReducerLambda(Type stateType, Type actionType, MethodInfo reducerMethod) + { + ArgumentNullException.ThrowIfNull(stateType); + ArgumentNullException.ThrowIfNull(actionType); + ArgumentNullException.ThrowIfNull(reducerMethod); + var stateParam = Expression.Parameter(stateType, "state"); + var actionParam = Expression.Parameter(actionType, "action"); + var methodCall = Expression.Call(null, reducerMethod, stateParam, actionParam); + var lambda = Expression.Lambda(methodCall, stateParam, actionParam); + return lambda; + } + /// + /// Builds a new effect for the specified action type and reducer method + /// + /// The type of action to create the effect for + /// The effect method + /// A new effect + protected virtual LambdaExpression BuildEffectLambda(Type actionType, MethodInfo effectMethod) + { + ArgumentNullException.ThrowIfNull(actionType); + ArgumentNullException.ThrowIfNull(effectMethod); + var actionParam = Expression.Parameter(actionType, "action"); + var contextParam = Expression.Parameter(typeof(IEffectContext), "context"); + var methodCall = Expression.Call(null, effectMethod, actionParam, contextParam); + var lambda = Expression.Lambda(methodCall, actionParam, contextParam); + return lambda; } } diff --git a/src/dashboard/CloudStreams.Dashboard.StateManagement/Usings.cs b/src/dashboard/CloudStreams.Dashboard.StateManagement/Usings.cs index 0cb82f43..4d5fee4f 100644 --- a/src/dashboard/CloudStreams.Dashboard.StateManagement/Usings.cs +++ b/src/dashboard/CloudStreams.Dashboard.StateManagement/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/CloudStreams.Dashboard.csproj b/src/dashboard/CloudStreams.Dashboard/CloudStreams.Dashboard.csproj index cc5dde2e..6ee946c5 100644 --- a/src/dashboard/CloudStreams.Dashboard/CloudStreams.Dashboard.csproj +++ b/src/dashboard/CloudStreams.Dashboard/CloudStreams.Dashboard.csproj @@ -1,30 +1,31 @@ - net7.0 + net8.0 enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True Apache-2.0 Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git + true - - + + - - - - + + + + diff --git a/src/dashboard/CloudStreams.Dashboard/Components/CloudEventDetails/CloudEventDetails.razor b/src/dashboard/CloudStreams.Dashboard/Components/CloudEventDetails/CloudEventDetails.razor index 0cd4e34c..208aa5f2 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/CloudEventDetails/CloudEventDetails.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/CloudEventDetails/CloudEventDetails.razor @@ -1,6 +1,5 @@ @namespace CloudStreams.Dashboard.Components @using System.Text.Json; -@using Hylo;
@@ -48,7 +47,7 @@ @foreach (var attr in cloudEvent.ExtensionAttributes) { - @attr.Key.ToPascalCase() + @attr.Key @attr.Value } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Layout/ApplicationLayout.cs b/src/dashboard/CloudStreams.Dashboard/Components/Layout/ApplicationLayout.cs index 9eefb50c..dd9362cc 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Layout/ApplicationLayout.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Layout/ApplicationLayout.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Layout/IApplicationLayout.cs b/src/dashboard/CloudStreams.Dashboard/Components/Layout/IApplicationLayout.cs index 8b3ecc0e..4ee65f81 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Layout/IApplicationLayout.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Layout/IApplicationLayout.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorHelper.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorHelper.cs index 6bfb5367..d85d8f1c 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorHelper.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorHelper.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using BlazorMonaco; -using System.ComponentModel; - namespace CloudStreams.Dashboard.Components; /// @@ -21,7 +18,7 @@ namespace CloudStreams.Dashboard.Components; /// /// The new preferred language. /// A task representing the asynchronous operation of handling the event. -public delegate Task PreferedLanguageChangedEventHandler(string newLanguage); +public delegate Task PreferredLanguageChangedEventHandler(string newLanguage); /// /// Represents a service used to facilitate the Monaco editor configuration @@ -29,21 +26,21 @@ namespace CloudStreams.Dashboard.Components; public interface IMonacoEditorHelper { /// - /// The prefered editor language + /// The preferred editor language /// - string PreferedLanguage { get; } + string PreferredLanguage { get; } /// /// Emits when the editor language changes /// - event PreferedLanguageChangedEventHandler? PreferedLanguageChanged; + event PreferredLanguageChangedEventHandler? PreferredLanguageChanged; /// /// A function used to facilitate the construction of /// /// The text of the editor /// Defines if the editor should be in read only - /// The default prefered language + /// The default preferred language /// A function used to build Func GetStandaloneEditorConstructionOptions(string value = "", bool readOnly = false, string language = "json"); @@ -55,10 +52,10 @@ public interface IMonacoEditorHelper Func GetDiffEditorConstructionOptions(bool readOnly = true); /// - /// Changes the prefered editor language + /// Changes the preferred editor language /// /// The new language to use /// A task representing the asynchronous operation - Task ChangePreferedLanguageAsync(string language); + Task ChangePreferredLanguageAsync(string language); } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorMarker.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorMarker.cs index f48760d5..e7176dc9 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorMarker.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/IMonacoEditorMarker.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorHelper.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorHelper.cs index cc627847..c214d1cc 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorHelper.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorHelper.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ public class MonacoEditorHelper : IMonacoEditorHelper { /// - public string PreferedLanguage { get; protected set; } = "yaml"; + public string PreferredLanguage { get; protected set; } = "yaml"; /// - public event PreferedLanguageChangedEventHandler? PreferedLanguageChanged; + public event PreferredLanguageChangedEventHandler? PreferredLanguageChanged; /// public Func GetStandaloneEditorConstructionOptions(string value = "", bool readOnly = false, string language = "yaml") { @@ -49,21 +49,21 @@ public Func GetDiffEditorCo } /// - public async Task ChangePreferedLanguageAsync(string language) + public async Task ChangePreferredLanguageAsync(string language) { - if (!string.IsNullOrEmpty(language) && language != this.PreferedLanguage) + if (!string.IsNullOrEmpty(language) && language != this.PreferredLanguage) { - this.PreferedLanguage = language; - await this.OnPreferedLanguageChangeAsync(language); + this.PreferredLanguage = language; + await this.OnPreferredLanguageChangeAsync(language); } } /// - protected async Task OnPreferedLanguageChangeAsync(string language) + protected async Task OnPreferredLanguageChangeAsync(string language) { - if (this.PreferedLanguageChanged != null) + if (this.PreferredLanguageChanged != null) { - await this.PreferedLanguageChanged.Invoke(language); + await this.PreferredLanguageChanged.Invoke(language); } await Task.CompletedTask; } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarker.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarker.cs index 9758e20d..b27e0a9a 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarker.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarker.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarkerSeverity.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarkerSeverity.cs index 64c593d5..ad55eebd 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarkerSeverity.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/MonacoEditorMarkerSeverity.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguage.cs b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguage.cs similarity index 83% rename from src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguage.cs rename to src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguage.cs index 8c7ed89f..808ce8e6 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguage.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguage.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ namespace CloudStreams.Dashboard.Components; /// -/// Defines the possible prefered language of a Monaco editor +/// Defines the possible preferred language of a Monaco editor /// -public static class PreferedLanguage +public static class PreferredLanguage { /// /// JSON diff --git a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguageSelector.razor b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguageSelector.razor similarity index 67% rename from src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguageSelector.razor rename to src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguageSelector.razor index 2847c512..dd5a71dc 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferedLanguageSelector.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/MonacoEditor/PreferredLanguageSelector.razor @@ -19,29 +19,29 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - this.isJsonSelected = this.MonacoEditorHelper.PreferedLanguage == PreferedLanguage.JSON; + this.isJsonSelected = this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON; } protected virtual async Task ToggleLanguage() { - if (this.MonacoEditorHelper.PreferedLanguage == PreferedLanguage.JSON) { - await this.MonacoEditorHelper.ChangePreferedLanguageAsync(PreferedLanguage.YAML); + if (this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON) { + await this.MonacoEditorHelper.ChangePreferredLanguageAsync(PreferredLanguage.YAML); } else { - await this.MonacoEditorHelper.ChangePreferedLanguageAsync(PreferedLanguage.JSON); + await this.MonacoEditorHelper.ChangePreferredLanguageAsync(PreferredLanguage.JSON); } } protected override void OnInitialized() { base.OnInitialized(); - this.MonacoEditorHelper.PreferedLanguageChanged += this.HandlePreferedLanguageChangeAsync; + this.MonacoEditorHelper.PreferredLanguageChanged += this.HandlePreferedLanguageChangeAsync; } protected async Task HandlePreferedLanguageChangeAsync(string language) { - this.isJsonSelected = this.MonacoEditorHelper.PreferedLanguage == PreferedLanguage.JSON; + this.isJsonSelected = this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON; await this.PreferedLanguageChange.InvokeAsync(language); this.StateHasChanged(); } @@ -49,7 +49,7 @@ public void Dispose() { if (this.MonacoEditorHelper != null) { - this.MonacoEditorHelper.PreferedLanguageChanged -= this.HandlePreferedLanguageChangeAsync; + this.MonacoEditorHelper.PreferredLanguageChanged -= this.HandlePreferedLanguageChangeAsync; } GC.SuppressFinalize(this); } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/ReadOptionsForm.razor b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/ReadOptionsForm.razor index 95e7b3f2..49145c9e 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/ReadOptionsForm.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/ReadOptionsForm.razor @@ -1,6 +1,5 @@ @namespace CloudStreams.Dashboard.Components @using CloudStreams.Dashboard.Components.ReadOptionsFormStateManagement -@using Hylo; @inherits StatefulComponent
@@ -43,7 +42,7 @@ @if (!Compact) {
- +
} @@ -101,7 +100,7 @@ ///
private List? partitions = null; /// - /// A random id used to bind datalists + /// A random id used to bind data lists /// private string id = Guid.NewGuid().ToString(); @@ -166,6 +165,6 @@ this.Store.SetPartitionId(null); this.Store.SetDirection(StreamReadDirection.Backwards); this.Store.SetOffset(null); - this.Store.SetLenght(null); + this.Store.SetLength(null); } } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/State.cs b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/State.cs index f416999d..d5092520 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/State.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/State.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -50,5 +50,5 @@ public record ReadOptionsFormState /// /// Gets the of suggested s /// - public List? Partitions { get; set; } = new(); + public List? Partitions { get; set; } = []; } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/Store.cs b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/Store.cs index cd0fbcd7..bb75906c 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/Store.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ReadOptionsForm/Store.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,32 +12,19 @@ // limitations under the License. using CloudStreams.Core.Api.Client.Services; -using CloudStreams.Dashboard.StateManagement; -using Hylo; -using System.Reactive.Linq; namespace CloudStreams.Dashboard.Components.ReadOptionsFormStateManagement; /// /// Represents a 's form /// -public class ReadOptionsFormStore - : ComponentStore +/// +/// Initializes a new +/// +/// The service used to interact with the Cloud Streams Gateway API +public class ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) + : ComponentStore(new()) { - /// - /// The service used to interact with the Cloud Streams Gateway API - /// - private ICloudStreamsCoreApiClient cloudStreamsApi; - - /// - /// Initializes a new - /// - /// The service used to interact with the Cloud Streams Gateway API - public ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) - : base(new()) - { - this.cloudStreamsApi = cloudStreamsApi; - } /// /// Gets an used to observe changes @@ -62,7 +49,7 @@ public ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) /// /// Gets an used to observe changes /// - protected IObservable _length => this.Select(state => state.Length).DistinctUntilChanged(); + protected IObservable FactualLength => this.Select(state => state.Length).DistinctUntilChanged(); /// /// Gets an used to observe changes @@ -78,18 +65,9 @@ public ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) this.StreamLength, (direction, offset, length) => { - if (length == null) - { - return null; - } - if (offset == null || offset == 0 || offset == -1) - { - return length; - } - if (direction == StreamReadDirection.Backwards) - { - return (ulong?)offset + 1; - } + if (length == null) return null; + if (offset == null || offset == 0 || offset == -1) return length; + if (direction == StreamReadDirection.Backwards) return (ulong?)offset + 1; return length - (ulong?)offset; } ); @@ -98,7 +76,7 @@ public ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) /// Gets an used to observe the computed length /// public IObservable Length => Observable.CombineLatest( - this._length, + this.FactualLength, this.MaxLength, (length, maxLength) => { @@ -135,20 +113,11 @@ public ReadOptionsFormStore(ICloudStreamsCoreApiClient cloudStreamsApi) { Type = partitionType.Value }; - if (!string.IsNullOrWhiteSpace(partitionId)) - { - partition.Id = partitionId; - } + if (!string.IsNullOrWhiteSpace(partitionId)) partition.Id = partitionId; options.Partition = partition; } - if (offset.HasValue) - { - options.Offset = offset.Value; - } - if (length.HasValue) - { - options.Length = length.Value; - } + if (offset.HasValue) options.Offset = offset.Value; + if (length.HasValue) options.Length = length.Value; return options; } ); @@ -226,7 +195,7 @@ public void SetOffset(long? offset) /// Sets the state's /// /// The new value - public void SetLenght(ulong? length) + public void SetLength(ulong? length) { this.Reduce(state => state with { @@ -269,7 +238,7 @@ public void SetReadOptions(StreamReadOptions readOptions) } if (changed) { - state.Partitions = new(); + state.Partitions = []; this.Reduce(_ => state); } } @@ -289,7 +258,7 @@ protected async Task UpdatePartitionsAsync(CloudEventPartitionType? partitionTyp }); return; } - var partitions = await (await this.cloudStreamsApi.CloudEvents.Partitions.ListPartitionsByTypeAsync(partitionType.Value, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + var partitions = await (await cloudStreamsApi.CloudEvents.Partitions.ListPartitionsByTypeAsync(partitionType.Value, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); this.Reduce(state => state with { Partitions = partitions! @@ -310,7 +279,7 @@ protected async Task UpdateMetadataAsync((CloudEventPartitionType?, string?) par (CloudEventPartitionType? type, string? id) = partition; if (!type.HasValue || string.IsNullOrWhiteSpace(id)) { - StreamMetadata metadata = await this.cloudStreamsApi.CloudEvents.Stream.GetStreamMetadataAsync(this.CancellationTokenSource.Token).ConfigureAwait(false); + StreamMetadata metadata = await cloudStreamsApi.CloudEvents.Stream.GetStreamMetadataAsync(this.CancellationTokenSource.Token).ConfigureAwait(false); this.Reduce(state => state with { StreamLength = metadata.Length @@ -318,7 +287,7 @@ protected async Task UpdateMetadataAsync((CloudEventPartitionType?, string?) par } else { - PartitionMetadata? metadata = await this.cloudStreamsApi.CloudEvents.Partitions.GetPartitionMetadataAsync(type!.Value, id!, this.CancellationTokenSource.Token).ConfigureAwait(false); + PartitionMetadata? metadata = await cloudStreamsApi.CloudEvents.Partitions.GetPartitionMetadataAsync(type!.Value, id!, this.CancellationTokenSource.Token).ConfigureAwait(false); if (metadata != null) { this.Reduce(state => state with diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceDetails/ResourceDetails.razor b/src/dashboard/CloudStreams.Dashboard/Components/ResourceDetails/ResourceDetails.razor index 45096860..660bf220 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceDetails/ResourceDetails.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceDetails/ResourceDetails.razor @@ -1,6 +1,5 @@ @namespace CloudStreams.Dashboard.Components @using System.Text.Json -@using Hylo; @typeparam TResource where TResource : Resource, new()
diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/ResourceEditor.razor b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/ResourceEditor.razor index c8180507..482c78dc 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/ResourceEditor.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/ResourceEditor.razor @@ -1,15 +1,15 @@ @namespace CloudStreams.Dashboard.Components @using CloudStreams.Dashboard.Components.ResourceEditorStateManagement -@using Hylo; @typeparam TResource where TResource : Resource, new() @inherits StatefulComponent, ResourceEditorState> @inject IMonacoEditorHelper MonacoEditorHelper +@inject IJSRuntime JSRuntime
- +
@@ -86,7 +86,7 @@ private bool saving = false; /// - /// The that occured when trying to save the resource, if any + /// The that occurred when trying to save the resource, if any /// private ProblemDetails? problemDetails = null; @@ -131,16 +131,13 @@ } /// - /// Sets the editor as readonly when saving + /// Sets the editor as read-only when saving /// - /// Whevener the resource is in a saving state + /// Whenever the resource is in a saving state private void OnSavingChanged(bool saving) { this.saving = saving; - if (this.textBasedEditor != null) - { - this.textBasedEditor.UpdateOptions(new EditorUpdateOptions() { ReadOnly = saving }); - } + if (this.textBasedEditor != null) this.textBasedEditor.UpdateOptions(new EditorUpdateOptions() { ReadOnly = saving }); this.StateHasChanged(); } @@ -150,11 +147,11 @@ /// A private async Task OnTextBasedEditorInit() { - string resourceUri = $"inmemory://{typeof(TResource).Name.ToLower()}"; - this.textEditorModel = await Global.GetModel(resourceUri); + var resourceUri = $"inmemory://{typeof(TResource).Name.ToLower()}"; + this.textEditorModel = await Global.GetModel(this.JSRuntime, resourceUri); if (this.textEditorModel == null) { - this.textEditorModel = await Global.CreateModel(this.textEditorValue, this.MonacoEditorHelper.PreferedLanguage, resourceUri); + this.textEditorModel = await Global.CreateModel(this.JSRuntime, this.textEditorValue, this.MonacoEditorHelper.PreferredLanguage, resourceUri); await this.textBasedEditor!.SetModel(this.textEditorModel); } else @@ -174,7 +171,7 @@ { if (!this.updating && this.textBasedEditor != null && this.textEditorInput != null) { - string text = await this.textBasedEditor.GetValue(); + var text = await this.textBasedEditor.GetValue(); this.textEditorInput.OnNext(text); } } @@ -187,11 +184,8 @@ { if (this.textBasedEditor != null) { - string editorText = await this.textBasedEditor.GetValue(); - if (this.textEditorValue != editorText) - { - await this.textBasedEditor.SetValue(this.textEditorValue); - } + var editorText = await this.textBasedEditor.GetValue(); + if (this.textEditorValue != editorText) await this.textBasedEditor.SetValue(this.textEditorValue); } } @@ -204,7 +198,7 @@ if (this.textBasedEditor != null && this.textEditorModel != null) { //TextModel model = await this.textBasedEditor!.GetModel(); - await Global.SetModelLanguage(this.textEditorModel, this.MonacoEditorHelper.PreferedLanguage); + await Global.SetModelLanguage(this.JSRuntime, this.textEditorModel, this.MonacoEditorHelper.PreferredLanguage); } } @@ -215,9 +209,8 @@ /// A private async Task ToggleTextBasedEditorLanguageAsync(string language) { - - TextModel model = await this.textBasedEditor!.GetModel(); - string editorLanguage = await model.GetLanguageId(); + var model = await this.textBasedEditor!.GetModel(); + var editorLanguage = await model.GetLanguageId(); if (editorLanguage != language) { await this.Store.ChangeTextEditorLanguageAsync(language); @@ -237,10 +230,7 @@ { if (disposing) { - if (this.textEditorInput != null) - { - this.textEditorInput.Dispose(); - } + if (this.textEditorInput != null) this.textEditorInput.Dispose(); if (this.textEditorModel != null) { this.textEditorModel.DisposeModel(); diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/State.cs b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/State.cs index 052db726..070b7282 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/State.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/State.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; - namespace CloudStreams.Dashboard.Components.ResourceEditorStateManagement; /// @@ -47,27 +45,27 @@ public record ResourceEditorState public bool Saving { get; set; } = false; /// - /// Gets/sets the type that occured when trying to save the resource, if any + /// Gets/sets the type that occurred when trying to save the resource, if any /// public Uri? ProblemType { get; set; } = null; /// - /// Gets/sets the title that occured when trying to save the resource, if any + /// Gets/sets the title that occurred when trying to save the resource, if any /// public string ProblemTitle { get; set; } = string.Empty; /// - /// Gets/sets the details that occured when trying to save the resource, if any + /// Gets/sets the details that occurred when trying to save the resource, if any /// public string ProblemDetail { get; set; } = string.Empty; /// - /// Gets/sets the status that occured when trying to save the resource, if any + /// Gets/sets the status that occurred when trying to save the resource, if any /// public int ProblemStatus { get; set; } = 0; /// - /// Gets/sets the list of errors that occured when trying to save the resource, if any + /// Gets/sets the list of errors that occurred when trying to save the resource, if any /// public IDictionary ProblemErrors { get; set; } = new Dictionary(); } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/Store.cs b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/Store.cs index 5a7346b7..145433a2 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/Store.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceEditor/Store.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,43 +11,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Dashboard.StateManagement; -using System.Reactive.Linq; using CloudStreams.Core.Api.Client.Services; using JsonCons.Utilities; +using Neuroglia; +using Neuroglia.Data; +using Neuroglia.Serialization; +using Neuroglia.Serialization.Yaml; using System.Text.Json; -using Hylo; namespace CloudStreams.Dashboard.Components.ResourceEditorStateManagement; /// /// Represents a 's form /// -public class ResourceEditorStore - : ComponentStore> +/// +/// Initializes a new +/// +/// The service used to interact with a Cloud Streams gateway's API +/// The service used to facilitate the Monaco editor interactions +/// The The service used to serialize/deserialize objects to/from JSON +/// The service used to serialize/deserialize objects to/from YAML +public class ResourceEditorStore(ICloudStreamsCoreApiClient resourceManagementApi, IMonacoEditorHelper monacoEditorHelper, IJsonSerializer jsonSerializer, IYamlSerializer yamlSerializer) + : ComponentStore>(new()) where TResource : Resource, new() { - /// - /// The service used to interact with a Cloud Streams gateway's API - /// - private ICloudStreamsCoreApiClient resourceManagementApi; - - /// - /// The service used to facilitate the Monaco editor interactions - /// - private IMonacoEditorHelper monacoEditorHelper; - - /// - /// Initializes a new - /// - /// The service used to interact with a Cloud Streams gateway's API - /// The service used to facilitate the Monaco editor interactions - public ResourceEditorStore(ICloudStreamsCoreApiClient resourceManagementApi, IMonacoEditorHelper monacoEditorHelper) - : base(new()) - { - this.resourceManagementApi = resourceManagementApi; - this.monacoEditorHelper = monacoEditorHelper; - } /// /// Gets an used to observe changes @@ -100,7 +87,7 @@ public ResourceEditorStore(ICloudStreamsCoreApiClient resourceManagementApi, IMo public IObservable> ProblemErrors => this.Select(state => state.ProblemErrors).DistinctUntilChanged(); /// - /// Gets an used to observe comptured + /// Gets an used to observe computed /// public IObservable ProblemDetails => Observable.CombineLatest( this.ProblemType, @@ -124,13 +111,13 @@ public override async Task InitializeAsync() await base.InitializeAsync().ConfigureAwait(false); this.Resource.Subscribe(resource => { - if (this.monacoEditorHelper.PreferedLanguage == PreferedLanguage.YAML) + if (monacoEditorHelper.PreferredLanguage == PreferredLanguage.YAML) { - this.SetEditorValue(Serializer.Yaml.Serialize(resource)); + this.SetEditorValue(YamlSerializer.Default.Serialize(resource)); } else { - this.SetEditorValue(Serializer.Json.Serialize(resource, true)); + this.SetEditorValue(YamlSerializer.Default.Serialize(resource)); } }, token: this.CancellationTokenSource.Token); } @@ -216,7 +203,7 @@ public void SetProblemDetails(ProblemDetails? problem) ProblemTitle = problem?.Title ?? string.Empty, ProblemStatus = problem?.Status ?? 0, ProblemDetail = problem?.Detail ?? string.Empty, - ProblemErrors = problem?.Errors?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? new Dictionary() + ProblemErrors = problem?.Errors?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? [] }); } @@ -227,18 +214,18 @@ public void SetProblemDetails(ProblemDetails? problem) /// public async Task ChangeTextEditorLanguageAsync(string language) { - string textEditorValue = this.Get(state => state.TextEditorValue); + var textEditorValue = this.Get(state => state.TextEditorValue); try { - string text = language == PreferedLanguage.YAML ? - Serializer.Yaml.ConvertFromJson(textEditorValue) : - Serializer.Yaml.ConvertToJson(textEditorValue, true); + var text = language == PreferredLanguage.YAML ? + yamlSerializer.ConvertFromJson(textEditorValue) : + yamlSerializer.ConvertToJson(textEditorValue); this.SetEditorValue(text); } catch (Exception ex) { Console.WriteLine(ex.ToString()); - await monacoEditorHelper.ChangePreferedLanguageAsync(language == PreferedLanguage.YAML ? PreferedLanguage.JSON : PreferedLanguage.YAML); + await monacoEditorHelper.ChangePreferredLanguageAsync(language == PreferredLanguage.YAML ? PreferredLanguage.JSON : PreferredLanguage.YAML); } } @@ -267,21 +254,18 @@ public async Task CreateResourceAsync() { this.SetProblemDetails(null); this.SetSaving(true); - string textEditorValue = this.Get(state => state.TextEditorValue); - if (monacoEditorHelper.PreferedLanguage == PreferedLanguage.YAML) - { - textEditorValue = Serializer.Yaml.ConvertToJson(textEditorValue); - } + var textEditorValue = this.Get(state => state.TextEditorValue); + if (monacoEditorHelper.PreferredLanguage == PreferredLanguage.YAML) textEditorValue = yamlSerializer.ConvertToJson(textEditorValue); TResource? resource; try { - resource = Serializer.Json.Deserialize(textEditorValue); - resource = await this.resourceManagementApi.Manage().CreateAsync(resource!, this.CancellationTokenSource.Token); + resource = jsonSerializer.Deserialize(textEditorValue); + resource = await resourceManagementApi.Manage().CreateAsync(resource!, this.CancellationTokenSource.Token); this.SetResource(resource); } - catch (CloudStreamsException ex) + catch (ProblemDetailsException ex) { - this.SetProblemDetails(ex.ProblemDetails); + this.SetProblemDetails(ex.Problem); } catch (Exception ex) { @@ -298,29 +282,26 @@ public async Task UpdateResourceAsync() { this.SetProblemDetails(null); this.SetSaving(true); - TResource? resource = this.Get(state => state.Resource); + var resource = this.Get(state => state.Resource); if (resource == null) { return; } - string textEditorValue = this.Get(state => state.TextEditorValue); - if (monacoEditorHelper.PreferedLanguage == PreferedLanguage.YAML) - { - textEditorValue = Serializer.Yaml.ConvertToJson(textEditorValue); - } - JsonDocument jsonPatch = JsonPatch.FromDiff(Serializer.Json.SerializeToElement(resource)!.Value, Serializer.Json.SerializeToElement(Serializer.Json.Deserialize(textEditorValue))!.Value); - Json.Patch.JsonPatch? patch = Serializer.Json.Deserialize(jsonPatch.RootElement); + var textEditorValue = this.Get(state => state.TextEditorValue); + if (monacoEditorHelper.PreferredLanguage == PreferredLanguage.YAML) textEditorValue = yamlSerializer.ConvertToJson(textEditorValue); + var jsonPatch = JsonPatch.FromDiff(jsonSerializer.SerializeToElement(resource)!.Value, jsonSerializer.SerializeToElement(jsonSerializer.Deserialize(textEditorValue))!.Value); + var patch = jsonSerializer.Deserialize(jsonPatch.RootElement); if (patch != null) { var resourcePatch = new Patch(PatchType.JsonPatch, jsonPatch); try { - resource = await this.resourceManagementApi.Manage().PatchAsync(resourcePatch, resource.GetName(), resource.GetNamespace(), this.CancellationTokenSource.Token); + resource = await resourceManagementApi.Manage().PatchAsync(resourcePatch, resource.GetName(), resource.GetNamespace(), this.CancellationTokenSource.Token); this.SetResource(resource); } - catch(CloudStreamsException ex) + catch(ProblemDetailsException ex) { - this.SetProblemDetails(ex.ProblemDetails); + this.SetProblemDetails(ex.Problem); } catch (Exception ex) { diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponent.cs b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponent.cs index 49ad98f4..24f03209 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponent.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponent.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ using BlazorBootstrap; using CloudStreams.Dashboard.Components.ResourceManagement; using CloudStreams.Dashboard.Pages.CloudEvents.List; -using Hylo; using Microsoft.AspNetCore.Components; +using Neuroglia.Serialization; namespace CloudStreams.Dashboard.Components; @@ -33,6 +33,12 @@ public abstract class ResourceManagementComponent [Inject] protected MonacoInterop? MonacoInterop { get; set; } + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + [Inject] + protected IJsonSerializer Serializer { get; set; } = null!; + /// /// The list of displayed s /// @@ -40,11 +46,11 @@ public abstract class ResourceManagementComponent /// /// The used to show the 's details /// - protected Offcanvas? detailsOffcanvas; + protected Offcanvas? detailsOffCanvas; /// /// The used to edit the /// - protected Offcanvas? editorOffcanvas; + protected Offcanvas? editorOffCanvas; /// /// The used to confirm the 's deletion /// @@ -70,7 +76,7 @@ protected override async Task OnInitializedAsync() this.definition = definition; if (this.definition != null && this.MonacoInterop != null) { - await this.MonacoInterop.AddValidationSchemaAsync(Serializer.Json.Serialize(this.definition.Spec.Versions.First().Schema.OpenAPIV3Schema), $"https://cloud-streams.io/schemas/{typeof(TResource).Name.ToLower()}.json", $"{typeof(TResource).Name.ToLower()}").ConfigureAwait(false); + await this.MonacoInterop.AddValidationSchemaAsync(this.Serializer.SerializeToText(this.definition.Spec.Versions.First().Schema.OpenAPIV3Schema), $"https://cloud-streams.io/schemas/{typeof(TResource).Name.ToLower()}.json", $"{typeof(TResource).Name.ToLower()}").ConfigureAwait(false); } } }, cancellationToken: this.CancellationTokenSource.Token); @@ -128,12 +134,12 @@ protected async Task OnDeleteResourceAsync(TResource resource) /// The to show the details for protected Task OnShowResourceDetailsAsync(TResource resource) { - if (this.detailsOffcanvas == null) return Task.CompletedTask; + if (this.detailsOffCanvas == null) return Task.CompletedTask; var parameters = new Dictionary { { "Resource", resource } }; - return this.detailsOffcanvas.ShowAsync>(title: typeof(TResource).Name + " details", parameters: parameters); + return this.detailsOffCanvas.ShowAsync>(title: typeof(TResource).Name + " details", parameters: parameters); } /// @@ -142,13 +148,13 @@ protected Task OnShowResourceDetailsAsync(TResource resource) /// The to edit protected Task OnShowResourceEditorAsync(TResource? resource = null) { - if (this.editorOffcanvas == null) return Task.CompletedTask; + if (this.editorOffCanvas == null) return Task.CompletedTask; var parameters = new Dictionary { { "Resource", resource! } }; string actionType = resource == null ? "creation" : "edition"; - return this.editorOffcanvas.ShowAsync>(title: typeof(TResource).Name + " " + actionType, parameters: parameters); + return this.editorOffCanvas.ShowAsync>(title: typeof(TResource).Name + " " + actionType, parameters: parameters); } } \ No newline at end of file diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentState.cs b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentState.cs index 82a594d1..1ae6e354 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentState.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentState.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Hylo; - namespace CloudStreams.Dashboard.Components.ResourceManagement; /// diff --git a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentStore.cs b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentStore.cs index bd9c3947..5d1626d3 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentStore.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/ResourceManagement/ResourceManagementComponentStore.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -13,9 +13,6 @@ using CloudStreams.Core.Api.Client.Services; using CloudStreams.Dashboard.Pages.CloudEvents.List; -using CloudStreams.Dashboard.StateManagement; -using Hylo; -using System.Reactive.Linq; namespace CloudStreams.Dashboard.Components.ResourceManagement; @@ -23,26 +20,19 @@ namespace CloudStreams.Dashboard.Components.ResourceManagement; /// Represents a used to manage Cloud Streams s of the specified type /// /// The type of s to manage -public class ResourceManagementComponentStore - : ComponentStore> +/// +/// Initializes a new +/// +/// The service used to interact with the Cloud Streams Resource management API +/// The websocket service client +public class ResourceManagementComponentStore(ICloudStreamsCoreApiClient resourceManagementApi, ResourceWatchEventHubClient resourceEventHub) + : ComponentStore>(new()) where TResource : Resource, new() { - readonly ICloudStreamsCoreApiClient resourceManagementApi; + ResourceDefinition? definition; List? resources; - /// - /// Initializes a new - /// - /// The service used to interact with the Cloud Streams Resource management API - /// The websocket service client - public ResourceManagementComponentStore(ICloudStreamsCoreApiClient resourceManagementApi, ResourceWatchEventHubClient resourceEventHub) - : base(new()) - { - this.resourceManagementApi = resourceManagementApi; - this.ResourceEventHub = resourceEventHub; - } - /// /// Gets an used to observe s of the specified type /// @@ -60,12 +50,12 @@ public ResourceManagementComponentStore(ICloudStreamsCoreApiClient resourceManag /// /// Gets the websocket service client /// - protected ResourceWatchEventHubClient ResourceEventHub { get; } + protected ResourceWatchEventHubClient ResourceEventHub { get; } = resourceEventHub; /// /// Gets the service used to monitor resources of the specified type /// - protected ResourceWatch ResourceWatch { get; private set; } = null!; + protected Core.Api.Client.Services.ResourceWatch ResourceWatch { get; private set; } = null!; /// /// Gets an that represents the store's subscription @@ -87,7 +77,7 @@ public override async Task InitializeAsync() /// A new awaitable public virtual async Task GetResourceDefinitionAsync() { - this.definition = await this.resourceManagementApi.Manage().GetDefinitionAsync().ConfigureAwait(false); + this.definition = await resourceManagementApi.Manage().GetDefinitionAsync().ConfigureAwait(false); this.Reduce(s => s with { Definition = this.definition @@ -104,7 +94,7 @@ public virtual async Task ListResourcesAsync() { Loading = true }); - this.resources = await (await this.resourceManagementApi.Manage().ListAsync().ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + this.resources = await (await resourceManagementApi.Manage().ListAsync().ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); this.Reduce(s => s with { Resources = this.resources, @@ -119,7 +109,7 @@ public virtual async Task ListResourcesAsync() /// A new awaitable public virtual async Task DeleteResourceAsync(TResource resource) { - await this.resourceManagementApi.Manage().DeleteAsync(resource.GetName(), resource.GetNamespace()).ConfigureAwait(false); + await resourceManagementApi.Manage().DeleteAsync(resource.GetName(), resource.GetNamespace()).ConfigureAwait(false); var match = this.resources?.ToList().FirstOrDefault(r => r.GetName() == resource.GetName() && r.GetNamespace() == resource.GetNamespace()); var resourceCollectionChanged = false; if (match != null) diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/CellRenderingContext.cs b/src/dashboard/CloudStreams.Dashboard/Components/Table/CellRenderingContext.cs index 633e4cac..c4be9bdd 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/CellRenderingContext.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/CellRenderingContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/Column.razor b/src/dashboard/CloudStreams.Dashboard/Components/Table/Column.razor index d8d35291..71fd2942 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/Column.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/Column.razor @@ -2,7 +2,7 @@ @using System.Linq.Expressions; @using System.Reflection; @using System.ComponentModel.DataAnnotations; -@using Hylo; + @typeparam TData @code { @@ -27,7 +27,7 @@ [Parameter] public bool IsFilterable { get; set; } = true; - [Parameter] public bool IsSorteable { get; set; } = true; + [Parameter] public bool IsSortable { get; set; } = true; [Parameter] public bool IsVisible { get; set; } = true; @@ -99,7 +99,7 @@ public void ToggleSortMode() { - if (!this.IsSorteable) + if (!this.IsSortable) return; int sortModeValue = (int)this.SortMode; sortModeValue++; diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/ColumnHeaderRenderingContext.cs b/src/dashboard/CloudStreams.Dashboard/Components/Table/ColumnHeaderRenderingContext.cs index aac2819d..9a17359c 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/ColumnHeaderRenderingContext.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/ColumnHeaderRenderingContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/RowRenderingContext.cs b/src/dashboard/CloudStreams.Dashboard/Components/Table/RowRenderingContext.cs index fbed55b5..f609cd48 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/RowRenderingContext.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/RowRenderingContext.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/SortMode.cs b/src/dashboard/CloudStreams.Dashboard/Components/Table/SortMode.cs index 66db32f6..0b251996 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/SortMode.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/SortMode.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Table/Table.razor b/src/dashboard/CloudStreams.Dashboard/Components/Table/Table.razor index 1a13d620..b6a12331 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Table/Table.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/Table/Table.razor @@ -1,5 +1,4 @@ @namespace CloudStreams.Dashboard.Components -@using Hylo; @typeparam TData @@ -123,7 +122,7 @@ [Parameter] public EventCallback> OnOrderBy { get; set; } /// - /// Adds the specifed + /// Adds the specified /// /// The to add public void AddColumn(Column column) @@ -133,7 +132,7 @@ } /// - /// Revmoes the specifed + /// Removes the specified /// /// The to remove public bool RemoveColumn(Column column) @@ -167,7 +166,7 @@ internal async Task OnClickColumn(Column column) { - if (!column.IsSorteable || column.PropertyPath == null) return; + if (!column.IsSortable || column.PropertyPath == null) return; column.ToggleSortMode(); if (this.OnOrderBy.HasDelegate) await this.OnOrderBy.InvokeAsync(column); } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/State.cs b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/State.cs index 8fbb701d..5a6e7936 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/State.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/State.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ public record TimelineState /// /// Gets/sets the list of used to populate s /// - public IEnumerable StreamsReadOptions { get; set; } = new List() { new StreamReadOptions(StreamReadDirection.Backwards) }; + public IEnumerable StreamsReadOptions { get; set; } = [new StreamReadOptions(StreamReadDirection.Backwards)]; /// /// Gets/sets the list of to build the with /// diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Store.cs b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Store.cs index 3d072344..b80e1263 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Store.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Store.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,32 +12,19 @@ // limitations under the License. using CloudStreams.Core.Api.Client.Services; -using CloudStreams.Dashboard.StateManagement; -using Hylo; -using System.Reactive.Linq; namespace CloudStreams.Dashboard.Components.TimelineStateManagement; /// /// Represents a 's form /// -public class TimelineStore - : ComponentStore +/// +/// Initializes a new +/// +/// The service used to interact with a Cloud Streams gateway's API +public class TimelineStore(ICloudStreamsCoreApiClient cloudStreamsApi) + : ComponentStore(new()) { - /// - /// The service used to interact with a Cloud Streams gateway's API - /// - ICloudStreamsCoreApiClient cloudStreamsApi; - - /// - /// Initializes a new - /// - /// The service used to interact with a Cloud Streams gateway's API - public TimelineStore(ICloudStreamsCoreApiClient cloudStreamsApi) - : base(new()) - { - this.cloudStreamsApi = cloudStreamsApi; - } /// /// Gets an used to observe changes @@ -95,7 +82,7 @@ public void AddStreamsReadOption() { var streamsReadOptions = new List(this.Get(state => state.StreamsReadOptions)) { - new StreamReadOptions(StreamReadDirection.Backwards) + new(StreamReadDirection.Backwards) }; this.Reduce(state => state with { @@ -138,7 +125,7 @@ public void TryAddPartitionReadOption(PartitionReference partition) } streamsReadOptions = new List(streamsReadOptions) { - new StreamReadOptions(partition, StreamReadDirection.Backwards) + new(partition, StreamReadDirection.Backwards) }; this.Reduce(state => state with { @@ -183,7 +170,7 @@ public async Task GatherCloudEventsAsync() } if (options!.Length <= StreamReadOptions.MaxLength) { - var cloudEvents = await (await this.cloudStreamsApi.CloudEvents.Stream.ReadStreamAsync(options, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + var cloudEvents = await (await cloudStreamsApi.CloudEvents.Stream.ReadStreamAsync(options, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); data.AddRange(cloudEvents!); } else @@ -199,10 +186,10 @@ public async Task GatherCloudEventsAsync() Offset = offset, Length = StreamReadOptions.MaxLength }; - var cloudEvents = await (await this.cloudStreamsApi.CloudEvents.Stream.ReadStreamAsync(readOptions, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + var cloudEvents = await (await cloudStreamsApi.CloudEvents.Stream.ReadStreamAsync(readOptions, this.CancellationTokenSource.Token).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); data.AddRange(cloudEvents!); offset = (long)cloudEvents.Last()!.GetSequence()!; - fetchMore = cloudEvents.Count() > 1 && (ulong)data.Count < options!.Length; + fetchMore = cloudEvents.Count > 1 && (ulong)data.Count < options!.Length; } while(fetchMore); } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Timeline.razor b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Timeline.razor index f9480098..81796d6c 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Timeline.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/Timeline.razor @@ -1,6 +1,5 @@ @namespace CloudStreams.Dashboard.Components @using CloudStreams.Dashboard.Components.TimelineStateManagement -@using Hylo; @inherits StatefulComponent @implements IAsyncDisposable diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineCloudEventDetails.razor b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineCloudEventDetails.razor index abff4005..b79607b7 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineCloudEventDetails.razor +++ b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineCloudEventDetails.razor @@ -1,5 +1,5 @@ @namespace CloudStreams.Dashboard.Components -@using Hylo; +@inject Neuroglia.Serialization.IJsonSerializer JsonSerializer @if (cloudEvent != null) { @@ -59,7 +59,7 @@ @foreach (var attr in cloudEvent.ExtensionAttributes) { - @attr.Key.ToPascalCase() + @attr.Key @attr.Value } @@ -69,7 +69,7 @@ @if (cloudEvent!.Data != null) {
-            @Serializer.Json.Serialize(cloudEvent.Data, true)
+            @JsonSerializer.SerializeToText(cloudEvent.Data)
         
} } diff --git a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineLane.cs b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineLane.cs index ccf48e6a..d16a05d4 100644 --- a/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineLane.cs +++ b/src/dashboard/CloudStreams.Dashboard/Components/Timeline/TimelineLane.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -25,5 +25,5 @@ public class TimelineLane /// /// The set of data to render /// - public IEnumerable Data { get; set; } = new List(); + public IEnumerable Data { get; set; } = []; } diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/About/View.razor b/src/dashboard/CloudStreams.Dashboard/Pages/About/View.razor index b980d386..8d89be1c 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/About/View.razor +++ b/src/dashboard/CloudStreams.Dashboard/Pages/About/View.razor @@ -1,6 +1,5 @@ @namespace CloudStreams.Dashboard.Components @page "/about" -@using Hylo; Cloud Streams - About diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/Brokers/List/View.razor b/src/dashboard/CloudStreams.Dashboard/Pages/Brokers/List/View.razor index c9dc3278..8dbce3be 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/Brokers/List/View.razor +++ b/src/dashboard/CloudStreams.Dashboard/Pages/Brokers/List/View.razor @@ -49,9 +49,9 @@ - + - + diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/State.cs b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/State.cs index 2cadc32f..3c4278f1 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/State.cs +++ b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/State.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/Store.cs b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/Store.cs index c1f27b64..f0fe1ae2 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/Store.cs +++ b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/Store.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,30 +12,22 @@ // limitations under the License. using CloudStreams.Core.Api.Client.Services; -using CloudStreams.Dashboard.StateManagement; using Microsoft.AspNetCore.Components.Web.Virtualization; -using System.Reactive.Linq; namespace CloudStreams.Dashboard.Pages.CloudEvents.List; /// /// Represents the Cloud Event List's /// -public class CloudEventListStore - : ComponentStore +/// +/// Initializes a new +/// +/// The service used to interact with the Cloud Streams Gateway API +public class CloudEventListStore(ICloudStreamsCoreApiClient cloudStreamsApi) + : ComponentStore(new()) { - ICloudStreamsCoreApiClient cloudStreamsApi; - - /// - /// Initializes a new - /// - /// The service used to interact with the Cloud Streams Gateway API - public CloudEventListStore(ICloudStreamsCoreApiClient cloudStreamsApi) - : base(new()) - { - this.cloudStreamsApi = cloudStreamsApi; - } + ICloudStreamsCoreApiClient cloudStreamsApi = cloudStreamsApi; /// /// Gets an used to observe changes @@ -110,35 +102,26 @@ public void SetLoading(bool loading) /// The resulting public async ValueTask> ProvideCloudEvents(ItemsProviderRequest request) { - StreamReadOptions readOptions = this.Get(state => state.ReadOptions); - if (readOptions == null) - { - return new ItemsProviderResult(new List(), 0); - } + var readOptions = this.Get(state => state.ReadOptions); + if (readOptions == null) return new ItemsProviderResult([], 0); this.SetLoading(true); readOptions = readOptions with { }; if (readOptions.Partition?.Type == null || readOptions.Partition?.Id == null) { readOptions = readOptions with { Partition = null }; } - int totalCount = (int?)this.Get(state => state.TotalCount) ?? 100; + var totalCount = (int?)this.Get(state => state.TotalCount) ?? 100; if (readOptions.Direction == StreamReadDirection.Forwards) { readOptions.Offset = (readOptions.Offset ?? 0) + request.StartIndex; } else { - if ((readOptions.Offset??0) == 0 && request.StartIndex == 0) - { - readOptions.Offset = -1; - } - else - { - readOptions.Offset = Math.Max((readOptions.Offset ?? totalCount) - request.StartIndex, 0); - } + if ((readOptions.Offset??0) == 0 && request.StartIndex == 0) readOptions.Offset = -1; + else readOptions.Offset = Math.Max((readOptions.Offset ?? totalCount) - request.StartIndex, 0); } readOptions.Length = (ulong)request.Count; - List fetchedCloudEvents = new(); + var fetchedCloudEvents = new List(); if (readOptions.Length <= StreamReadOptions.MaxLength) { fetchedCloudEvents = await (await this.cloudStreamsApi.CloudEvents.Stream.ReadStreamAsync(readOptions, request.CancellationToken).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false) as List; @@ -146,7 +129,7 @@ public async ValueTask> ProvideCloudEvents(Items else { bool fetchMore = true; - long offset = readOptions.Offset.Value; + var offset = readOptions.Offset.Value; do { StreamReadOptions tempReadOptions = readOptions with { }; diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/View.razor b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/View.razor index 3a608ee4..5be483ba 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/View.razor +++ b/src/dashboard/CloudStreams.Dashboard/Pages/CloudEvents/List/View.razor @@ -3,7 +3,6 @@ @using CloudStreams.Dashboard.Pages.CloudEvents.List @using System.Reactive.Linq @using CloudStreams.Dashboard.StateManagement -@using Hylo; @inherits StatefulComponent Events diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/Gateways/List/View.razor b/src/dashboard/CloudStreams.Dashboard/Pages/Gateways/List/View.razor index dae3f897..7ebf5ac1 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/Gateways/List/View.razor +++ b/src/dashboard/CloudStreams.Dashboard/Pages/Gateways/List/View.razor @@ -55,9 +55,9 @@ - + - + diff --git a/src/dashboard/CloudStreams.Dashboard/Pages/Subscriptions/List/View.razor b/src/dashboard/CloudStreams.Dashboard/Pages/Subscriptions/List/View.razor index 8cc92e21..fd62ec77 100644 --- a/src/dashboard/CloudStreams.Dashboard/Pages/Subscriptions/List/View.razor +++ b/src/dashboard/CloudStreams.Dashboard/Pages/Subscriptions/List/View.razor @@ -1,9 +1,9 @@ @namespace CloudStreams.Dashboard.Pages.Subscriptions.List @page "/subscriptions" @using CloudStreams.Core.Api.Client.Services; -@using Hylo; @using Json.Patch; @using Json.Pointer; +@using Neuroglia.Data @inherits ResourceManagementComponent @inject ICloudStreamsCoreApiClient ApiClient; @@ -101,9 +101,9 @@ - + - + diff --git a/src/dashboard/CloudStreams.Dashboard/Program.cs b/src/dashboard/CloudStreams.Dashboard/Program.cs index e8329eec..a724c63b 100644 --- a/src/dashboard/CloudStreams.Dashboard/Program.cs +++ b/src/dashboard/CloudStreams.Dashboard/Program.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,19 +11,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -using BlazorBootstrap; using CloudStreams.Core.Api.Client; using CloudStreams.Dashboard; using CloudStreams.Dashboard.Components; -using CloudStreams.Dashboard.StateManagement; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Neuroglia.Serialization; +using Neuroglia.Serialization.Json.Converters; +using Neuroglia.Serialization.Json; +using System.Text.Json.Serialization; +using System.Text.Json; +using Microsoft.Extensions.Options; +using System.Text.Json.Nodes; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); +builder.Services.Configure(Neuroglia.Serialization.Json.JsonSerializer.DefaultOptionsConfiguration); +builder.Services.AddSerialization(); +builder.Services.AddJsonSerializer(); +builder.Services.AddYamlDotNetSerializer(); builder.Services.AddScoped(provider => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddCloudStreamsCoreApiClient(options => { @@ -39,4 +48,6 @@ builder.Services.AddSingleton(); builder.Services.AddBlazorBootstrap(); -await builder.Build().RunAsync(); \ No newline at end of file +var app = builder.Build(); + +await app.RunAsync(); \ No newline at end of file diff --git a/src/dashboard/CloudStreams.Dashboard/Services/EventDropsInterop.cs b/src/dashboard/CloudStreams.Dashboard/Services/EventDropsInterop.cs index 3e6a5032..b2b4280d 100644 --- a/src/dashboard/CloudStreams.Dashboard/Services/EventDropsInterop.cs +++ b/src/dashboard/CloudStreams.Dashboard/Services/EventDropsInterop.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Services/MonacoInterop.cs b/src/dashboard/CloudStreams.Dashboard/Services/MonacoInterop.cs index caf5d9d2..1a93fcce 100644 --- a/src/dashboard/CloudStreams.Dashboard/Services/MonacoInterop.cs +++ b/src/dashboard/CloudStreams.Dashboard/Services/MonacoInterop.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/StatefulComponent.cs b/src/dashboard/CloudStreams.Dashboard/StatefulComponent.cs index 8651a1bb..44ec1075 100644 --- a/src/dashboard/CloudStreams.Dashboard/StatefulComponent.cs +++ b/src/dashboard/CloudStreams.Dashboard/StatefulComponent.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/dashboard/CloudStreams.Dashboard/Usings.cs b/src/dashboard/CloudStreams.Dashboard/Usings.cs index 56ed2631..2352264a 100644 --- a/src/dashboard/CloudStreams.Dashboard/Usings.cs +++ b/src/dashboard/CloudStreams.Dashboard/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using BlazorMonaco; global using BlazorMonaco.Editor; - global using CloudStreams.Core; -global using CloudStreams.Core.Data; -global using CloudStreams.Dashboard.Services; \ No newline at end of file +global using CloudStreams.Core.Resources; +global using CloudStreams.Dashboard.Services; +global using CloudStreams.Dashboard.StateManagement; +global using Neuroglia; +global using Neuroglia.Data.Infrastructure.EventSourcing; +global using Neuroglia.Data.Infrastructure.ResourceOriented; +global using Neuroglia.Eventing.CloudEvents; +global using Neuroglia.Reactive; +global using System.Reactive.Linq; \ No newline at end of file diff --git a/src/dashboard/CloudStreams.Dashboard/_Imports.razor b/src/dashboard/CloudStreams.Dashboard/_Imports.razor index 677d08ee..17a431e1 100644 --- a/src/dashboard/CloudStreams.Dashboard/_Imports.razor +++ b/src/dashboard/CloudStreams.Dashboard/_Imports.razor @@ -1,6 +1,12 @@ @using BlazorBootstrap; @using BlazorMonaco; @using BlazorMonaco.Editor; +@using CloudStreams.Core; +@using CloudStreams.Dashboard; +@using CloudStreams.Dashboard.Components +@using CloudStreams.Dashboard.Services; +@using CloudStreams.Dashboard.StateManagement; +@using CloudStreams.Core.Api.Client.Services; @using Microsoft.AspNetCore.Components.Forms; @using Microsoft.AspNetCore.Components.Routing; @using Microsoft.AspNetCore.Components.Web; @@ -11,12 +17,4 @@ @using System.Net.Http; @using System.Net.Http.Json; @using System.Reactive.Linq; -@using System.Reactive.Subjects; - -@using CloudStreams.Core; -@using CloudStreams.Core.Data; -@using CloudStreams.Dashboard; -@using CloudStreams.Dashboard.Components -@using CloudStreams.Dashboard.Services; -@using CloudStreams.Dashboard.StateManagement; -@using CloudStreams.Core.Api.Client.Services; \ No newline at end of file +@using System.Reactive.Subjects; \ No newline at end of file diff --git a/src/dashboard/CloudStreams.Dashboard/package-lock.json b/src/dashboard/CloudStreams.Dashboard/package-lock.json new file mode 100644 index 00000000..4052382d --- /dev/null +++ b/src/dashboard/CloudStreams.Dashboard/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "CloudStreams.Dashboard", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/src/dashboard/CloudStreams.Dashboard/wwwroot/lib/bootstrap/package-lock.json b/src/dashboard/CloudStreams.Dashboard/wwwroot/lib/bootstrap/package-lock.json new file mode 100644 index 00000000..9604d89d --- /dev/null +++ b/src/dashboard/CloudStreams.Dashboard/wwwroot/lib/bootstrap/package-lock.json @@ -0,0 +1,11532 @@ +{ + "name": "bootstrap", + "version": "5.3.0-alpha1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bootstrap", + "version": "5.3.0-alpha1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "devDependencies": { + "@babel/cli": "^7.19.3", + "@babel/core": "^7.20.5", + "@babel/preset-env": "^7.20.2", + "@popperjs/core": "^2.11.6", + "@rollup/plugin-babel": "^6.0.3", + "@rollup/plugin-commonjs": "^24.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2", + "autoprefixer": "^10.4.13", + "bundlewatch": "^0.3.3", + "clean-css-cli": "^5.6.1", + "cross-env": "^7.0.3", + "eslint": "^8.30.0", + "eslint-config-xo": "^0.43.1", + "eslint-plugin-html": "^7.1.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-markdown": "^3.0.0", + "eslint-plugin-unicorn": "^45.0.2", + "find-unused-sass-variables": "^4.0.5", + "globby": "^11.1.0", + "hammer-simulator": "0.0.1", + "hugo-bin": "^0.96.0", + "ip": "^2.0.0", + "jquery": "^3.6.2", + "karma": "^6.4.1", + "karma-browserstack-launcher": "1.4.0", + "karma-chrome-launcher": "^3.1.1", + "karma-coverage-istanbul-reporter": "^3.0.3", + "karma-detect-browsers": "^2.3.3", + "karma-firefox-launcher": "^2.1.2", + "karma-jasmine": "^5.1.0", + "karma-jasmine-html-reporter": "^2.0.0", + "karma-rollup-preprocessor": "7.0.7", + "lockfile-lint": "^4.9.6", + "nodemon": "^2.0.20", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.20", + "postcss-cli": "^10.1.0", + "rollup": "^3.7.5", + "rollup-plugin-istanbul": "^4.0.0", + "rtlcss": "^4.0.0", + "sass": "^1.57.1", + "shelljs": "^0.8.5", + "stylelint": "^14.16.0", + "stylelint-config-twbs-bootstrap": "^7.0.0", + "terser": "5.16.0", + "vnu-jar": "22.9.29" + }, + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.24.1.tgz", + "integrity": "sha512-HbmrtxyFUr34LwAlV9jS+sSIjUp4FpdtIMGwgufY3AsxrIfsh/HxlMTywsONAZsU0RMYbZtbZFpUCrSGs7o0EA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "commander": "^4.0.1", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", + "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz", + "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", + "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", + "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", + "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", + "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz", + "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.4", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.1", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.1", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.1", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.1", + "@babel/plugin-transform-parameters": "^7.24.1", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.1", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.1", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz", + "integrity": "sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", + "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/magic-string": { + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz", + "integrity": "sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg==", + "dev": true + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", + "dev": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0.tgz", + "integrity": "sha512-jVZa3njBv6tcOUw34nlUdUM/40wwtm/gnVF8rtk0tA6vNcokqYI8CFU1BZjlpFwUSZaXxYkrtuPE/f2MMFlTxQ==", + "dev": true, + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yarnpkg/parsers/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@yarnpkg/parsers/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "dev": true, + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.1", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "dev": true, + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "dev": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dev": true, + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstacktunnel-wrapper": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.5.tgz", + "integrity": "sha512-oociT3nl+FhQnyJbAb1RM4oQ5pN7aKeXEURkTkiEVm/Rji2r0agl3Wbw5V23VFn9lCU5/fGyDejRZPtGYsEcFw==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1", + "unzipper": "^0.9.3" + }, + "engines": { + "node": ">= 0.10.20" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bundlewatch": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/bundlewatch/-/bundlewatch-0.3.3.tgz", + "integrity": "sha512-qzSVWrZyyWXa546JpAPRPTFmnXms9YNVnfzB05DRJKmN6wRRa7SkxE4OgKQmbAY74Z6CM2mKAc6vwvd2R+1lUQ==", + "dev": true, + "dependencies": { + "axios": "^0.24.0", + "bytes": "^3.1.1", + "chalk": "^4.0.0", + "ci-env": "^1.17.0", + "commander": "^5.0.0", + "glob": "^7.1.2", + "gzip-size": "^6.0.0", + "jsonpack": "^1.1.5", + "lodash.merge": "^4.6.1", + "read-pkg-up": "^7.0.1" + }, + "bin": { + "bundlewatch": "lib/bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bundlewatch/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/bundlewatch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/bundlewatch/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/bundlewatch/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/bundlewatch/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bundlewatch/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bundlewatch/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "dev": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001608", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", + "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dev": true, + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-env": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ci-env/-/ci-env-1.17.0.tgz", + "integrity": "sha512-NtTjhgSEqv4Aj90TUYHQLxHdnCPXnjdtuGG1X8lTfp/JqeXTdw0FTWl/vUAPuvbWZTF8QVpv6ASe/XacE+7R2A==", + "dev": true + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css-cli": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/clean-css-cli/-/clean-css-cli-5.6.3.tgz", + "integrity": "sha512-MUAta8pEqA/d2DKQwtZU5nm0Og8TCyAglOx3GlWwjhGdKBwY4kVF6E5M6LU/jmmuswv+HbYqG/dKKkq5p1dD0A==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "clean-css": "^5.3.3", + "commander": "7.x", + "glob": "^7.1.6" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/clean-css-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", + "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-functions-list": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz", + "integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==", + "dev": true, + "engines": { + "node": ">=12 || >=16" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "dev": true, + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/download/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.733", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.733.tgz", + "integrity": "sha512-gUI9nhI2iBGF0OaYYLKOaOtliFMl+Bt1rY7VmEjwxOxqoYLub/D9xmduPEhbw2imE6gYkJKhIE5it+KE2ulVxQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-xo": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.43.1.tgz", + "integrity": "sha512-azv1L2PysRA0NkZOgbndUpN+581L7wPqkgJOgxxw3hxwXAbJgD6Hqb/SjHRiACifXt/AvxCzE/jIKFAlI7XjvQ==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "1.0.11" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "eslint": ">=8.27.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-html": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz", + "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-markdown": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-3.0.1.tgz", + "integrity": "sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "45.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-45.0.2.tgz", + "integrity": "sha512-Y0WUDXRyGDMcKLiwgL3zSMpHrXI00xmdyixEGIg90gHnj0PcHY4moNv3Ppje/kDivdAy5vUeUr7z211ImPv2gw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.19.1", + "@eslint-community/eslint-utils": "^4.1.2", + "ci-info": "^3.6.1", + "clean-regexp": "^1.0.0", + "esquery": "^1.4.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.0", + "jsesc": "^3.0.2", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.24", + "regjsparser": "^0.9.1", + "safe-regex": "^2.1.1", + "semver": "^7.3.8", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.28.0" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-unused-sass-variables": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-unused-sass-variables/-/find-unused-sass-variables-4.1.0.tgz", + "integrity": "sha512-lAxvefiOTVn8k+3viGZfU4nUyj1Y/9KChFTyPzkT5aUX7pCOCrwYEMiBHpkScEViiidUCjwgF/e9vxD/RT3ocA==", + "dev": true, + "dependencies": { + "commander": "^9.5.0", + "escape-string-regexp": "^5.0.0", + "glob": "^7.2.3", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-scss": "^4.0.6", + "strip-bom": "^5.0.0" + }, + "bin": { + "find-unused-sass-variables": "cli.js", + "fusv": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.14.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/XhmikosR" + } + }, + "node_modules/find-unused-sass-variables/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/find-unused-sass-variables/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hammer-simulator": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/hammer-simulator/-/hammer-simulator-0.0.1.tgz", + "integrity": "sha512-WbyZImCJlHOs2HtkPJSCksq1i/V/MIbpk44/ALOCTF03FvOKhWcwAl3x4W9dQm8cW0VCM57HpxaCjslDEYPIJg==", + "dev": true + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/hugo-bin": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/hugo-bin/-/hugo-bin-0.96.0.tgz", + "integrity": "sha512-a85/ZvD7NJ/Puz8/fRKVhHxxB/+qaN96fdOxHrtTeqsnZXXcpMGuD3LhN4h8xeqfAfJHWroeI2R7bPfnr+qmWg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bin-wrapper": "^4.1.0", + "picocolors": "^1.0.0", + "pkg-conf": "^4.0.0", + "rimraf": "^3.0.2" + }, + "bin": { + "hugo": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "dev": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "dev": true + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jasmine-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "dev": true, + "peer": true + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpack": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jsonpack/-/jsonpack-1.1.5.tgz", + "integrity": "sha512-d2vwomK605ks7Q+uCpbwGyoIF5j+UZuJjlYcugISBt3CxM+eBo/W6y63yVPIyIvbYON+pvJYsYZjCYbzqJj/xQ==", + "dev": true + }, + "node_modules/karma": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-browserstack-launcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.4.0.tgz", + "integrity": "sha512-bUQK84U+euDfOUfEjcF4IareySMOBNRLrrl9q6cttIe8f011Ir6olLITTYMOJDcGY58wiFIdhPHSPd9Pi6+NfQ==", + "dev": true, + "dependencies": { + "browserstack": "~1.5.1", + "browserstacktunnel-wrapper": "~2.0.2", + "q": "~1.5.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/mattlewis92" + } + }, + "node_modules/karma-detect-browsers": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/karma-detect-browsers/-/karma-detect-browsers-2.3.3.tgz", + "integrity": "sha512-ltFVyA3ijThv9l9TQ+TKnccoMk6YAWn8OMaccL+n8pO2LGwMOcy6tUWy3Mnv9If29jqvVHDCWntj7wBQpPtv7Q==", + "dev": true, + "dependencies": { + "which": "^1.2.4" + } + }, + "node_modules/karma-detect-browsers/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz", + "integrity": "sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==", + "dev": true, + "dependencies": { + "is-wsl": "^2.2.0", + "which": "^3.0.0" + } + }, + "node_modules/karma-firefox-launcher/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", + "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "dev": true + }, + "node_modules/karma-rollup-preprocessor": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/karma-rollup-preprocessor/-/karma-rollup-preprocessor-7.0.7.tgz", + "integrity": "sha512-Y1QwsTCiCBp8sSALZdqmqry/mWIWIy0V6zonUIpy+0/D/Kpb2XZvR+JZrWfacQvcvKQdZFJvg6EwlnKtjepu3Q==", + "dev": true, + "dependencies": { + "chokidar": "^3.3.1", + "debounce": "^1.2.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": ">= 1.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", + "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lockfile-lint": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/lockfile-lint/-/lockfile-lint-4.13.2.tgz", + "integrity": "sha512-yeg0vJ3NjC6OVMZtC+nSLLavu/e8LE5FZp9u0Itqyt7I0gYYCgGxAsJV3TJ7WtaJd4PahineJvHqSk/4sqzU8w==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.2.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "lockfile-lint-api": "^5.9.1", + "yargs": "^17.7.2" + }, + "bin": { + "lockfile-lint": "bin/lockfile-lint.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/lockfile-lint-api": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/lockfile-lint-api/-/lockfile-lint-api-5.9.1.tgz", + "integrity": "sha512-us5IT1bGA6KXbq1WrhrSzk9mtPgHKz5nhvv3S4hwcYnhcVOKW2uK0W8+PN9oIgv4pI49WsD5wBdTQFTpNChF/Q==", + "dev": true, + "dependencies": { + "@yarnpkg/parsers": "^3.0.0-rc.48.1", + "debug": "^4.3.4", + "object-hash": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/lockfile-lint/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lockfile-lint/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lockfile-lint/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-conf": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-4.0.0.tgz", + "integrity": "sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==", + "dev": true, + "dependencies": { + "find-up": "^6.0.0", + "load-json-file": "^7.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-conf/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-cli": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-10.1.0.tgz", + "integrity": "sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==", + "dev": true, + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^11.0.0", + "get-stdin": "^9.0.0", + "globby": "^13.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^4.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/postcss-cli/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-cli/node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-cli/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/postcss-cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sorting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", + "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", + "dev": true, + "peerDependencies": { + "postcss": "^8.3.9" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-istanbul": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-istanbul/-/rollup-plugin-istanbul-4.0.0.tgz", + "integrity": "sha512-AOauxxl4eAHWdvTnY/uwSrwMkbDymTWUhaD6aym8a4YJaO9hxK2U8bcuhZA0iravuOTUulqPWUbYP7mTV7i4oQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.2", + "istanbul-lib-instrument": "^5.2.1" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/rtlcss": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", + "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "dev": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.74.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz", + "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "dev": true, + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length/node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz", + "integrity": "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/stylelint": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", + "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.26.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-recess-order": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-3.1.0.tgz", + "integrity": "sha512-LXR6zD5O9cS1a9gbLbuKvWLs7qmHj4xm5MQ5KhhwZPMhtQP9da3F6Jsp/NAUdsAwDQEnT1ShU16YVdgN6p4a/w==", + "dev": true, + "dependencies": { + "stylelint-order": "5.x" + }, + "peerDependencies": { + "stylelint": ">=14" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", + "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.10.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz", + "integrity": "sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==", + "dev": true, + "dependencies": { + "postcss-scss": "^4.0.2", + "stylelint-config-recommended": "^9.0.0", + "stylelint-scss": "^4.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^14.10.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-standard": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", + "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^9.0.0" + }, + "peerDependencies": { + "stylelint": "^14.14.0" + } + }, + "node_modules/stylelint-config-standard-scss": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-6.1.0.tgz", + "integrity": "sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==", + "dev": true, + "dependencies": { + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-standard": "^29.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^14.14.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-twbs-bootstrap": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-twbs-bootstrap/-/stylelint-config-twbs-bootstrap-7.0.0.tgz", + "integrity": "sha512-CZUc+BhBTJFi0Hw/6vq3KqvCaLMCQlFGz1tIAHACEt10QuFBh6IBSTWCr/oU8xS6m4YbYcng7ayPYwn+43l68w==", + "dev": true, + "dependencies": { + "stylelint-config-recess-order": "^3.0.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-config-standard-scss": "^6.1.0", + "stylelint-scss": "^4.3.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "stylelint": "^14.14.0" + } + }, + "node_modules/stylelint-order": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz", + "integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==", + "dev": true, + "dependencies": { + "postcss": "^8.3.11", + "postcss-sorting": "^7.0.1" + }, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-scss": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", + "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "dev": true, + "dependencies": { + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1 || ^15.0.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stylelint/node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terser": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.0.tgz", + "integrity": "sha512-KjTV81QKStSfwbNiwlBXfcgMcOloyuRdb62/iLFPGBcVNF4EXjhdYBhYHmbJpiBrVxZhDvltE11j+LBQUxEEJg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", + "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vnu-jar": { + "version": "22.9.29", + "resolved": "https://registry.npmjs.org/vnu-jar/-/vnu-jar-22.9.29.tgz", + "integrity": "sha512-AM0mnQEXT5UgT/YoCGy/4KE0i0A4iz9MWUIevzUlYb36UMnsHHQQYnA0ptm971JyaQ5Dg+z3u8AyobotmwkZUQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/CloudStreams.Gateway.Api.Client.csproj b/src/gateway/CloudStreams.Gateway.Api.Client/CloudStreams.Gateway.Api.Client.csproj deleted file mode 100644 index 1d7cd78d..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/CloudStreams.Gateway.Api.Client.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;gateway;api;client - Contains the client implementation of the CloudStreams Gateway API - logo.png - Apache-2.0 - True - True - - - - - \ - True - - - \ - True - - - - - - - - - - - - diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Configuration/CloudStreamsGatewayApiOptions.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Configuration/CloudStreamsGatewayApiOptions.cs deleted file mode 100644 index a4b7b7cf..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Configuration/CloudStreamsGatewayApiOptions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Gateway.Api.Client.Services; - -namespace CloudStreams.Gateway.Api.Client.Configuration; - -/// -/// Represents the options used to configure an -/// -public class CloudStreamGatewayApiClientOptions -{ - - /// - /// Gets/sets the base address of the Cloud Streams API to connect to - /// - public virtual string BaseAddress { get; set; } = null!; - -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Extensions/IServiceCollectionExtensions.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Extensions/IServiceCollectionExtensions.cs deleted file mode 100644 index 20de2cb1..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Extensions/IServiceCollectionExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Gateway.Api.Client.Configuration; -using CloudStreams.Gateway.Api.Client.Services; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; - -namespace CloudStreams.Gateway.Api.Client; - -/// -/// Defines extensions for s -/// -public static class IServiceCollectionExtensions -{ - - /// - /// Adds and configures a new - /// - /// The to configure - /// An used to setup the to use - /// The configured - public static IServiceCollection AddCloudStreamsGatewayApiClient(this IServiceCollection services, Action setup) - { - services.Configure(setup); - services.TryAddScoped(); - services.TryAddSingleton(provider => - { - var options = provider.GetRequiredService>().Value; - var connection = new HubConnectionBuilder() - .WithUrl($"{options.BaseAddress}api/gateway/v1/ws/cloud-events") - .WithAutomaticReconnect() - .Build(); - return new CloudEventHubClient(connection); - }); - return services; - } - -} \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudEventHubClient.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudEventHubClient.cs deleted file mode 100644 index 23165947..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudEventHubClient.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; -using Microsoft.AspNetCore.SignalR.Client; -using System.Reactive.Subjects; - -namespace CloudStreams.Gateway.Api.Client.Services; - -/// -/// Represents the service used to listen to s in real-time -/// -public class CloudEventHubClient - : IAsyncDisposable -{ - - private bool _Disposed; - - /// - /// Initializes a new - /// - /// The underlying - public CloudEventHubClient(HubConnection connection) - { - this.Connection = connection; - this.Connection.On(nameof(ICloudEventHubClient.StreamEvent), this.Subject.OnNext); - } - - /// - /// Gets the underlying - /// - protected HubConnection Connection { get; } - - /// - /// Gets the used to monitor ingested s - /// - protected Subject Subject { get; } = new(); - - /// - /// Starts the if it's not already running - /// - /// A new awaitable - public virtual Task StartAsync() => this.Connection.State == HubConnectionState.Disconnected ? this.Connection.StartAsync() : Task.CompletedTask; - - /// - public IObservable SelectAll() => this.Subject; - - /// - /// Disposes of the - /// - /// A boolean indicating whether or not the is being disposed of - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (this._Disposed) return; - if (disposing) - { - this.Subject.Dispose(); - await this.Connection.DisposeAsync(); - } - this._Disposed = true; - } - - /// - public async ValueTask DisposeAsync() - { - await this.DisposeAsync(disposing: true); - GC.SuppressFinalize(this); - } - -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.CloudEvents.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.CloudEvents.cs deleted file mode 100644 index 22c66da2..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.CloudEvents.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; -using System.Text; - -namespace CloudStreams.Gateway.Api.Client.Services; - -public partial class CloudStreamsGatewayApiClient - : ICloudEventsApi -{ - - private const string ApiVersionPathPrefix = "api/gateway/v1/"; - private const string CloudEventsApiPath = ApiVersionPathPrefix + "cloud-events/"; - - /// - public virtual async Task PublishCloudEventAsync(CloudEvent e, CancellationToken cancellationToken = default) - { - if (e == null) throw new ArgumentNullException(nameof(e)); - var json = Hylo.Serializer.Json.Serialize(e); - using var content = new StringContent(json, Encoding.UTF8, CloudEventMediaTypeNames.CloudEventsJson); - using var request = await this.ProcessRequestAsync(new HttpRequestMessage(HttpMethod.Post, $"{CloudEventsApiPath}pub") { Content = content }, cancellationToken).ConfigureAwait(false); - using var response = await this.ProcessResponseAsync(await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); - } - -} \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.cs deleted file mode 100644 index c4218496..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/CloudStreamsGatewayApiClient.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Hylo; -using Microsoft.Extensions.Logging; - -namespace CloudStreams.Gateway.Api.Client.Services; - -/// -/// Represents the default implementation of the interface -/// -public partial class CloudStreamsGatewayApiClient - : ICloudStreamsGatewayApiClient -{ - - /// - /// Initializes a new - /// - /// The service used to create s - /// The service used to perform http requests - public CloudStreamsGatewayApiClient(ILoggerFactory loggerFactory, HttpClient httpClient) - { - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.HttpClient = httpClient; - } - - /// - /// Gets the service used to perform logging - /// - protected ILogger Logger { get; } - - /// - /// Gets the service used to perform http requests - /// - protected HttpClient HttpClient { get; } - - ICloudEventsApi ICloudStreamsGatewayApiClient.CloudEvents => this; - - /// - /// Processes the specified before sending it - /// - /// the to process - /// A - /// The processed - protected virtual Task ProcessRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) - { - if(request == null) throw new ArgumentNullException(nameof(request)); - return Task.FromResult(request); - } - - /// - /// Processes the specified - /// - /// the to process - /// A - /// The processed - protected virtual async Task ProcessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) - { - if (response == null) throw new ArgumentNullException(nameof(response)); - if (response.IsSuccessStatusCode) return response; - var content = string.Empty; - if (response.Content != null) content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - this.Logger.LogError("The remote server responded with a non-success status code '{statusCode}': {errorDetails}", response.StatusCode, content); - if (!response.IsSuccessStatusCode) - { - if (string.IsNullOrWhiteSpace(content)) response.EnsureSuccessStatusCode(); - else throw new CloudStreamsException(Hylo.Serializer.Json.Deserialize(content)); - } - return response; - } - -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventsApi.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventsApi.cs deleted file mode 100644 index 5c54c15d..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventsApi.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Data; - -namespace CloudStreams.Gateway.Api.Client.Services; - -/// -/// Defines the fundamentals of the Cloud Streams gateway API used to manage s -/// -public interface ICloudEventsApi -{ - - /// - /// Publishes the specified to the Cloud Streams gateway - /// - /// The to publish - /// A - /// A new awaitable - Task PublishCloudEventAsync(CloudEvent e, CancellationToken cancellationToken = default); - -} diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Usings.cs b/src/gateway/CloudStreams.Gateway.Api.Client/Usings.cs deleted file mode 100644 index 0cb82f43..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Usings.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -global using CloudStreams.Core; \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj b/src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj deleted file mode 100644 index 8bf50a1c..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - net7.0 - enable - enable - True - 0.14.0 - $(VersionPrefix) - $(VersionPrefix) - en - Apache-2.0 - Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. - https://github.com/neuroglia-io/cloud-streams - README.md - https://github.com/neuroglia-io/cloud-streams - git - cloudstreams-gateway - Linux - ..\..\.. - - - - - - - - - - - - - - - diff --git a/src/gateway/CloudStreams.Gateway.Api.Server/Dockerfile b/src/gateway/CloudStreams.Gateway.Api.Server/Dockerfile deleted file mode 100644 index 917b503a..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Server/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 -RUN apt-get update -RUN apt-get install -y jq - -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj", "src/gateway/CloudStreams.Gateway.Api.Server/"] -COPY ["src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj", "src/gateway/CloudStreams.Gateway.Api/"] -COPY ["src/gateway/CloudStreams.Gateway.Api.Client/CloudStreams.Gateway.Api.Client.csproj", "src/gateway/CloudStreams.Gateway.Api.Client/"] -COPY ["src/core/CloudStreams.Core/CloudStreams.Core.csproj", "src/core/CloudStreams.Core/"] -COPY ["src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj", "src/gateway/CloudStreams.Gateway.Application/"] -COPY ["src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj", "src/core/CloudStreams.Core.Application/"] -COPY ["src/core/CloudStreams.Core.Infrastructure/CloudStreams.Core.Infrastructure.csproj", "src/core/CloudStreams.Core.Infrastructure/"] -RUN dotnet restore "src/gateway/CloudStreams.Gateway.Api.Server/CloudStreams.Gateway.Api.Server.csproj" -COPY . . -WORKDIR "/src/src/gateway/CloudStreams.Gateway.Api.Server" -RUN dotnet build "CloudStreams.Gateway.Api.Server.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "CloudStreams.Gateway.Api.Server.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "CloudStreams.Gateway.Api.Server.dll"] \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api.Server/Program.cs b/src/gateway/CloudStreams.Gateway.Api.Server/Program.cs deleted file mode 100644 index 24810f5b..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Server/Program.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Application.Configuration; -using CloudStreams.Gateway.Api.Configuration; -using CloudStreams.Gateway.Api.Hubs; -using HealthChecks.UI.Client; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Swashbuckle.AspNetCore.SwaggerUI; - -var builder = WebApplication.CreateBuilder(args); - -builder.UseCloudStreams(builder => -{ - builder.UseGatewayApi(); -}); - -using var app = builder.Build(); - -app.UseExceptionHandler("/error"); -app.UseResponseCompression(); -app.UseStaticFiles(); -app.UseRouting(); -app.UseAuthorization(); -app.UseHealthChecks("/healthz", new HealthCheckOptions() -{ - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.UseSwagger(builder => -{ - builder.RouteTemplate = "api/{documentName}/doc/oas.{json|yaml}"; -}); -app.UseSwaggerUI(builder => -{ - builder.DocExpansion(DocExpansion.None); - builder.SwaggerEndpoint("/api/v1/doc/oas.json", "Cloud Streams API v1"); - builder.RoutePrefix = "api/doc"; - builder.DisplayOperationId(); -}); - -app.MapControllers(); -app.MapHub("api/gateway/v1/ws/cloud-events"); - -await app.RunAsync().ConfigureAwait(false); diff --git a/src/gateway/CloudStreams.Gateway.Api.Server/Properties/launchSettings.json b/src/gateway/CloudStreams.Gateway.Api.Server/Properties/launchSettings.json deleted file mode 100644 index aa5c1d1e..00000000 --- a/src/gateway/CloudStreams.Gateway.Api.Server/Properties/launchSettings.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "profiles": { - "http": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "CLOUDSTREAMS_GATEWAY_NAME": "gateway-1", - "CONNECTIONSTRINGS__EVENTSTORE": "esdb://localhost:2113?tls=false", - "CONNECTIONSTRINGS__REDIS": "localhost" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5224" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", - "publishAllPorts": true - } - }, - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:46069", - "sslPort": 0 - } - } -} \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj b/src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj index eb8bdecb..7b337db2 100644 --- a/src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj +++ b/src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj @@ -1,50 +1,35 @@ - + - net7.0 - Library - enable + net8.0 enable - True + enable 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;gateway;api; - Contains the Cloud Streams Gateway API controllers and services - logo.png - Apache-2.0 - True - True - True + true - - - - - - \ - True - - - \ - True - - - - - + + + + + + - + + diff --git a/src/gateway/CloudStreams.Gateway.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs b/src/gateway/CloudStreams.Gateway.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs deleted file mode 100644 index a1f124bb..00000000 --- a/src/gateway/CloudStreams.Gateway.Api/Configuration/ICloudStreamsApiBuilderExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2023-Present The Cloud Streams Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"), -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using CloudStreams.Core.Infrastructure.Configuration; -using CloudStreams.Gateway.Api.Controllers; -using CloudStreams.Gateway.Api.Services; -using CloudStreams.Gateway.Application.Commands.CloudEvents; -using CloudStreams.Gateway.Application.Configuration; -using CloudStreams.Gateway.Application.Services; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace CloudStreams.Gateway.Api.Configuration; - -/// -/// Defines extensions for s -/// -public static class ICloudStreamsApiBuilderExtensions -{ - - /// - /// Configures CloudStreams to use the Gateway API - /// - /// The to configure - /// The configured - public static ICloudStreamsApplicationBuilder UseGatewayApi(this ICloudStreamsApplicationBuilder builder) - { - var options = new GatewayOptions(); - builder.Configuration.AddEnvironmentVariables(GatewayOptions.EnvironmentVariablePrefix); - builder.Configuration.Bind(options); - - builder.WithServiceName(options.Name); - builder.Services.Configure(builder.Configuration); - builder.RegisterApplicationPart(); - builder.RegisterMediationAssembly(); - builder.RegisterValidationAssembly(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(provider => provider.GetRequiredService()); - builder.Services.AddHostedService(); - builder.Services.AddHostedService(provider => provider.GetRequiredService()); - builder.Services.AddSingleton(); - return builder; - } - -} diff --git a/src/gateway/CloudStreams.Gateway.Api/Controllers/CloudEventsController.cs b/src/gateway/CloudStreams.Gateway.Api/Controllers/EventsController.cs similarity index 60% rename from src/gateway/CloudStreams.Gateway.Api/Controllers/CloudEventsController.cs rename to src/gateway/CloudStreams.Gateway.Api/Controllers/EventsController.cs index 2ed1b025..5206b76c 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Controllers/CloudEventsController.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Controllers/EventsController.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,21 +12,21 @@ // limitations under the License. using CloudStreams.Gateway.Application.Commands.CloudEvents; -using Hylo.Api.Http; +using Microsoft.AspNetCore.Mvc; +using Neuroglia.Mediation; +using Neuroglia.Mediation.AspNetCore; -namespace CloudStreams.Gateway.Api.Controllers; +namespace CloudStreams.Gateway.Controllers; /// /// Represents the API controller used to manage events /// -[Route("api/gateway/v1/cloud-events")] -public class CloudEventsController - : ApiController +/// The service used to mediate calls +[Route("api/events")] +public class EventsController(IMediator mediator) + : Controller { - /// - public CloudEventsController(IMediator mediator) : base(mediator) { } - /// /// Publishes the specified cloud event /// @@ -34,15 +34,15 @@ public CloudEventsController(IMediator mediator) : base(mediator) { } /// A /// A new [HttpPost("pub")] - [Consumes(CloudEventMediaTypeNames.CloudEvents, CloudEventMediaTypeNames.CloudEventsJson, CloudEventMediaTypeNames.CloudEventsYaml)] + [Consumes(CloudEventContentType.Json)] [ProducesResponseType((int)HttpStatusCode.Accepted)] - [ProducesResponseType(typeof(ProblemDetails), (int)HttpStatusCode.ServiceUnavailable)] - [ProducesResponseType(typeof(ProblemDetails), (int)HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(ProblemDetails), (int)HttpStatusCode.Forbidden)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.ServiceUnavailable)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(Neuroglia.ProblemDetails), (int)HttpStatusCode.Forbidden)] public virtual async Task PublishCloudEvent([FromBody] CloudEvent e, CancellationToken cancellationToken) { if (!this.ModelState.IsValid) return this.ValidationProblem(this.ModelState); - return this.Process(await this.Mediator.Send(new ConsumeEventCommand(e), cancellationToken).ConfigureAwait(false)); + return this.Process(await mediator.ExecuteAsync(new ConsumeEventCommand(e), cancellationToken).ConfigureAwait(false), (int)HttpStatusCode.Accepted); } } diff --git a/src/gateway/CloudStreams.Gateway.Api/Dockerfile b/src/gateway/CloudStreams.Gateway.Api/Dockerfile new file mode 100644 index 00000000..35edcddd --- /dev/null +++ b/src/gateway/CloudStreams.Gateway.Api/Dockerfile @@ -0,0 +1,29 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 80 +USER root +RUN apt-get update +RUN apt-get install -y jq + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj", "src/gateway/CloudStreams.Gateway.Api/"] +COPY ["src/core/CloudStreams.Core.Api/CloudStreams.Core.Api.csproj", "src/core/CloudStreams.Core.Api/"] +COPY ["src/core/CloudStreams.Core.Application/CloudStreams.Core.Application.csproj", "src/core/CloudStreams.Core.Application/"] +COPY ["src/core/CloudStreams.Core/CloudStreams.Core.csproj", "src/core/CloudStreams.Core/"] +COPY ["src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj", "src/gateway/CloudStreams.Gateway.Application/"] +RUN dotnet restore "./src/gateway/CloudStreams.Gateway.Api/CloudStreams.Gateway.Api.csproj" +COPY . . +WORKDIR "/src/src/gateway/CloudStreams.Gateway.Api" +RUN dotnet build "./CloudStreams.Gateway.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./CloudStreams.Gateway.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "CloudStreams.Gateway.Api.dll"] \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api/Hubs/CloudEventHub.cs b/src/gateway/CloudStreams.Gateway.Api/Hubs/CloudEventHub.cs index deb1d06f..d1157ae2 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Hubs/CloudEventHub.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Hubs/CloudEventHub.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Gateway.Api.Client.Services; using Microsoft.AspNetCore.SignalR; namespace CloudStreams.Gateway.Api.Hubs; @@ -19,7 +18,6 @@ namespace CloudStreams.Gateway.Api.Hubs; /// /// Represents the used to observe s /// -[Route("api/gateway/v1/ws/cloud-events")] public class CloudEventHub : Hub, ICloudEventHub { diff --git a/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHub.cs b/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHub.cs index 66495596..7ddda7f9 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHub.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHub.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventHubClient.cs b/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHubClient.cs similarity index 84% rename from src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventHubClient.cs rename to src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHubClient.cs index 54b87e98..9b032db8 100644 --- a/src/gateway/CloudStreams.Gateway.Api.Client/Services/Interfaces/ICloudEventHubClient.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Hubs/ICloudEventHubClient.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; - -namespace CloudStreams.Gateway.Api.Client.Services; +namespace CloudStreams.Gateway.Api.Hubs; /// /// Defines the fundamentals of a service used by clients to observe ingested cloud events @@ -22,11 +20,11 @@ public interface ICloudEventHubClient { /// - /// Notifies clients about the succesfull ingestion of any and all + /// Notifies clients about the successful ingestion of any and all /// /// The that has been ingested /// A /// A new awaitable Task StreamEvent(CloudEvent e, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api/Program.cs b/src/gateway/CloudStreams.Gateway.Api/Program.cs new file mode 100644 index 00000000..f49b8763 --- /dev/null +++ b/src/gateway/CloudStreams.Gateway.Api/Program.cs @@ -0,0 +1,76 @@ +// Copyright © 2024-Present The Cloud Streams Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"), +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using CloudStreams.Core; +using CloudStreams.Core.Api; +using CloudStreams.Core.Api.Hubs; +using CloudStreams.Core.Application.Services; +using CloudStreams.Gateway.Api.Hubs; +using CloudStreams.Gateway.Api.Services; +using CloudStreams.Gateway.Application.Commands.CloudEvents; +using CloudStreams.Gateway.Application.Configuration; +using CloudStreams.Gateway.Application.Services; +using CloudStreams.Gateway.Controllers; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Swashbuckle.AspNetCore.SwaggerUI; + +CloudStreamsDefaults.Telemetry.ActivitySource = new("Cloud Streams Gateway"); + +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddEnvironmentVariables(GatewayOptions.EnvironmentVariablePrefix); +builder.UseCloudStreams(builder => +{ + builder.UseCoreApi(); + builder.RegisterApplicationPart(); + builder.RegisterMediationAssembly(); +}); + +builder.Services.Configure(builder.Configuration); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(provider => provider.GetRequiredService()); +builder.Services.AddSingleton(provider => provider.GetRequiredService()); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +using var app = builder.Build(); + +app.UseResponseCompression(); +app.UseBlazorFrameworkFiles(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthorization(); +app.UseHealthChecks("/healthz", new HealthCheckOptions() +{ + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); +app.UseSwagger(builder => +{ + builder.RouteTemplate = "api/{documentName}/doc/oas.{json|yaml}"; +}); +app.UseSwaggerUI(builder => +{ + builder.DocExpansion(DocExpansion.None); + builder.SwaggerEndpoint("/api/v1/doc/oas.json", "Cloud Streams Gateway API v1"); + builder.RoutePrefix = "api/doc"; + builder.DisplayOperationId(); +}); +app.MapControllers(); +app.MapHub("api/ws/resources/watch"); +app.MapHub("api/ws/events"); +app.MapFallbackToFile("index.html"); + +await app.RunAsync(); \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api/Properties/launchSettings.json b/src/gateway/CloudStreams.Gateway.Api/Properties/launchSettings.json index 3873f590..750a05e1 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Properties/launchSettings.json +++ b/src/gateway/CloudStreams.Gateway.Api/Properties/launchSettings.json @@ -1,12 +1,39 @@ { "profiles": { - "CloudStreams.Gateway.Api": { + "http": { "commandName": "Project", "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "CLOUDSTREAMS_GATEWAY_NAME": "gateway-1" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5139" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "8080" }, - "applicationUrl": "https://localhost:52337" + "publishAllPorts": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56004", + "sslPort": 0 } } } \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Api/Services/CloudEventHubDispatcher.cs b/src/gateway/CloudStreams.Gateway.Api/Services/CloudEventHubDispatcher.cs index 393cf693..bd98032f 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Services/CloudEventHubDispatcher.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Services/CloudEventHubDispatcher.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Infrastructure.Services; -using CloudStreams.Gateway.Api.Client.Services; +using CloudStreams.Core.Application; +using CloudStreams.Core.Application.Services; using CloudStreams.Gateway.Api.Hubs; -using Hylo; using Microsoft.AspNetCore.SignalR; +using Neuroglia.Data.Infrastructure.EventSourcing; +using Neuroglia.Reactive; using System.Reactive.Linq; namespace CloudStreams.Gateway.Api.Services; @@ -23,37 +24,57 @@ namespace CloudStreams.Gateway.Api.Services; /// /// Represents a service used to dispatch ingested cloud events to all s /// -public class CloudEventHubDispatcher +/// +/// Initializes a new +/// +/// The current +/// The service used to perform logging +/// The current 's +public class CloudEventHubDispatcher(IServiceProvider serviceProvider, ILogger logger, IHubContext hubContext) : BackgroundService { /// - /// Initializes a new + /// Gets the current /// - /// The service used to provide an implementation - /// The current 's - public CloudEventHubDispatcher(IEventStoreProvider eventStoreProvider, IHubContext hubContext) - { - this.EventStoreProvider = eventStoreProvider; - this.HubContext = hubContext; - } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; + + /// + /// Gets the service used to perform logging + /// + protected ILogger Logger { get; } = logger; /// - /// Gets the service used to provide an implementation + /// Gets the service used to source s /// - protected IEventStoreProvider EventStoreProvider { get; } + protected ICloudEventStore EventStore => this.ServiceProvider.GetRequiredService(); /// /// Gets the current 's /// - protected IHubContext HubContext { get; } + protected IHubContext HubContext { get; } = hubContext; /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - (await this.EventStoreProvider.GetEventStore().SubscribeAsync(cancellationToken: stoppingToken).ConfigureAwait(false)) - .Select(e => e.ToCloudEvent(default)) - .SubscribeAsync(e => this.HubContext.Clients.All.StreamEvent(e, stoppingToken), cancellationToken: stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + try + { + (await this.EventStore + .ObserveAsync(cancellationToken: stoppingToken)) + .Select(e => e.ToCloudEvent(default)) + .SubscribeAsync(e => this.HubContext.Clients.All.StreamEvent(e, stoppingToken), cancellationToken: stoppingToken); + break; + } + catch (StreamNotFoundException) + { + var delay = 5000; + this.Logger.LogDebug("Failed to observe the cloud event stream because the first cloud event is yet to be published. Retrying in {delay} milliseconds...", delay); + await Task.Delay(delay, stoppingToken).ConfigureAwait(false); + } + } + } } diff --git a/src/gateway/CloudStreams.Gateway.Api/Usings.cs b/src/gateway/CloudStreams.Gateway.Api/Usings.cs index 8f790459..2cca8957 100644 --- a/src/gateway/CloudStreams.Gateway.Api/Usings.cs +++ b/src/gateway/CloudStreams.Gateway.Api/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using CloudStreams.Core; -global using CloudStreams.Core.Data; -global using MediatR; -global using Microsoft.AspNetCore.Mvc; +global using Neuroglia; +global using Neuroglia.Eventing.CloudEvents; global using System.Net; +global using System.Text.RegularExpressions; diff --git a/src/gateway/CloudStreams.Gateway.Api/appsettings.Development.json b/src/gateway/CloudStreams.Gateway.Api/appsettings.Development.json new file mode 100644 index 00000000..6b06496b --- /dev/null +++ b/src/gateway/CloudStreams.Gateway.Api/appsettings.Development.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "eventstore": "esdb://localhost:2113?tls=false&tlsVerifyCert=false", + "redis": "localhost:6379" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/gateway/CloudStreams.Gateway.Api/appsettings.json b/src/gateway/CloudStreams.Gateway.Api/appsettings.json new file mode 100644 index 00000000..7b9ce52f --- /dev/null +++ b/src/gateway/CloudStreams.Gateway.Api/appsettings.json @@ -0,0 +1,44 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Plugins": { + "Sources": [ + { + "name": "event-store", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.EventSourcing.EventStore" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IEventStore, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + }, + { + "implements": "Neuroglia.Data.Infrastructure.EventSourcing.Services.IProjectionManager, Neuroglia.Data.Infrastructure.EventSourcing.Abstractions" + } + ] + } + }, + { + "name": "resource-database", + "type": "nuget", + "properties": { + "packageId": "Neuroglia.Data.Infrastructure.ResourceOriented.Redis" + }, + "filter": { + "criteria": [ + { + "implements": "Neuroglia.Data.Infrastructure.ResourceOriented.Services.IDatabase, Neuroglia.Data.Infrastructure.ResourceOriented.Abstractions" + } + ] + } + } + ] + }, + "AllowedHosts": "*" +} diff --git a/src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj b/src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj index f1b92784..1b47e38e 100644 --- a/src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj +++ b/src/gateway/CloudStreams.Gateway.Application/CloudStreams.Gateway.Application.csproj @@ -1,38 +1,26 @@ - + - net7.0 - Library - enable + net8.0 + enable enable - True 0.14.0 $(VersionPrefix) $(VersionPrefix) en + true + True + Apache-2.0 + Copyright © 2023-Present The Cloud Streams Authors. All rights reserved. https://github.com/neuroglia-io/cloud-streams - README.md https://github.com/neuroglia-io/cloud-streams git - Copyright © 2023 - Present The Cloud Streams Authors. All rights reserverd - cloudstreams;gateway;application - Contains the application services, commands and queries used by CloudStreams Gateways - logo.png - Apache-2.0 - True - True - True + true - - \ - True - - - \ - True - + + diff --git a/src/gateway/CloudStreams.Gateway.Application/Commands/Events/ConsumeEventCommand.cs b/src/gateway/CloudStreams.Gateway.Application/Commands/Events/ConsumeEventCommand.cs index bae9ab3b..1c31a8e9 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Commands/Events/ConsumeEventCommand.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Commands/Events/ConsumeEventCommand.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,33 +11,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure; -using CloudStreams.Core.Infrastructure.Services; +using CloudStreams.Core.Application.Services; using CloudStreams.Gateway.Application.Services; -using Hylo.Api.Application; +using Neuroglia.Mediation; using System.ComponentModel.DataAnnotations; namespace CloudStreams.Gateway.Application.Commands.CloudEvents; /// -/// Represents the used to consume an incoming s +/// Represents the used to consume an incoming s /// public class ConsumeEventCommand - : ICommand + : Command { /// /// Initializes a new /// - /// The to consume + protected ConsumeEventCommand() { this.CloudEvent = null!; } + + /// + /// Initializes a new + /// + /// The to consume public ConsumeEventCommand(CloudEvent cloudEvent) { this.CloudEvent = cloudEvent; } - + /// - /// Gets the to consume + /// Gets the to consume /// [Required] public CloudEvent CloudEvent { get; } @@ -47,31 +50,20 @@ public ConsumeEventCommand(CloudEvent cloudEvent) /// /// Represents the service used to handle s /// -public class ConsumeCloudEventCommandHandler - : ICommandHandler +/// +public class ConsumeCloudEventCommandHandler(ICloudEventAdmissionControl eventAdmissionControl, IGatewayMetrics metrics, ICloudEventStore eventStore) + : ICommandHandler { - readonly ICloudEventAdmissionControl _EventAdmissionControl; - readonly IGatewayMetrics _Metrics; - readonly IEventStoreProvider _EventStoreProvider; - - /// - public ConsumeCloudEventCommandHandler(ICloudEventAdmissionControl eventAdmissionControl, IGatewayMetrics metrics, IEventStoreProvider eventStoreProvider) - { - this._EventAdmissionControl = eventAdmissionControl; - this._Metrics = metrics; - this._EventStoreProvider = eventStoreProvider; - } - /// - public async Task Handle(ConsumeEventCommand command, CancellationToken cancellationToken) + public async Task HandleAsync(ConsumeEventCommand command, CancellationToken cancellationToken) { var e = command.CloudEvent; - var admissionResult = await this._EventAdmissionControl.EvaluateAsync(e, cancellationToken).ConfigureAwait(false); - if (!admissionResult.IsSuccessStatusCode()) return new ApiResponse(admissionResult.Type!, admissionResult.Title!, admissionResult.Status, admissionResult.Detail, admissionResult.Instance, admissionResult.Errors?.ToDictionary(e => e.Key, e => e.Value), admissionResult.ExtensionData); - await this._EventStoreProvider.GetEventStore().AppendAsync(admissionResult.Content!, cancellationToken).ConfigureAwait(false); - this._Metrics.IncrementTotalIngestedEvents(); - return ApiResponse.Accepted(); + var admissionResult = await eventAdmissionControl.EvaluateAsync(e, cancellationToken).ConfigureAwait(false); + if (admissionResult.Data == null || !admissionResult.IsSuccess()) return admissionResult; + await eventStore.AppendAsync(admissionResult.Data, cancellationToken).ConfigureAwait(false); + metrics.IncrementTotalIngestedEvents(); + return new OperationResult((int)HttpStatusCode.Accepted); } } diff --git a/src/gateway/CloudStreams.Gateway.Application/Configuration/GatewayOptions.cs b/src/gateway/CloudStreams.Gateway.Application/Configuration/GatewayOptions.cs index 94e5dcda..abe29561 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Configuration/GatewayOptions.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Configuration/GatewayOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. diff --git a/src/core/CloudStreams.Core.Infrastructure/JsonSchemaGenerationOptions.cs b/src/gateway/CloudStreams.Gateway.Application/Configuration/JsonSchemaGenerationOptions.cs similarity index 88% rename from src/core/CloudStreams.Core.Infrastructure/JsonSchemaGenerationOptions.cs rename to src/gateway/CloudStreams.Gateway.Application/Configuration/JsonSchemaGenerationOptions.cs index af6e53b3..d2832e80 100644 --- a/src/core/CloudStreams.Core.Infrastructure/JsonSchemaGenerationOptions.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Configuration/JsonSchemaGenerationOptions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,7 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Infrastructure; +using Json.Schema; + +namespace CloudStreams.Gateway.Application.Configuration; /// /// Represents an object used to configure how to generate s @@ -29,4 +31,4 @@ public class JsonSchemaGenerationOptions /// public virtual string? Title { get; set; } -} \ No newline at end of file +} diff --git a/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAdmissionControl.cs b/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAdmissionControl.cs index 1fcb1062..8d1af251 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAdmissionControl.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAdmissionControl.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -12,89 +12,80 @@ // limitations under the License. using CloudStreams.Core; -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure; -using CloudStreams.Core.Infrastructure.Services; +using CloudStreams.Core.Application.Services; +using CloudStreams.Core.Resources; using CloudStreams.Gateway.Application.Configuration; using FluentValidation; -using Hylo; -using Hylo.Infrastructure.Services; -using Hylo.Properties; using Json.Schema; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Net; -using System.Text.RegularExpressions; +using Neuroglia.Data.Expressions.Services; +using Neuroglia.Data.Infrastructure.ResourceOriented; +using Neuroglia.Data.Infrastructure.ResourceOriented.Services; +using Neuroglia.Serialization; namespace CloudStreams.Gateway.Application.Services; /// /// Represents the default implementation of the interface /// -public class CloudEventAdmissionControl - : BackgroundService, ICloudEventAdmissionControl, IDisposable +/// +/// Initializes a new +/// +/// The current +/// The service used to perform logging +/// The service used to access the current +/// The service used to serialize/deserialize objects to/from JSON +/// The service used to manage Cloud Streams gateway related metrics +/// The service used to manage authorization +/// The service used to generate s +/// The service used to register and manage s +/// The service used to evaluate runtime expressions +/// An containing the s used to validate s +public class CloudEventAdmissionControl(IServiceProvider serviceProvider, ILogger logger, IOptions gatewayOptions, IJsonSerializer serializer, IGatewayMetrics metrics, ICloudEventAuthorizationManager authorizationManager, + IJsonSchemaGenerator schemaGenerator, IJsonSchemaRegistry schemaRegistry, IExpressionEvaluator expressionEvaluator, IEnumerable> validators) + : BackgroundService, ICloudEventAdmissionControl, IDisposable { - readonly IGatewayMetrics _Metrics; - bool _Disposed; - - /// - /// Initializes a new - /// - /// The current - /// The service used to create s - /// The service used to access the current - /// The service used to manage Cloud Streams gateway related metrics - /// The service used to manage authorization - /// The service used to generate s - /// The service used to provide an implementation - /// The service used to provide an implementation - /// An containing the s used to validate s - public CloudEventAdmissionControl(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IOptions gatewayOptions, IGatewayMetrics metrics, IAuthorizationManager authorizationManager, - ISchemaGenerator schemaGenerator, ISchemaRegistryProvider schemaRegistryProvider, IExpressionEvaluatorProvider expressionEvaluatorProvider, IEnumerable> validators) - { - this.ServiceProvider = serviceProvider; - this.Logger = loggerFactory.CreateLogger(this.GetType()); - this.GatewayOptions = gatewayOptions.Value; - this._Metrics = metrics; - this.AuthorizationManager = authorizationManager; - this.SchemaGenerator = schemaGenerator; - this.SchemaRegistryProvider = schemaRegistryProvider; - this.ExpressionEvaluatorProvider = expressionEvaluatorProvider; - this.Validators = validators; - } + readonly IGatewayMetrics _metrics = metrics; + bool _disposed; /// /// Gets the current /// - protected IServiceProvider ServiceProvider { get; } + protected IServiceProvider ServiceProvider { get; } = serviceProvider; /// /// Gets the service used to perform logging /// - protected ILogger Logger { get; } + protected ILogger Logger { get; } = logger; /// /// gets the object used to configure a /// - protected GatewayOptions GatewayOptions { get; } + protected GatewayOptions GatewayOptions { get; } = gatewayOptions.Value; + + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + protected IJsonSerializer Serializer { get; } = serializer; /// /// Gets the service used to manage authorization /// - protected IAuthorizationManager AuthorizationManager { get; } + protected ICloudEventAuthorizationManager AuthorizationManager { get; } = authorizationManager; /// /// Gets the service used to generate s /// - protected ISchemaGenerator SchemaGenerator { get; } + protected IJsonSchemaGenerator SchemaGenerator { get; } = schemaGenerator; /// - /// Gets the service used to provide an implementation + /// Gets the service used to register and manage s /// - protected ISchemaRegistryProvider SchemaRegistryProvider { get; } + protected IJsonSchemaRegistry SchemaRegistry { get; } = schemaRegistry; /// /// Gets the service used to manage s @@ -102,33 +93,33 @@ public CloudEventAdmissionControl(IServiceProvider serviceProvider, ILoggerFacto protected IRepository Resources => this.ServiceProvider.GetRequiredService(); /// - /// Gets the service used to provide an implementation + /// Gets the service used to evaluate runtime expressions /// - protected IExpressionEvaluatorProvider ExpressionEvaluatorProvider { get; } + protected IExpressionEvaluator ExpressionEvaluator { get; } = expressionEvaluator; /// /// Gets an containing the s used to validate s /// - protected IEnumerable> Validators { get; } + protected IEnumerable> Validators { get; } = validators; /// - /// Gets the service used to manage the state of the current + /// Gets the service used to manage the state of the current /// - protected IResourceMonitor? Configuration { get; private set; } + protected IResourceMonitor? Configuration { get; private set; } /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - Core.Data.Gateway? gateway = null; + Core.Resources.Gateway? gateway = null; try { - gateway = await this.Resources.GetAsync(this.GatewayOptions.Name, this.GatewayOptions.Namespace, stoppingToken).ConfigureAwait(false); + gateway = await this.Resources.GetAsync(this.GatewayOptions.Name, this.GatewayOptions.Namespace, stoppingToken).ConfigureAwait(false); } - catch (HyloException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { } + catch (ProblemDetailsException ex) when (ex.Problem.Status == (int)HttpStatusCode.NotFound) { } finally { - if (gateway == null) await this.Resources.AddAsync(new Core.Data.Gateway(new ResourceMetadata(this.GatewayOptions.Name, this.GatewayOptions.Namespace), new GatewaySpec()), false, stoppingToken).ConfigureAwait(false); - this.Configuration = await this.Resources.MonitorAsync(this.GatewayOptions.Name, this.GatewayOptions.Namespace, false, stoppingToken).ConfigureAwait(false); + if (gateway == null) await this.Resources.AddAsync(new Core.Resources.Gateway(new ResourceMetadata(this.GatewayOptions.Name, this.GatewayOptions.Namespace), new GatewaySpec()), false, stoppingToken).ConfigureAwait(false); + this.Configuration = await this.Resources.MonitorAsync(this.GatewayOptions.Name, this.GatewayOptions.Namespace, false, stoppingToken).ConfigureAwait(false); } } @@ -136,40 +127,35 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) public virtual async Task> EvaluateAsync(CloudEvent e, CancellationToken cancellationToken = default) { var validationResults = new List(this.Validators.Count()); - foreach (var validator in this.Validators) + foreach (var validator in this.Validators) validationResults.Add(await validator.ValidateAsync(e, cancellationToken).ConfigureAwait(false)); + if (!validationResults.All(r => r.IsValid)) { - validationResults.Add(await validator.ValidateAsync(e, cancellationToken).ConfigureAwait(false)); - } - if (!validationResults.All(r => r.IsValid)) return new() - { - Status = (int)HttpStatusCode.BadRequest, - Title = ProblemTitles.ValidationFailed, - Errors = new(validationResults + return new((int)HttpStatusCode.BadRequest, errors: [new Error(ProblemTypes.SchemaValidationFailed, "Invalid", (int)HttpStatusCode.BadRequest, errors: validationResults .Where(r => !r.IsValid) .SelectMany(r => r.Errors) .GroupBy(e => e.PropertyName) - .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray())) - }; + .Select(g => new KeyValuePair(g.Key, g.Select(e => e.ErrorMessage).ToArray())))]); + } this.Logger.LogDebug("Started admission evaluation for cloud event with id '{eventId}'...", e.Id); var result = await this.AuthorizeAsync(e, cancellationToken).ConfigureAwait(false); - if (!result.IsSuccessStatusCode()) + if (!result.IsSuccess()) { - this.Logger.LogDebug("Admission evaluation failed with status code '{statusCode}' for cloud event with id '{eventId}': {detail}", result.Status, e.Id, result.Detail); - this._Metrics.IncrementTotalRejectedEvents(); + this.Logger.LogDebug("Admission evaluation failed with status code '{statusCode}' for cloud event with id '{eventId}'", result.Status, e.Id); + this._metrics.IncrementTotalRejectedEvents(); return result.OfType(); } result = await this.ValidateAsync(e, cancellationToken).ConfigureAwait(false); - if (!result.IsSuccessStatusCode()) + if (!result.IsSuccess()) { - this.Logger.LogDebug("Admission evaluation failed with status code '{statusCode}' for cloud event with id '{eventId}': {detail}", result.Status, e.Id, result.Detail); - this._Metrics.IncrementTotalInvalidEvents(); + this.Logger.LogDebug("Admission evaluation failed with status code '{statusCode}' for cloud event with id '{eventId}'", result.Status, e.Id); + this._metrics.IncrementTotalInvalidEvents(); return result.OfType(); } this.Logger.LogDebug("Admission evaluation for cloud event with id '{eventId}' completed successfully", e.Id); this.Logger.LogDebug("Extracting metadata from the cloud event with id '{eventId}'", e.Id); var metadata = await this.ExtractMetadataAsync(e, cancellationToken).ConfigureAwait(false); this.Logger.LogDebug("Metadata successfully extracted from the cloud event with id '{eventId}'", e.Id); - return OperationResult.Ok(new CloudEventDescriptor(metadata, e.Data)); + return new((int)HttpStatusCode.OK, new CloudEventDescriptor(metadata, e.Data)); } /// @@ -180,18 +166,18 @@ public virtual async Task> EvaluateAsync(C /// A that describes the result of the operation protected virtual async Task AuthorizeAsync(CloudEvent e, CancellationToken cancellationToken = default) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); this.Logger.LogDebug("Authorizing cloud event with id '{eventId}'...", e.Id); var policy = this.Configuration?.Resource.Spec.Sources?.FirstOrDefault(s => s.Uri == e.Source)?.Authorization ?? this.Configuration?.Resource.Spec.Authorization; - if (policy == null) return OperationResult.Ok(); + if (policy == null) return new((int)HttpStatusCode.OK); var result = await this.AuthorizationManager.EvaluateAsync(e, policy, cancellationToken).ConfigureAwait(false); - if (!result.IsSuccessStatusCode()) + if (!result.IsSuccess()) { - this.Logger.LogDebug("Authorization denied for cloud event with id '{eventId}': {detail}", e.Id, result.Detail); + this.Logger.LogDebug("Authorization denied for cloud event with id '{eventId}'", e.Id); return result; } this.Logger.LogDebug("Authorization granted for cloud event with id '{eventId}' completed successfully", e.Id); - return OperationResult.Ok(); + return new((int)HttpStatusCode.OK); } /// @@ -202,14 +188,14 @@ protected virtual async Task AuthorizeAsync(CloudEvent e, Cance /// A that describes the result of the operation protected virtual async Task ValidateAsync(CloudEvent e, CancellationToken cancellationToken = default) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); this.Logger.LogDebug("Validating cloud event with id '{eventId}'...", e.Id); var policy = this.Configuration?.Resource.Spec.Sources?.FirstOrDefault(s => s.Uri == e.Source)?.Validation ?? this.Configuration?.Resource.Spec.Validation; - if (policy == null) return OperationResult.Ok(); + if (policy == null) return new((int)HttpStatusCode.OK); if (policy.SkipValidation) { this.Logger.LogDebug("Skipping validation of cloud event with id '{eventId}': the validation policy for source '{sourceUri}' is configured to skip validation", e.Id, e.Source); - return OperationResult.Ok(); + return new((int)HttpStatusCode.OK); } var dataSchemaPolicy = policy.DataSchema; JsonSchema? schema = null; @@ -219,42 +205,35 @@ protected virtual async Task ValidateAsync(CloudEvent e, Cancel if (dataSchemaPolicy?.Required == true) { this.Logger.LogDebug("Validation of cloud event with id '{eventId}' failed: the validation policy for source '{sourceUri}' requires the cloud event's 'dataSchema' attribute to be set", e.Id, e.Source); - return OperationResult.ValidationFailed(StringExtensions.Format(Core.Properties.ProblemDetails.MissingDataSchema, e.Source!)); + return new((int)HttpStatusCode.BadRequest); } - var schemaUri = await this.SchemaRegistryProvider.GetSchemaRegistry().GetSchemaUriByIdAsync(e.Type, cancellationToken).ConfigureAwait(false); - if (schemaUri != null) + if (dataSchemaPolicy?.AutoGenerate == true && e.Data != null) { - schema = await this.SchemaRegistryProvider.GetSchemaRegistry().GetSchemaAsync(schemaUri, cancellationToken).ConfigureAwait(false); - e.DataSchema = schemaUri; - } - else if (dataSchemaPolicy?.AutoGenerate == true && e.Data != null) - { - schema = await this.SchemaGenerator.GenerateAsync(e.Data, new() { Id = e.Type }, cancellationToken).ConfigureAwait(false); - schemaUri = await this.SchemaRegistryProvider.GetSchemaRegistry().RegisterSchemaAsync(schema!, cancellationToken).ConfigureAwait(false); - e.DataSchema = schemaUri; + schema = (await this.SchemaGenerator.GenerateAsync(e.Data, new() { Id = e.Type }, cancellationToken).ConfigureAwait(false))!; + e.DataSchema = schema.BaseUri; } } else { - schema = await this.SchemaRegistryProvider.GetSchemaRegistry().GetSchemaAsync(e.DataSchema, cancellationToken).ConfigureAwait(false); + schema = await this.SchemaRegistry.GetAsync(e.DataSchema, cancellationToken).ConfigureAwait(false); if (schema == null) { this.Logger.LogDebug("Validation of cloud event with id '{eventId}' failed: failed to find the specified data schema '{dataSchemaUri}'", e.Id, e.DataSchema); - return OperationResult.ValidationFailed(StringExtensions.Format(Core.Properties.ProblemDetails.DataSchemaNotFound, e.DataSchema)); + return new((int)HttpStatusCode.BadRequest); } } if (schema != null) { var validationOptions = new EvaluationOptions() { OutputFormat = OutputFormat.Hierarchical }; - var validationResults = schema.Evaluate(Serializer.Json.SerializeToNode(e.Data), validationOptions); + var validationResults = schema.Evaluate(this.Serializer.SerializeToNode(e.Data), validationOptions); if (!validationResults.IsValid) { this.Logger.LogDebug("Validation of cloud event with id '{eventId}' failed: {detail}", e.Id, validationResults); - return OperationResult.ValidationFailed(validationResults); + return new((int)HttpStatusCode.BadRequest, null, [new Error(ProblemTypes.SchemaValidationFailed, "Invalid", (int)HttpStatusCode.BadRequest, errors: validationResults.Errors?.GroupBy(kvp => kvp.Key).Select(g => new KeyValuePair(g.Key, g.Select(kvp => kvp.Value).ToArray())))]); } } this.Logger.LogDebug("Cloud event with id '{eventId}' successfully validated", e.Id); - return OperationResult.Ok(); + return new((int)HttpStatusCode.OK); } /// @@ -265,7 +244,7 @@ protected virtual async Task ValidateAsync(CloudEvent e, Cancel /// The metadata extracted from the specified protected virtual async Task ExtractMetadataAsync(CloudEvent e, CancellationToken cancellationToken = default) { - if (e == null) throw new ArgumentNullException(nameof(e)); + ArgumentNullException.ThrowIfNull(e); if (!e.Time.HasValue) e.Time = DateTimeOffset.Now; var metadata = new CloudEventMetadata(e.GetContextAttributes().ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); var ingestionConfiguration = this.Configuration?.Resource.Spec.Events?.LastOrDefault(c => c.AppliesTo(e)); @@ -293,7 +272,7 @@ protected virtual async Task ExtractMetadataAsync(CloudEvent break; case CloudEventMetadataPropertyResolutionStrategy.Expression: if (string.IsNullOrWhiteSpace(property.Expression)) throw new NullReferenceException($"The '{nameof(property.Expression)}' property cannot be null when the metadata property resolution strategy has been set to '{EnumHelper.Stringify(CloudEventMetadataPropertyResolutionStrategy.Expression)}'"); - attributeValue = this.ExpressionEvaluatorProvider.GetExpressionEvaluator().Evaluate(property.Expression, e, cancellationToken: cancellationToken); + attributeValue = await this.ExpressionEvaluator.EvaluateAsync(property.Expression, e, cancellationToken: cancellationToken).ConfigureAwait(false); if (attributeValue != null) metadata.ExtensionData![property.Name] = attributeValue; break; default: @@ -302,7 +281,7 @@ protected virtual async Task ExtractMetadataAsync(CloudEvent } catch (Exception ex) { - this.Logger.LogError("An error occured while extracting the metadata property '{property}' from the cloud event with id '{eventId}': {error}", property.Name, e.Id, ex); + this.Logger.LogError("An error occurred while extracting the metadata property '{property}' from the cloud event with id '{eventId}': {error}", property.Name, e.Id, ex); continue; } } @@ -315,13 +294,10 @@ protected virtual async Task ExtractMetadataAsync(CloudEvent /// A boolean indicating whether or not the is being disposed of protected virtual void Dispose(bool disposing) { - if (!this._Disposed) + if (!this._disposed) { - if (disposing) - { - this.Configuration?.Dispose(); - } - this._Disposed = true; + if (disposing) this.Configuration?.Dispose(); + this._disposed = true; } } diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/AuthorizationManager.cs b/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAuthorizationManager.cs similarity index 72% rename from src/core/CloudStreams.Core.Infrastructure/Services/AuthorizationManager.cs rename to src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAuthorizationManager.cs index 23860a07..4a93b756 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/AuthorizationManager.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/CloudEventAuthorizationManager.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,46 +11,52 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Net; -using System.Text; -using System.Text.RegularExpressions; +using CloudStreams.Core; +using CloudStreams.Core.Resources; +using Neuroglia.Serialization; -namespace CloudStreams.Core.Infrastructure.Services; +namespace CloudStreams.Gateway.Application.Services; /// -/// Represents the default implementation of the interface +/// Represents the default implementation of the interface /// -public class AuthorizationManager - : IAuthorizationManager +/// The service used to serialize/deserialize objects to/from JSON +public class CloudEventAuthorizationManager(IJsonSerializer serializer) + : ICloudEventAuthorizationManager { + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + protected IJsonSerializer Serializer { get; } = serializer; + /// public virtual Task EvaluateAsync(CloudEvent e, CloudEventAuthorizationPolicy policy, CancellationToken cancellationToken = default) { - if (e == null) throw new ArgumentNullException(nameof(e)); - if (policy == null) throw new ArgumentNullException(nameof(policy)); + ArgumentNullException.ThrowIfNull(e); + ArgumentNullException.ThrowIfNull(policy); return Task.Run(() => { - if (policy.Rules == null || !policy.Rules.Any()) return OperationResult.Ok(); + if (policy.Rules == null || policy.Rules.Count == 0) return new OperationResult((int)HttpStatusCode.OK); switch (policy.DecisionStrategy) { case RuleBasedDecisionStrategy.Consensus: var results = policy.Rules.Select(r => this.Evaluate(e, r)); var succeeded = results.Count(r => r); var failed = results.Count(r => !r); - if (succeeded <= failed) return new((int)HttpStatusCode.Forbidden); // TODO: fix me: ApiResponse.Forbidden(); - return OperationResult.Ok(); + if (succeeded <= failed) return new((int)HttpStatusCode.Forbidden); + return new OperationResult((int)HttpStatusCode.OK); case RuleBasedDecisionStrategy.Minority: results = policy.Rules.Select(r => this.Evaluate(e, r)); succeeded = results.Count(r => r); failed = results.Count(r => !r); - if (succeeded <= 0) return new((int)HttpStatusCode.Forbidden); // TODO: fix me: ApiResponse.Forbidden(); - return OperationResult.Ok(); + if (succeeded <= 0) return new((int)HttpStatusCode.Forbidden); + return new OperationResult((int)HttpStatusCode.OK); case RuleBasedDecisionStrategy.Unanimous: - if (!policy.Rules.All(r => this.Evaluate(e, r))) return new((int)HttpStatusCode.Forbidden); // TODO: fix me: ApiResponse.Forbidden(); - return OperationResult.Ok(); + if (!policy.Rules.All(r => this.Evaluate(e, r))) return new((int)HttpStatusCode.Forbidden); + return new OperationResult((int)HttpStatusCode.OK); default: - return new((int)HttpStatusCode.BadRequest); // TODO: fix me: ApiResponse.ValidationFailed($"The specified {nameof(RuleBasedDecisionStrategy)} '{policy.DecisionStrategy}' is not supported"); + return new OperationResult((int)HttpStatusCode.BadRequest); } }); } @@ -64,8 +70,8 @@ public virtual Task EvaluateAsync(CloudEvent e, CloudEventAutho /// protected virtual bool Evaluate(CloudEvent e, CloudEventAuthorizationRule rule) { - if (e == null) throw new ArgumentNullException(nameof(e)); - if (rule == null) throw new ArgumentNullException(nameof(rule)); + ArgumentNullException.ThrowIfNull(e); + ArgumentNullException.ThrowIfNull(rule); var match = rule.Effect switch { AuthorizationPolicyEffect.Forbid => false, @@ -80,7 +86,7 @@ protected virtual bool Evaluate(CloudEvent e, CloudEventAuthorizationRule rule) if (!string.IsNullOrWhiteSpace(rule.AttributeValue) && !Regex.IsMatch(value.ToString()!, rule.AttributeValue)) return mismatch; break; case CloudEventAuthorizationRuleType.Payload: - var payloadSize = Encoding.UTF8.GetBytes(Hylo.Serializer.Json.Serialize(e.Data)).Length; + var payloadSize = this.Serializer.SerializeToByteArray(e.Data)!.Length; if (payloadSize > rule.MaxSize) return mismatch; break; case CloudEventAuthorizationRuleType.Temporary: @@ -97,4 +103,4 @@ protected virtual bool Evaluate(CloudEvent e, CloudEventAuthorizationRule rule) return match; } -} \ No newline at end of file +} diff --git a/src/gateway/CloudStreams.Gateway.Application/Services/GatewayMetrics.cs b/src/gateway/CloudStreams.Gateway.Application/Services/GatewayMetrics.cs index 62780480..b98c19e3 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Services/GatewayMetrics.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/GatewayMetrics.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,9 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; +using CloudStreams.Core; using System.Diagnostics.Metrics; -using CloudStreams.Core.Application; namespace CloudStreams.Gateway.Application.Services; @@ -24,10 +23,10 @@ public class GatewayMetrics : IGatewayMetrics { - private const string MetricsPrefix = "cloud_streams_gateway_"; - private const string CloudEventMetricsPrefix = MetricsPrefix + "events_"; + const string MetricsPrefix = "cloud_streams_gateway_"; + const string CloudEventMetricsPrefix = MetricsPrefix + "events_"; - private bool _Disposed; + bool _disposed; /// /// Initializes a new @@ -42,7 +41,7 @@ public GatewayMetrics() /// /// Gets the /// - protected Meter Meter { get; } = new(Telemetry.ActivitySource.Name, Telemetry.ActivitySource.Version); + protected Meter Meter { get; } = new(CloudStreamsDefaults.Telemetry.ActivitySource.Name, CloudStreamsDefaults.Telemetry.ActivitySource.Version); /// /// Gets the used to keep track of the total amount of ingested s @@ -83,13 +82,13 @@ public virtual void IncrementTotalRejectedEvents() /// A boolean indicating whether or not the is being disposed of protected virtual void Dispose(bool disposing) { - if (!this._Disposed) + if (!this._disposed) { if (disposing) { this.Meter.Dispose(); } - this._Disposed = true; + this._disposed = true; } } diff --git a/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAdmissionControl.cs b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAdmissionControl.cs index 2d09689b..4ecfb083 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAdmissionControl.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAdmissionControl.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,8 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using CloudStreams.Core.Data; -using CloudStreams.Core.Infrastructure; +using CloudStreams.Core; namespace CloudStreams.Gateway.Application.Services; diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IAuthorizationManager.cs b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAuthorizationManager.cs similarity index 86% rename from src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IAuthorizationManager.cs rename to src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAuthorizationManager.cs index 6a40f54d..f20e548a 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/IAuthorizationManager.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/ICloudEventAuthorizationManager.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,12 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Infrastructure.Services; +using CloudStreams.Core.Resources; + +namespace CloudStreams.Gateway.Application.Services; /// /// Defines the fundamentals of a service used to manage authorization /// -public interface IAuthorizationManager +public interface ICloudEventAuthorizationManager { /// @@ -28,4 +30,4 @@ public interface IAuthorizationManager /// A new , used to describe the result of the evaluation Task EvaluateAsync(CloudEvent e, CloudEventAuthorizationPolicy policy, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IGatewayMetrics.cs b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IGatewayMetrics.cs index 02ee14ea..7db4f686 100644 --- a/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IGatewayMetrics.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IGatewayMetrics.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -35,4 +35,4 @@ public interface IGatewayMetrics /// void IncrementTotalIngestedEvents(); -} \ No newline at end of file +} diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaGenerator.cs b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IJsonSchemaGenerator.cs similarity index 84% rename from src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaGenerator.cs rename to src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IJsonSchemaGenerator.cs index 8fc215e8..2577f860 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/Interfaces/ISchemaGenerator.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/Interfaces/IJsonSchemaGenerator.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,12 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace CloudStreams.Core.Infrastructure.Services; +using CloudStreams.Gateway.Application.Configuration; +using Json.Schema; + +namespace CloudStreams.Gateway.Application.Services; /// /// Defines the fundamentals of a service used to generate s /// -public interface ISchemaGenerator +public interface IJsonSchemaGenerator { /// @@ -28,4 +31,4 @@ public interface ISchemaGenerator /// A new Task GenerateAsync(object? graph, JsonSchemaGenerationOptions? options = null, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/core/CloudStreams.Core.Infrastructure/Services/SchemaGenerator.cs b/src/gateway/CloudStreams.Gateway.Application/Services/JsonSchemaGenerator.cs similarity index 85% rename from src/core/CloudStreams.Core.Infrastructure/Services/SchemaGenerator.cs rename to src/gateway/CloudStreams.Gateway.Application/Services/JsonSchemaGenerator.cs index 267596fa..9885901b 100644 --- a/src/core/CloudStreams.Core.Infrastructure/Services/SchemaGenerator.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Services/JsonSchemaGenerator.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,22 +11,31 @@ // See the License for the specific language governing permissions and // limitations under the License. +using CloudStreams.Gateway.Application.Configuration; +using Json.Schema; +using Neuroglia.Serialization; using System.Text.Json.Nodes; -namespace CloudStreams.Core.Infrastructure.Services; +namespace CloudStreams.Gateway.Application.Services; /// -/// Represents the default implementation of the interface +/// Represents the default implementation of the interface /// -public class SchemaGenerator - : ISchemaGenerator +/// The service used to serialize/deserialize objects to/from JSON +public class JsonSchemaGenerator(IJsonSerializer serializer) + : IJsonSchemaGenerator { + /// + /// Gets the service used to serialize/deserialize objects to/from JSON + /// + protected IJsonSerializer Serializer { get; } = serializer; + /// public virtual async Task GenerateAsync(object? graph, JsonSchemaGenerationOptions? options = null, CancellationToken cancellationToken = default) { if (graph == null) return new JsonSchemaBuilder().Type(SchemaValueType.Null).Build(); - if(graph is not JsonNode graphNode) graphNode = Hylo.Serializer.Json.SerializeToNode(graph)!; + if (graph is not JsonNode graphNode) graphNode = this.Serializer.SerializeToNode(graph)!; return graphNode switch { JsonArray jsonArray => await this.GenerateForJsonArrayAsync(jsonArray, options, cancellationToken), @@ -68,7 +77,7 @@ public class SchemaGenerator if (!string.IsNullOrWhiteSpace(options?.Id)) schemaBuilder = schemaBuilder.Id(options.Id); if (!string.IsNullOrWhiteSpace(options?.Title)) schemaBuilder = schemaBuilder.Title(options.Title); var properties = new Dictionary(); - foreach(var jsonProperty in obj) + foreach (var jsonProperty in obj) { var schema = await this.GenerateAsync(jsonProperty.Value!, null, cancellationToken); if (schema == null) continue; diff --git a/src/broker/CloudStreams.Broker.Api/Usings.cs b/src/gateway/CloudStreams.Gateway.Application/Usings.cs similarity index 73% rename from src/broker/CloudStreams.Broker.Api/Usings.cs rename to src/gateway/CloudStreams.Gateway.Application/Usings.cs index 2a8c8fe7..2cca8957 100644 --- a/src/broker/CloudStreams.Broker.Api/Usings.cs +++ b/src/gateway/CloudStreams.Gateway.Application/Usings.cs @@ -1,4 +1,4 @@ -// Copyright © 2023-Present The Cloud Streams Authors +// Copyright © 2024-Present The Cloud Streams Authors // // Licensed under the Apache License, Version 2.0 (the "License"), // you may not use this file except in compliance with the License. @@ -11,4 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -global using CloudStreams.Core.Data; +global using Neuroglia; +global using Neuroglia.Eventing.CloudEvents; +global using System.Net; +global using System.Text.RegularExpressions;