diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a4713c52..decf14ab 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,8 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.27.3", - "commands": [ - "dotnet-csharpier" - ] + "version": "0.29.2", + "commands": ["dotnet-csharpier"] } } -} \ No newline at end of file +} diff --git a/.editorconfig b/.editorconfig index 171ea2b4..b79badb1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,35 @@ [*.cs] indent_style = space indent_size = 4 -max_line_length = 180 \ No newline at end of file +max_line_length = 180 + +# csharpier doesn't indent +csharp_indent_case_contents_when_block = false + +# adopt net9 defaults but soften to none for IDE0007/IDE0008 +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:none + +csharp_style_namespace_declarations = file_scoped + +# silent will remove the warnings but still show the suggestions in IDE + +# prefer braces on all if statements +csharp_prefer_braces = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_style_prefer_primary_constructors = true:silent +csharp_style_expression_bodied_methods = false:silent +dotnet_style_prefer_collection_expression = true:silent +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_as_with_null_check = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_namespace_match_folder = true:silent + +# We enable EnforceCodeStyleInBuild = true to enable IDE0005 (Remove unnecessary usings/imports) +# warnings but we don't want the warnings on XML comments +# Should work on cleaning this up in the future +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CS1570.severity = none +dotnet_diagnostic.CS1573.severity = none +dotnet_diagnostic.CS1587.severity = none \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a4391e3d..f890d9cf 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,10 +12,14 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET 8.0.x + - name: Setup .NET 9.0.x uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + 9.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.vscode/launch.json b/.vscode/launch.json index 18bb28dd..9303fba5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -44,7 +44,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/src/Benchmarks/bin/Debug/net8.0/Benchmarks.dll", + "program": "${workspaceFolder}/src/Benchmarks/bin/Debug/net9.0/Benchmarks.dll", "args": [ "--filter", "*QueryBenchmarks*" diff --git a/CHANGELOG.md b/CHANGELOG.md index 63206d38..c7238f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,32 @@ +# 5.6.0 + +## Changes + +- Added additional framework target `net9.0` +- `ResolveWithService` and `ResolveWithServices` methods are now marked obsolete. Please use `Resolve` in the same way +- #430 - `MapGraphQL` has new parameter `followSpec` which changes the behavior to follow https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md and is `false` by default in v5.x. If set to `true` it will follow the spec, the major change being that if the HTTP request was valid you'll get a `200` status code even if there may be GraphQL errors in the response. _Note this will be the default behavior in version v6._ +- Add `IsNullable` to the `IField` interface to control setting the nullability of the field return type in the schema + +## Fixes + +- #435 - fix `isAny()` in `filter` +- Fix issue selecting fields from an object a service field returns where the GraphQL field name differs from the dotnet property name. + +Example a field that returns a `User` type using a service and you are changing the field names in the schema like below + +```cs +schema.Type().AddField("username", u => u.Name, "Username") +``` + +- #439 - Fix using fields / properties with key words (`null`, `true`, `false`) at the start of the name in the filter expressions +- Related to #430 - Make sure if the schema says a field is not nullable, that it is not returned as `null` when using `null` to exit a mutation or field on error. +- Fix issue where an expression extracted from a service field may clash with a existing field name + # 5.5.3 ## Fixes -- #432 - filter expression was incorrectly using bit-wise OR or OrElse +- #432 - filter expression was incorrectly using bit-wise `Or` instead of `OrElse` # 5.5.2 @@ -362,7 +386,7 @@ schema.ReplaceField("people", ## Breaking Changes -- `EntityGraphQL.AspNet` now targets `net6.0` and `net7.0`, dropping tagets `netcoreapp3.1` or `net5.0`. You can still use the base `EntityGraphQL` library with older targets. +- `EntityGraphQL.AspNet` now targets `net6.0` and `net7.0`, dropping targets `netcoreapp3.1` and `net5.0`. You can still use the base `EntityGraphQL` library with older targets. - Interface `IExposableException` has been removed. Use `SchemaBuilderSchemaOptions.AllowedExceptions` or the new `AllowedExceptionAttribute` to define which exceptions are rendered into the results - #254 - Previously passing `null` for the `ClaimsPrincipal` in `ExecuteRequest()` would skip any authorization checks. All authorization checks are now done regardless of the `ClaimsPrincipal` value. Meaning `null` will fail if there is fields requiring authorization. - `IDirectiveProcessor` interface has changed. See upgrade docs for changes diff --git a/EntityGraphQL.sln b/EntityGraphQL.sln index 6a958b14..20c668a6 100644 --- a/EntityGraphQL.sln +++ b/EntityGraphQL.sln @@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureFunctionApp", "src\exa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityGraphQL.EF.Tests", "src\tests\EntityGraphQL.EF.Tests\EntityGraphQL.EF.Tests.csproj", "{D5087AB2-C96F-42F4-9F34-5C8747846908}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityGraphQL.Tests.Util", "src\tests\EntityGraphQL.Tests.Util\EntityGraphQL.Tests.Util.csproj", "{FBFD1928-D1F3-4975-AD77-C63576572B9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.App", "src\tests\Test.App\Test.App.csproj", "{0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +149,30 @@ Global {D5087AB2-C96F-42F4-9F34-5C8747846908}.Release|x64.Build.0 = Release|Any CPU {D5087AB2-C96F-42F4-9F34-5C8747846908}.Release|x86.ActiveCfg = Release|Any CPU {D5087AB2-C96F-42F4-9F34-5C8747846908}.Release|x86.Build.0 = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|x64.Build.0 = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Debug|x86.Build.0 = Debug|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|Any CPU.Build.0 = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|x64.ActiveCfg = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|x64.Build.0 = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|x86.ActiveCfg = Release|Any CPU + {FBFD1928-D1F3-4975-AD77-C63576572B9B}.Release|x86.Build.0 = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|x64.ActiveCfg = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|x64.Build.0 = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|x86.ActiveCfg = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Debug|x86.Build.0 = Debug|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|Any CPU.Build.0 = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|x64.ActiveCfg = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|x64.Build.0 = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|x86.ActiveCfg = Release|Any CPU + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -161,6 +189,8 @@ Global {55645B0C-88D1-4060-AB5A-C444B36E815A} = {D8AA6BD5-5945-4997-887E-491A25378303} {37B1BF94-489C-4C7E-9A1C-C1A9FC9CC9A4} = {D8AA6BD5-5945-4997-887E-491A25378303} {D5087AB2-C96F-42F4-9F34-5C8747846908} = {5F132C01-AA1F-4241-B90F-BE4B2275B34E} + {FBFD1928-D1F3-4975-AD77-C63576572B9B} = {5F132C01-AA1F-4241-B90F-BE4B2275B34E} + {0A9FF9A3-52C0-4520-BD3A-F35CC67E4569} = {5F132C01-AA1F-4241-B90F-BE4B2275B34E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {74156BCF-DABB-4919-A8AC-90629FB4565E} diff --git a/README.md b/README.md index 572d9ce6..3bc97244 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ _Please explore, give feedback or join the development._ ## Installation -The [EntityGraphQL.AspNet ![Nuget](https://img.shields.io/nuget/dt/EntityGraphQL.AspNet)](https://www.nuget.org/packages/EntityGraphQL.AspNet) package will get you easily set up with ASP.NET. +The [EntityGraphQL.AspNet ![Nuget](https://img.shields.io/nuget/dt/EntityGraphQL.AspNet)](https://www.nuget.org/packages/EntityGraphQL.AspNet) package will get you easily set up with ASP.NET. However the core [EntityGraphQL ![Nuget](https://img.shields.io/nuget/dt/EntityGraphQL)](https://www.nuget.org/packages/EntityGraphQL) package has no ASP.NET dependency. @@ -59,25 +59,18 @@ Here is an example for a ASP.NET. You will also need to install EntityGraphQL.As [![Nuget](https://img.shields.io/nuget/dt/EntityGraphQL.AspNet)](https://www.nuget.org/packages/EntityGraphQL.AspNet) ```c# -public class Startup { - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(opt => opt.UseInMemoryDatabase()); - // This registers a SchemaProvider - services.AddGraphQLSchema(); - } +using EntityGraphQL.AspNet; - public void Configure(IApplicationBuilder app, DemoContext db) - { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - // default to /graphql endpoint - endpoints.MapGraphQL(); - }); - } -} +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Demo")); +builder.Services.AddGraphQLSchema(); + +var app = builder.Build(); + +app.MapGraphQL(); +app.Run(); ``` This sets up 1 end point: diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 95aaa73c..99e933db 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -85,29 +85,29 @@ You will need to install [EntityGraphQL.AspNet](https://www.nuget.org/packages/E ```cs using EntityGraphQL.AspNet; -public class Startup { - public void ConfigureServices(IServiceCollection services) - { - // Again, just an example using EF but you do not have to - services.AddDbContext(opt => opt.UseInMemoryDatabase("Demo")); - // This registers a SchemaProvider and uses reflection to build the schema with default options - services.AddGraphQLSchema(); - } +var builder = WebApplication.CreateBuilder(args); - public void Configure(IApplicationBuilder app, DemoContext db) - { - app.UseRouting(); - app.UseEndpoints(endpoints => - { - // defaults to /graphql endpoint - endpoints.MapGraphQL(); - }); - } -} +builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Demo")); +builder.Services.AddGraphQLSchema(); + +var app = builder.Build(); + +app.MapGraphQL(); + +app.Run(); ``` This sets up a `HTTP` `POST` end point at `/graphql` where the body of the post is expected to be a GraphQL query. You can change the path with the `path` argument in `MapGraphQL()` +:::tip +Version 5.6 introduced an argument to `MapGraphQL` called `followSpec`. When set to true it will follow [this GraphQL over HTTP spec](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md). This will be the default behavior in version 6 so if you are starting a new project we suggest setting it to `true` now. + +```cs +app.MapGraphQL(followSpec: true); +``` + +::: + _You can authorize the route how ever you wish using ASP.NET. See the Authorization section for more details._ You can also expose any endpoint over any protocol you'd like. We'll use HTTP/S for these examples. @@ -116,7 +116,7 @@ You can also expose any endpoint over any protocol you'd like. We'll use HTTP/S When using `MapGraphQL()`, the route is added with the `IEndpointRouteBuilder.MapPost` method. The `.MapPost()` method can be chained with `.RequireAuthorization()` where an array of Policy Names can be passed. The policy names are ANDed together with `.RequireAuthorization()`. -To add one or more security policies when using `MapGraphQL()` you can pass an a configure function that has access to the `IEndpointConventionBuilder` from the created `.MapPost()`. +To add one or more security policies when using `MapGraphQL()` you can pass a configure function that has access to the `IEndpointConventionBuilder` from the created `.MapPost()`. ```cs //in ConfigureServices diff --git a/docs/docs/schema-creation/input-types.md b/docs/docs/schema-creation/input-types.md index caf7094a..c8cc069e 100644 --- a/docs/docs/schema-creation/input-types.md +++ b/docs/docs/schema-creation/input-types.md @@ -28,7 +28,7 @@ public class PeopleMutations } } -[MutationArguments] +[GraphQLArguments] public class AddPersonArgs { public PersonInput PersonInput { get; set; } @@ -60,7 +60,7 @@ public class PeopleMutations } } -[MutationArguments] +[GraphQLArguments] public class AddPersonArgs { public Person PersonInput { get; set; } @@ -130,4 +130,4 @@ POST localhost:5000/graphql } ``` -Input types can be modified using the [OneOf](../directives/schema-directives) Schema Directive to tell clients that only one field should contain a value. \ No newline at end of file +Input types can be modified using the [OneOf](../directives/schema-directives) Schema Directive to tell clients that only one field should contain a value. diff --git a/docs/docs/schema-creation/mutations.md b/docs/docs/schema-creation/mutations.md index 4d3b840c..aaffb229 100644 --- a/docs/docs/schema-creation/mutations.md +++ b/docs/docs/schema-creation/mutations.md @@ -299,7 +299,7 @@ public Expression> AddNewPerson(DemoContext db, } ``` -You may also have mutations where you want to have the same or similar arguments. EntityGraphQL lets you use a MutationArguments class. If a parameter in the method has the `GraphQLArgumentsAttribute` that type will be expanded. The above could be changed to the following. +You may also have mutations where you want to have the same or similar arguments. EntityGraphQL lets you use a class. If a parameter in the method has the `GraphQLArgumentsAttribute` that type will be expanded. The above could be changed to the following. ```cs [GraphQLMutation("Add a new person to the system.")] diff --git a/docs/docs/validation.md b/docs/docs/validation.md index b5981fc8..708df9ef 100644 --- a/docs/docs/validation.md +++ b/docs/docs/validation.md @@ -17,7 +17,7 @@ public Expression> AddNewPerson(DemoContext db, AddPer return (ctx) => ctx.People.First(p => p.Id == person.Id); } -[MutationArguments] +[GraphQLArguments] public class AddPersonArgs { [Required(AllowEmptyStrings = false, ErrorMessage = "Actor Name is required")] @@ -29,11 +29,11 @@ public class AddPersonArgs } ``` -If any of those validations fail, the graph QL result will have errors for each one that failed. If your model validation fails your mutation method _will not be called_. +If any of those validations fail, the GraphQL result will have errors for each one that failed. If your model validation fails your mutation method _will not be called_. Throwing an exception in your mutation will cause the the error to be reported in the GraphQL response. You can also collect multiple error messages instead of throwing an exception on the first error using the `GraphQLValidator` service. -This service needs to be registered in your service provider. You can always implement you own `GraphQLValidator` by implementing the `IGraphQLValidator` interface. +This service needs to be registered in your service provider. You can implement you own `GraphQLValidator` by implementing the `IGraphQLValidator` interface or use the default with `AddGraphQLValidator`. ```cs @@ -47,7 +47,7 @@ services.AddScoped(); public class MovieMutations { [GraphQLMutation] - public Expression> AddActor(MyDbContext db, ActorArgs args, IGraphQLValidator validator) + public Expression>? AddActor(MyDbContext db, ActorArgs args, IGraphQLValidator validator) { if (string.IsNullOrEmpty(args.Name)) validator.AddError("Name argument is required"); @@ -69,7 +69,7 @@ public class MovieMutations ## Query Field Validation -If a query field has arguments - like an Id to search for or a filter string etc. - you may also want to perform validation on the input. You can use the same `[Required]`, `[Range]` & `[StringLength]` attributes on your argument object if you are using typed field arguments. +If a query field has arguments - like an `Id` to search for or a filter string etc. - you may also want to perform validation on the input. You can use the same `[Required]`, `[Range]` & `[StringLength]` attributes on your argument object if you are using typed field arguments. ```cs schema.Query().AddField( @@ -184,7 +184,7 @@ You can use the `ArgumentValidator` attribute to register validator on input arg ```cs // On mutation arguments directly -[MutationArguments] +[GraphQLArguments] [ArgumentValidator(typeof(PersonValidator))] public class PersonArgs { @@ -223,7 +223,7 @@ Errors often can be useful for end users. Other times they are for the developer public class MovieMutations { [GraphQLMutation] - public Expression> AddActor(MyDbContext db, ActorArgs args, IGraphQLValidator validator) + public Expression>? AddActor(MyDbContext db, ActorArgs args, IGraphQLValidator validator) { if (string.IsNullOrEmpty(args.Name)) validator.AddError("Name argument is required", new Dictionary {{"type", 1}}); diff --git a/docs/package-lock.json b/docs/package-lock.json index 77e4ec0c..87da6761 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,50 +8,50 @@ "name": "entity-graphql-docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/preset-classic": "3.5.2", - "@mdx-js/react": "3.0.1", - "prism-react-renderer": "2.3.1", + "@docusaurus/core": "3.7.0", + "@docusaurus/preset-classic": "3.7.0", + "@mdx-js/react": "3.1.0", + "prism-react-renderer": "2.4.1", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.5.2", - "gh-pages": "6.1.1" + "@docusaurus/module-type-aliases": "3.7.0", + "gh-pages": "6.3.0" }, "engines": { "node": ">=18.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -59,145 +59,109 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", - "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "node_modules/@algolia/client-abtesting": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.19.0.tgz", + "integrity": "sha512-dMHwy2+nBL0SnIsC1iHvkBao64h4z+roGelOz11cxrDBrAdASxLxmfVMop8gmodQ2yZSacX0Rzevtxa+9SqxCw==", "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", - "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", - "license": "MIT" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", - "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", - "license": "MIT", - "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", - "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", - "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.19.0.tgz", + "integrity": "sha512-CDW4RwnCHzU10upPJqS6N6YwDpDHno7w6/qXT9KPbPbt8szIIzCHrva4O9KIfx1OhdsHzfGSI5hMAiOOYl4DEQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "node_modules/@algolia/client-common": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.19.0.tgz", + "integrity": "sha512-2ERRbICHXvtj5kfFpY5r8qu9pJII/NAHsdgUXnUitQFwPdPL7wXiupcvZJC7DSntOnE8AE0lM7oDsPhrJfj5nQ==", "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "node_modules/@algolia/client-insights": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.19.0.tgz", + "integrity": "sha512-xPOiGjo6I9mfjdJO7Y+p035aWePcbsItizIp+qVyfkfZiGgD+TbNxM12g7QhFAHIkx/mlYaocxPY/TmwPzTe+A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.2.3.tgz", - "integrity": "sha512-zqfcbgjYR72Y/rx/+/6g5Li/eV33yhRq5mkGbU06JYBzvGq6viy0gZl1ckCFhLLifKzXZ4yzUQTw/KG6FV+smg==", - "license": "MIT", - "peer": true, + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", - "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.19.0.tgz", + "integrity": "sha512-B9eoce/fk8NLboGje+pMr72pw+PV7c5Z01On477heTZ7jkxoZ4X92dobeGuEQop61cJ93Gaevd1of4mBr4hu2A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.19.0.tgz", + "integrity": "sha512-6fcP8d4S8XRDtVogrDvmSM6g5g6DndLc0pEm1GCKe9/ZkAzCmM3ZmW1wFYYPxdjMeifWy1vVEDMJK7sbE4W7MA==", "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.2.3.tgz", - "integrity": "sha512-xXdCg8vpiwE8gqSyvjxq8V3qbFa+gHasY5epIz718IByWv3WKLLi/n4SMIfB/zRwXTLVWeGOH/UJSz5VCnAAqg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.19.0.tgz", + "integrity": "sha512-Ctg3xXD/1VtcwmkulR5+cKGOMj4r0wC49Y/KZdGQcqpydKn+e86F6l3tb3utLJQVq4lpEJud6kdRykFgcNsp8Q==", "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3", - "@algolia/requester-browser-xhr": "5.2.3", - "@algolia/requester-node-http": "5.2.3" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" }, "engines": { "node": ">= 14.0.0" @@ -209,233 +173,139 @@ "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", "license": "MIT" }, - "node_modules/@algolia/logger-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", - "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", - "license": "MIT" - }, - "node_modules/@algolia/logger-console": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", - "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", - "license": "MIT", - "dependencies": { - "@algolia/logger-common": "4.24.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", - "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", - "license": "MIT", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "node_modules/@algolia/ingestion": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.19.0.tgz", + "integrity": "sha512-LO7w1MDV+ZLESwfPmXkp+KLeYeFrYEgtbCZG6buWjddhYraPQ9MuQWLhLLiaMlKxZ/sZvFTcZYuyI6Jx4WBhcg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "node_modules/@algolia/monitoring": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.19.0.tgz", + "integrity": "sha512-Mg4uoS0aIKeTpu6iv6O0Hj81s8UHagi5TLm9k2mLIib4vmMtX7WgIAHAcFIaqIZp5D6s5EVy1BaDOoZ7buuJHA==", "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "node_modules/@algolia/recommend": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.19.0.tgz", + "integrity": "sha512-PbgrMTbUPlmwfJsxjFhal4XqZO2kpBNRjemLVTkUiti4w/+kzcYO4Hg5zaBgVqPwvFDNQ8JS4SS3TBBem88u+g==", "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.2.3.tgz", - "integrity": "sha512-lezcE4E7ax7JkDGDKA/xAnyAY9p9LZ4AxzsyL0pksqUpOvn4U0msP553M2yJRfsxxdGDp15noCnPuRsh7u8dMg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.19.0.tgz", + "integrity": "sha512-GfnhnQBT23mW/VMNs7m1qyEyZzhZz093aY2x8p0era96MMyNv8+FxGek5pjVX0b57tmSCZPf4EqNCpkGcGsmbw==", "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3" + "@algolia/client-common": "5.19.0" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", - "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", - "license": "MIT" - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.2.3.tgz", - "integrity": "sha512-xTxsRnJqxG1dylIkxmflrHO9LJfJKjSHqEF5yGdRrtnqIEvb2hiQPCHm2XwqxMa3NBcf6lmydGfJqhPLnRJwtw==", + "node_modules/@algolia/requester-fetch": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.19.0.tgz", + "integrity": "sha512-oyTt8ZJ4T4fYvW5avAnuEc6Laedcme9fAFryMD9ndUTIUe/P0kn3BuGcCLFjN3FDmdrETHSFkgPPf1hGy3sLCw==", "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3" + "@algolia/client-common": "5.19.0" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@algolia/transporter": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", - "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "node_modules/@algolia/requester-node-http": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.19.0.tgz", + "integrity": "sha512-p6t8ue0XZNjcRiqNkb5QAM0qQRAKsCiebZ6n9JjWA+p8fWf8BvnhO55y2fO28g3GW0Imj7PrAuyBuxq8aDVQwQ==", "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/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==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/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==" - }, - "node_modules/@babel/code-frame/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==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/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==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/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==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -454,54 +324,48 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "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==", - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.25.9" }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -513,23 +377,23 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz", - "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==", - "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.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -543,17 +407,19 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "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==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -567,14 +433,16 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -586,69 +454,41 @@ "@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==", - "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==", - "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==", - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "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==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", "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" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -658,32 +498,35 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "license": "MIT", "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -693,13 +536,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -708,183 +552,126 @@ "@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==", - "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==", - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", - "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/types": "^7.26.3" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "color-convert": "^1.9.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/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==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/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==" - }, - "node_modules/@babel/highlight/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==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/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==", - "engines": { - "node": ">=4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/highlight/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==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "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.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "version": "7.25.9", + "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.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -894,13 +681,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -910,12 +698,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -928,6 +717,7 @@ "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==", + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -935,10 +725,11 @@ "@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==", + "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==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -946,23 +737,28 @@ "@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==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "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==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -971,195 +767,31 @@ "@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==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.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==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "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==", - "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==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.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==", - "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==", - "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==", - "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==", - "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==", - "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==", - "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==", - "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==", - "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-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -1169,6 +801,7 @@ "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==", + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1181,11 +814,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1195,14 +829,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1212,13 +846,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1228,11 +863,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1242,11 +878,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1256,12 +893,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1271,13 +909,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1287,18 +925,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -1309,12 +945,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1324,11 +961,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1338,12 +976,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1353,11 +992,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1366,13 +1006,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1382,12 +1038,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1397,12 +1053,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1412,12 +1068,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1427,13 +1084,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1443,12 +1101,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1458,11 +1116,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1472,12 +1131,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1487,11 +1146,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1501,12 +1161,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1516,13 +1177,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1532,14 +1193,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1549,12 +1211,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1564,12 +1227,13 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1579,11 +1243,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1593,12 +1258,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1608,12 +1273,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1623,15 +1288,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1641,12 +1305,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1656,12 +1321,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1671,13 +1336,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1687,11 +1352,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1701,12 +1367,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1716,14 +1383,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1733,11 +1400,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1747,12 +1415,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz", - "integrity": "sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", + "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1762,11 +1430,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", - "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1776,15 +1445,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1794,11 +1464,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1808,12 +1479,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", - "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1823,11 +1495,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1837,12 +1510,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1852,15 +1542,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", - "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -1874,16 +1565,18 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1893,12 +1586,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1908,11 +1602,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1922,11 +1617,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1936,11 +1632,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1950,14 +1647,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz", + "integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1967,11 +1666,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1981,12 +1681,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1996,12 +1697,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2011,12 +1713,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2026,89 +1729,79 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", - "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@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.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@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-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -2122,6 +1815,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -2130,6 +1824,7 @@ "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==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2140,16 +1835,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", - "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-react-display-name": "^7.23.3", - "@babel/plugin-transform-react-jsx": "^7.22.15", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.23.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2159,15 +1855,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2176,15 +1873,11 @@ "@babel/core": "^7.0.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==" - }, "node_modules/@babel/runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", - "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2193,9 +1886,10 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.7.tgz", - "integrity": "sha512-ER55qzLREVA5YxeyQ3Qu48tgsF2ZrFjFjUS6V6wF0cikSw+goBJgB9PBRM1T6+Ah4iiM+sxmfS/Sy/jdzFfhiQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "license": "MIT", "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" @@ -2205,31 +1899,30 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@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.23.6", - "@babel/types": "^7.23.6", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2238,13 +1931,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2259,30 +1952,1132 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", - "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==", - "license": "MIT" - }, - "node_modules/@docsearch/react": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", - "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", + "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.1", - "algoliasearch": "^4.19.1" + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", + "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", + "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", + "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.7.tgz", + "integrity": "sha512-aDHYmhNIHR6iLw4ElWhf+tRqqaXwKnMl0YsQ/X105Zc4dQwe6yJpMrTN6BwOoESrkDjOYMOfORviSSLeDTJkdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.7.tgz", + "integrity": "sha512-e68Nev4CxZYCLcrfWhHH4u/N1YocOfTmw67/kVX5Rb7rnguqqLyxPjhHWjSBX8o4bmyuukmNf3wrUSU3//kT7g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", + "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.6.tgz", + "integrity": "sha512-IgJA5DQsQLu/upA3HcdvC6xEMR051ufebBTIXZ5E9/9iiaA7juXWz1ceYj814lnDYP/7eWjZnw0grRJlX4eI6g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.7.tgz", + "integrity": "sha512-gzFEZPoOkY0HqGdyeBXR3JP218Owr683u7KOZazTK7tQZBE8s2yhg06W1tshOqk7R7SWvw9gkw2TQogKpIW8Xw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.7.tgz", + "integrity": "sha512-WgEyBeg6glUeTdS2XT7qeTFBthTJuXlS9GFro/DVomj7W7WMTamAwpoP4oQCq/0Ki2gvfRYFi/uZtmRE14/DFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.7.tgz", + "integrity": "sha512-LKYqjO+wGwDCfNIEllessCBWfR4MS/sS1WXO+j00KKyOjm7jDW2L6jzUmqASEiv/kkJO39GcoIOvTTfB3yeBUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", + "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz", + "integrity": "sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", + "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", + "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", + "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.6.tgz", + "integrity": "sha512-J1+4Fr2W3pLZsfxkFazK+9kr96LhEYqoeBszLmFjb6AjYs+g9oDAw3J5oQignLKk3rC9XHW+ebPTZ9FaW5u5pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", + "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.7.tgz", + "integrity": "sha512-I6WFQIbEKG2IO3vhaMGZDkucbCaUSXMxvHNzDdnfsTCF5tc0UlV3Oe2AhamatQoKFjBi75dSEMrgWq3+RegsOQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", + "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.2.tgz", + "integrity": "sha512-vBCT6JvgdEkvRc91NFoNrLjgGtkLWt47GKT6E2UDn3nd8ZkMBiziQ1Md1OiKoSsgzxsSnGKG3RVdhlbdZEkHjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.7.tgz", + "integrity": "sha512-apbT31vsJVd18MabfPOnE977xgct5B1I+Jpf+Munw3n6kKb1MMuUmGGH+PT9Hm/fFs6fe61Q/EWnkrb4bNoNQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.1.tgz", + "integrity": "sha512-MslYkZCeMQDxetNkfmmQYgKCy4c+w9pPDfgOBCJOo/RI1RveEUdZQYtOfrC6cIZB7sD7/PHr2VGOcMXlZawrnA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.6.tgz", + "integrity": "sha512-/dwlO9w8vfKgiADxpxUbZOWlL5zKoRIsCymYoh1IPuBsXODKanKnfuZRr32DEqT0//3Av1VjfNZU9yhxtEfIeA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz", + "integrity": "sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.6.tgz", + "integrity": "sha512-c4Y1D2Why/PeccaSouXnTt6WcNHJkoJRidV2VW9s5gJ97cNxnLgQ4Qj8qOqkIR9VmTQKJyNcbF4hy79ZQnWD7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", @@ -2305,80 +3100,124 @@ } } }, - "node_modules/@docusaurus/core": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", - "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", + "node_modules/@docusaurus/babel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.7.0.tgz", + "integrity": "sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ==", "license": "MIT", "dependencies": { - "@babel/core": "^7.23.3", - "@babel/generator": "^7.23.3", + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.22.9", - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@babel/runtime": "^7.22.6", - "@babel/runtime-corejs3": "^7.22.6", - "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "autoprefixer": "^10.4.14", - "babel-loader": "^9.1.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.7.0.tgz", + "integrity": "sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.7.0", + "@docusaurus/cssnano-preset": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.1", + "null-loader": "^4.0.1", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "postcss-preset-env": "^10.1.0", + "react-dev-utils": "^12.0.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.7.0.tgz", + "integrity": "sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.7.0", + "@docusaurus/bundler": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "boxen": "^6.2.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "clean-css": "^5.3.2", "cli-table3": "^0.6.3", "combine-promises": "^1.1.0", "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", "core-js": "^3.31.1", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", "del": "^6.1.1", "detect-port": "^1.5.1", "escape-html": "^1.0.3", "eta": "^2.2.0", "eval": "^0.1.8", - "file-loader": "^6.2.0", "fs-extra": "^11.1.1", - "html-minifier-terser": "^7.2.0", "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.5.3", + "html-webpack-plugin": "^5.6.0", "leven": "^3.1.0", "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.6", "p-map": "^4.0.0", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", "prompts": "^2.4.2", "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", "react-loadable-ssr-addon-v5-slorber": "^1.0.1", "react-router": "^5.3.4", "react-router-config": "^5.1.1", "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", "semver": "^7.5.4", - "serve-handler": "^6.1.5", + "serve-handler": "^6.1.6", "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.9", "tslib": "^2.6.0", "update-notifier": "^6.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.9.0", - "webpackbar": "^5.0.2" + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^6.0.1" }, "bin": { "docusaurus": "bin/docusaurus.mjs" @@ -2388,14 +3227,14 @@ }, "peerDependencies": { "@mdx-js/react": "^3.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", - "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.7.0.tgz", + "integrity": "sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ==", "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", @@ -2408,9 +3247,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", - "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.7.0.tgz", + "integrity": "sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -2421,14 +3260,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", - "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.7.0.tgz", + "integrity": "sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2455,22 +3294,22 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", - "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz", + "integrity": "sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.5.2", + "@docusaurus/types": "3.7.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", "@types/react-router-dom": "*", - "react-helmet-async": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@*", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, "peerDependencies": { @@ -2479,19 +3318,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", - "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.7.0.tgz", + "integrity": "sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", @@ -2508,25 +3347,25 @@ }, "peerDependencies": { "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", - "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0.tgz", + "integrity": "sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2540,21 +3379,21 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", - "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0.tgz", + "integrity": "sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2563,19 +3402,19 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", - "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.7.0.tgz", + "integrity": "sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2584,38 +3423,38 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", - "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.7.0.tgz", + "integrity": "sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", - "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.7.0.tgz", + "integrity": "sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2623,103 +3462,128 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", - "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.7.0.tgz", + "integrity": "sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", - "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.7.0.tgz", + "integrity": "sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.7.0.tgz", + "integrity": "sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" + "@docusaurus/core": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", - "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/plugin-debug": "3.5.2", - "@docusaurus/plugin-google-analytics": "3.5.2", - "@docusaurus/plugin-google-gtag": "3.5.2", - "@docusaurus/plugin-google-tag-manager": "3.5.2", - "@docusaurus/plugin-sitemap": "3.5.2", - "@docusaurus/theme-classic": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-search-algolia": "3.5.2", - "@docusaurus/types": "3.5.2" + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.7.0.tgz", + "integrity": "sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/plugin-content-blog": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/plugin-content-pages": "3.7.0", + "@docusaurus/plugin-debug": "3.7.0", + "@docusaurus/plugin-google-analytics": "3.7.0", + "@docusaurus/plugin-google-gtag": "3.7.0", + "@docusaurus/plugin-google-tag-manager": "3.7.0", + "@docusaurus/plugin-sitemap": "3.7.0", + "@docusaurus/plugin-svgr": "3.7.0", + "@docusaurus/theme-classic": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-search-algolia": "3.7.0", + "@docusaurus/types": "3.7.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", - "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.7.0.tgz", + "integrity": "sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/plugin-content-blog": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/plugin-content-pages": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-translations": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.44", + "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2734,20 +3598,20 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", - "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.7.0.tgz", + "integrity": "sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A==", "license": "MIT", "dependencies": { - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "@docusaurus/mdx-loader": "3.7.0", + "@docusaurus/module-type-aliases": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2762,26 +3626,26 @@ }, "peerDependencies": { "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", - "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "algoliasearch": "^4.18.0", - "algoliasearch-helper": "^3.13.3", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.7.0.tgz", + "integrity": "sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g==", + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.8.1", + "@docusaurus/core": "3.7.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/plugin-content-docs": "3.7.0", + "@docusaurus/theme-common": "3.7.0", + "@docusaurus/theme-translations": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-validation": "3.7.0", + "algoliasearch": "^5.17.1", + "algoliasearch-helper": "^3.22.6", "clsx": "^2.0.0", "eta": "^2.2.0", "fs-extra": "^11.1.1", @@ -2793,14 +3657,14 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", - "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.7.0.tgz", + "integrity": "sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -2811,9 +3675,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", - "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz", + "integrity": "sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -2821,25 +3685,39 @@ "@types/react": "*", "commander": "^5.1.0", "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "utility-types": "^3.10.0", - "webpack": "^5.88.1", + "webpack": "^5.95.0", "webpack-merge": "^5.9.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/@docusaurus/utils": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", - "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.7.0.tgz", + "integrity": "sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@svgr/webpack": "^8.1.0", + "@docusaurus/logger": "3.7.0", + "@docusaurus/types": "3.7.0", + "@docusaurus/utils-common": "3.7.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", @@ -2860,45 +3738,30 @@ }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", - "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.7.0.tgz", + "integrity": "sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA==", "license": "MIT", "dependencies": { + "@docusaurus/types": "3.7.0", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", - "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.7.0.tgz", + "integrity": "sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "@docusaurus/logger": "3.7.0", + "@docusaurus/utils": "3.7.0", + "@docusaurus/utils-common": "3.7.0", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -2928,6 +3791,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -2939,6 +3803,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2952,13 +3817,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2973,9 +3839,10 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -2995,23 +3862,25 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" }, "node_modules/@mdx-js/mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", - "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -3020,14 +3889,15 @@ "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", - "estree-util-to-js": "^2.0.0", + "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", - "hast-util-to-estree": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", - "periscopic": "^3.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", @@ -3044,9 +3914,10 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", - "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "license": "MIT", "dependencies": { "@types/mdx": "^2.0.0" }, @@ -3129,9 +4000,10 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "license": "MIT" }, "node_modules/@sideway/address": { "version": "4.1.5", @@ -3157,7 +4029,8 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", @@ -3454,6 +4327,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", "engines": { "node": ">=10.13.0" } @@ -3471,6 +4345,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -3480,6 +4355,7 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3488,6 +4364,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3496,6 +4373,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" @@ -3510,10 +4388,31 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "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==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3528,6 +4427,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -3536,9 +4436,22 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.4.tgz", + "integrity": "sha512-5kz9ScmzBdzTgB/3susoCgfqNDzBjvLL4taparufgSvlwjdLy6UyUy9T/tCpYd2GIdIilCatC4iSQS0QSYHt0w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -3579,12 +4492,14 @@ "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3592,12 +4507,14 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -3606,6 +4523,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -3632,7 +4550,8 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" }, "node_modules/@types/ms": { "version": "0.7.34", @@ -3652,6 +4571,7 @@ "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3662,9 +4582,10 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.11", @@ -3672,14 +4593,16 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.46", @@ -3723,7 +4646,8 @@ "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" }, "node_modules/@types/sax": { "version": "1.2.7", @@ -3743,6 +4667,7 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -3752,24 +4677,27 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3781,17 +4709,19 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -3799,159 +4729,178 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" }, "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==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", "license": "ISC" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" }, "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==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -3964,6 +4913,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3972,6 +4922,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -3979,10 +4930,20 @@ "node": ">= 0.6" } }, + "node_modules/accepts/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==", + "license": "MIT", + "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==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3990,14 +4951,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4008,9 +4961,13 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", - "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -4036,14 +4993,15 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -4054,6 +5012,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -4070,6 +5029,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -4078,32 +5038,33 @@ } }, "node_modules/algoliasearch": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", - "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", - "license": "MIT", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-account": "4.24.0", - "@algolia/client-analytics": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-personalization": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/recommend": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.19.0.tgz", + "integrity": "sha512-zrLtGhC63z3sVLDDKGW+SlCRN9eJHFTgdEmoAOpsVh6wgGL1GgTTDou7tpCBjevzgIvi3AIyDAQO3Xjbg5eqZg==", + "license": "MIT", + "dependencies": { + "@algolia/client-abtesting": "5.19.0", + "@algolia/client-analytics": "5.19.0", + "@algolia/client-common": "5.19.0", + "@algolia/client-insights": "5.19.0", + "@algolia/client-personalization": "5.19.0", + "@algolia/client-query-suggestions": "5.19.0", + "@algolia/client-search": "5.19.0", + "@algolia/ingestion": "1.19.0", + "@algolia/monitoring": "1.19.0", + "@algolia/recommend": "5.19.0", + "@algolia/requester-browser-xhr": "5.19.0", + "@algolia/requester-fetch": "5.19.0", + "@algolia/requester-node-http": "5.19.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.4.tgz", - "integrity": "sha512-fvBCywguW9f+939S6awvRMstqMF1XXcd2qs1r1aGqL/PJ1go/DqN06tWmDVmhCDqBJanm++imletrQWf0G2S1g==", + "version": "3.22.6", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.6.tgz", + "integrity": "sha512-F2gSb43QHyvZmvH/2hxIjbk/uFdO2MguQYTFP7J+RowMW1csjIODMobEnpLI8nbLQuzZnGZdIxl5Bpy1k9+CFQ==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -4112,45 +5073,6 @@ "algoliasearch": ">= 3.1 < 6" } }, - "node_modules/algoliasearch/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4177,6 +5099,33 @@ "node": ">=8" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -4184,6 +5133,7 @@ "engines": [ "node >= 0.8.0" ], + "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } @@ -4231,12 +5181,14 @@ "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==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-union": { "version": "2.1.0", @@ -4246,15 +5198,6 @@ "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", @@ -4316,9 +5259,10 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -4335,17 +5279,19 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "license": "MIT", "dependencies": { "object.assign": "^4.1.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -4356,28 +5302,31 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4401,12 +5350,14 @@ "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", "engines": { "node": "*" } @@ -4423,6 +5374,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4446,6 +5398,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4454,6 +5407,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4461,15 +5415,15 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } @@ -4521,9 +5475,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -4538,11 +5492,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4560,6 +5515,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4596,20 +5552,49 @@ "engines": { "node": ">=14.16" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "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==", + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", "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" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4650,6 +5635,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -4658,9 +5644,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -4674,7 +5660,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", @@ -4910,6 +5897,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -4919,21 +5907,11 @@ "node": ">=6" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4967,12 +5945,14 @@ "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/combine-promises": { "version": "1.2.0", @@ -5003,7 +5983,8 @@ "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" }, "node_modules/commondir": { "version": "1.0.1", @@ -5015,6 +5996,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -5023,34 +6005,46 @@ } }, "node_modules/compressible/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==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", + "negotiator": "~0.6.4", "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5058,12 +6052,8 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -5101,19 +6091,25 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.3.3.tgz", + "integrity": "sha512-Qil5KwghMzlqd51UXM0b6fyaGHtOC22scxrwrz4A2882LyUMwQjnvaedN1HAeXzphspQ6CpHkzMAWxBTUruDLg==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5122,6 +6118,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5129,12 +6126,14 @@ "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==" + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5142,7 +6141,8 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/copy-text-to-clipboard": { "version": "3.2.0", @@ -5160,6 +6160,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", "dependencies": { "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", @@ -5183,6 +6184,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -5194,6 +6196,7 @@ "version": "13.2.2", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.3.0", @@ -5212,6 +6215,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -5230,11 +6234,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", - "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "license": "MIT", "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.24.3" }, "funding": { "type": "opencollective", @@ -5242,10 +6247,11 @@ } }, "node_modules/core-js-pure": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.0.tgz", - "integrity": "sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz", + "integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -5254,12 +6260,14 @@ "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==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -5319,10 +6327,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -5330,19 +6377,82 @@ "postcss": "^8.0.9" } }, + "node_modules/css-has-pseudo": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz", + "integrity": "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -5352,13 +6462,23 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-minimizer-webpack-plugin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "cssnano": "^6.0.1", @@ -5398,10 +6518,33 @@ } } }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -5417,6 +6560,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" @@ -5436,10 +6580,27 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssdb": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.3.tgz", + "integrity": "sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, "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==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -5451,6 +6612,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", "dependencies": { "cssnano-preset-default": "^6.1.2", "lilconfig": "^3.1.1" @@ -5491,6 +6653,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "css-declaration-sorter": "^7.2.0", @@ -5534,6 +6697,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -5545,6 +6709,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -5557,6 +6722,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -5569,7 +6735,8 @@ "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" }, "node_modules/csstype": { "version": "3.1.3", @@ -5579,7 +6746,8 @@ "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" }, "node_modules/debug": { "version": "4.3.4", @@ -5655,6 +6823,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", "dependencies": { "execa": "^5.0.0" }, @@ -5674,6 +6843,7 @@ "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==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -5698,6 +6868,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -5735,6 +6906,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5752,6 +6924,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5760,7 +6933,8 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" }, "node_modules/detect-port": { "version": "1.5.1", @@ -5828,15 +7002,11 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -5856,6 +7026,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -5880,6 +7051,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -5891,9 +7063,10 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -5934,6 +7107,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -5947,12 +7134,14 @@ "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==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz", - "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==" + "version": "1.5.79", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz", + "integrity": "sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==", + "license": "ISC" }, "node_modules/email-addresses": { "version": "5.0.0", @@ -5975,6 +7164,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -5993,6 +7183,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6013,6 +7204,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -6028,34 +7220,78 @@ "is-arrayish": "^0.2.1" } }, - "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==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" }, - "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==", - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" - }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6178,6 +7414,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-util-to-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", @@ -6194,9 +7444,9 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", - "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.2.1.tgz", + "integrity": "sha512-Vt2UOjyPbNQQgT5eJh+K5aATti0OjCIAGc9SgMdOFYbohuifsWclR74l0iZTJwePMgWYdX1hlVS+dedH9XV8kw==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -6232,6 +7482,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -6251,6 +7502,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6270,7 +7522,8 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" }, "node_modules/events": { "version": "3.3.0", @@ -6284,6 +7537,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -6303,9 +7557,10 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -6326,7 +7581,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -6341,17 +7596,17 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, "node_modules/express/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==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -6363,6 +7618,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6370,17 +7626,20 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6428,13 +7687,21 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } + "node_modules/fast-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", + "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.16.0", @@ -6461,6 +7728,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -6480,10 +7748,35 @@ "node": ">=0.4.0" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/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==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -6503,6 +7796,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6518,6 +7812,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -6525,12 +7820,14 @@ "node_modules/file-loader/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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -6593,6 +7890,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -6610,6 +7908,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6617,12 +7916,14 @@ "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==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" @@ -6638,6 +7939,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -6653,20 +7955,22 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, "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==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -6816,6 +8120,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6837,6 +8142,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6889,20 +8195,27 @@ "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==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "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==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6917,6 +8230,19 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "license": "ISC" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6929,18 +8255,19 @@ } }, "node_modules/gh-pages": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", - "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", + "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==", "dev": true, + "license": "MIT", "dependencies": { "async": "^3.2.4", - "commander": "^11.0.0", + "commander": "^13.0.0", "email-addresses": "^5.0.0", "filenamify": "^4.3.0", "find-cache-dir": "^3.3.1", "fs-extra": "^11.1.1", - "globby": "^6.1.0" + "globby": "^11.1.0" }, "bin": { "gh-pages": "bin/gh-pages.js", @@ -6950,25 +8277,14 @@ "node": ">=10" } }, - "node_modules/gh-pages/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gh-pages/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", + "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/gh-pages/node_modules/find-cache-dir": { @@ -7001,22 +8317,6 @@ "node": ">=8" } }, - "node_modules/gh-pages/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gh-pages/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -7179,6 +8479,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", "engines": { "node": ">=4" } @@ -7203,11 +8504,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7307,7 +8609,8 @@ "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -7321,6 +8624,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -7328,21 +8632,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7362,9 +8656,10 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -7373,15 +8668,15 @@ } }, "node_modules/hast-util-from-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", - "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", + "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", - "hastscript": "^8.0.0", + "hastscript": "^9.0.0", "property-information": "^6.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", @@ -7406,9 +8701,9 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", - "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -7431,9 +8726,9 @@ } }, "node_modules/hast-util-to-estree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", - "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.1.tgz", + "integrity": "sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -7449,7 +8744,7 @@ "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.0", + "style-to-object": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" }, @@ -7459,9 +8754,9 @@ } }, "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", - "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -7485,21 +8780,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", - "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==", - "license": "MIT" - }, - "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.7.tgz", - "integrity": "sha512-uSjr59G5u6fbxUfKbb8GcqMGT3Xs9v5IbPkjb0S16GyOeBLAzSRK0CixBv5YrYvzO6TDLzIS6QCn78tkqWngPw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.3" - } - }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", @@ -7533,9 +8813,9 @@ } }, "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", + "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -7582,6 +8862,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -7592,12 +8873,14 @@ "node_modules/hpack.js/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/hpack.js/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==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7611,20 +8894,22 @@ "node_modules/hpack.js/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==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/hpack.js/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==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "funding": [ { "type": "github", @@ -7634,17 +8919,20 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" }, "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==" + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" }, "node_modules/html-minifier-terser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "clean-css": "~5.3.2", @@ -7665,6 +8953,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", "engines": { "node": ">=14" } @@ -7776,12 +9065,14 @@ "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -7796,12 +9087,14 @@ "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -7815,6 +9108,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -7838,6 +9132,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -7861,6 +9156,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -7869,6 +9165,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7880,6 +9177,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -7896,9 +9194,9 @@ } }, "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.0.tgz", + "integrity": "sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==", "license": "MIT", "dependencies": { "queue": "6.0.2" @@ -7959,9 +9257,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.44", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", - "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "license": "MIT", "engines": { "node": ">=12" @@ -7987,9 +9285,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, "node_modules/interpret": { @@ -8009,9 +9307,10 @@ } }, "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -8220,20 +9519,15 @@ } }, "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "license": "MIT", "dependencies": { - "@types/estree": "*" + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/is-regexp": { @@ -8257,6 +9551,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -8302,6 +9597,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8310,6 +9606,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -8326,6 +9623,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -8340,6 +9638,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8351,9 +9650,10 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -8380,6 +9680,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -8388,14 +9689,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -8411,12 +9713,14 @@ "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==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -8474,9 +9778,10 @@ } }, "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "license": "MIT", "dependencies": { "picocolors": "^1.0.0", "shell-quote": "^1.8.1" @@ -8491,9 +9796,10 @@ } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -8518,6 +9824,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -8531,6 +9838,7 @@ "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==", + "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -8549,17 +9857,20 @@ "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==" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", @@ -8605,6 +9916,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -8646,15 +9958,24 @@ } }, "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-directive": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", @@ -8676,9 +9997,9 @@ } }, "node_modules/mdast-util-find-and-replace": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", - "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -8704,9 +10025,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", - "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -8728,9 +10049,9 @@ } }, "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8810,9 +10131,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8830,9 +10151,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8928,9 +10249,9 @@ } }, "node_modules/mdast-util-mdx-expression": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", - "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -9023,9 +10344,9 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", - "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -9033,6 +10354,7 @@ "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" @@ -9058,12 +10380,14 @@ "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" }, "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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9083,6 +10407,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -9104,14 +10429,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micromark": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", - "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", "funding": [ { "type": "GitHub Sponsors", @@ -9144,9 +10470,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", - "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", "funding": [ { "type": "GitHub Sponsors", @@ -9178,9 +10504,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9198,9 +10524,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9218,9 +10544,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9234,9 +10560,9 @@ "license": "MIT" }, "node_modules/micromark-extension-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.1.tgz", - "integrity": "sha512-VGV2uxUzhEZmaP7NSFo2vtq7M2nUD+WfmYQD+d8i/1nHbzE+rMy9uzTvUybBbNiVbrhOZibg3gbyoARGqgDWyg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -9253,9 +10579,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9273,9 +10599,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9293,9 +10619,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9325,9 +10651,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9345,9 +10671,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9397,9 +10723,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9417,9 +10743,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9453,9 +10779,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9473,9 +10799,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9493,9 +10819,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9527,9 +10853,9 @@ } }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9560,9 +10886,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9580,9 +10906,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9600,9 +10926,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9646,9 +10972,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9666,9 +10992,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9686,9 +11012,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9728,9 +11054,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9748,9 +11074,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9768,9 +11094,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9784,9 +11110,9 @@ "license": "MIT" }, "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", "license": "MIT", "dependencies": { "@types/acorn": "^4.0.0", @@ -9796,6 +11122,7 @@ "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" @@ -9806,9 +11133,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9826,9 +11153,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9846,9 +11173,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9916,9 +11243,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9936,9 +11263,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9952,9 +11279,9 @@ "license": "MIT" }, "node_modules/micromark-factory-destination": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", - "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -9973,9 +11300,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9993,9 +11320,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10009,9 +11336,9 @@ "license": "MIT" }, "node_modules/micromark-factory-label": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", - "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -10031,9 +11358,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10051,9 +11378,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10067,9 +11394,9 @@ "license": "MIT" }, "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", - "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", "funding": [ { "type": "GitHub Sponsors", @@ -10084,6 +11411,7 @@ "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -10092,10 +11420,30 @@ "vfile-message": "^4.0.0" } }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10113,9 +11461,9 @@ } }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10165,9 +11513,9 @@ "license": "MIT" }, "node_modules/micromark-factory-title": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", - "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -10187,9 +11535,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -10207,9 +11555,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10227,9 +11575,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10243,9 +11591,9 @@ "license": "MIT" }, "node_modules/micromark-factory-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", - "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10265,9 +11613,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -10285,9 +11633,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10305,9 +11653,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10357,9 +11705,9 @@ "license": "MIT" }, "node_modules/micromark-util-chunked": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", - "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -10376,9 +11724,9 @@ } }, "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10392,9 +11740,9 @@ "license": "MIT" }, "node_modules/micromark-util-classify-character": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", - "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10413,9 +11761,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10433,9 +11781,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10449,9 +11797,9 @@ "license": "MIT" }, "node_modules/micromark-util-combine-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", - "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -10469,9 +11817,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", - "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -10488,9 +11836,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10504,9 +11852,9 @@ "license": "MIT" }, "node_modules/micromark-util-decode-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", - "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10526,9 +11874,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10546,9 +11894,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10562,9 +11910,9 @@ "license": "MIT" }, "node_modules/micromark-util-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -10604,9 +11952,9 @@ } }, "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10620,9 +11968,9 @@ "license": "MIT" }, "node_modules/micromark-util-html-tag-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", - "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -10636,9 +11984,9 @@ "license": "MIT" }, "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", - "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10655,9 +12003,9 @@ } }, "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10671,9 +12019,9 @@ "license": "MIT" }, "node_modules/micromark-util-resolve-all": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", - "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -10690,9 +12038,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", - "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10711,9 +12059,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10731,9 +12079,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10747,9 +12095,9 @@ "license": "MIT" }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", - "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", "funding": [ { "type": "GitHub Sponsors", @@ -10769,9 +12117,9 @@ } }, "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10801,9 +12149,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10817,9 +12165,9 @@ "license": "MIT" }, "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -10837,9 +12185,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10857,9 +12205,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10888,6 +12236,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -10899,6 +12248,7 @@ "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10907,6 +12257,7 @@ "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", "dependencies": { "mime-db": "~1.33.0" }, @@ -10918,6 +12269,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -10934,11 +12286,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "license": "MIT", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -10954,7 +12308,8 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" }, "node_modules/minimatch": { "version": "3.1.2", @@ -10979,6 +12334,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "license": "MIT", "engines": { "node": ">=10" } @@ -10992,6 +12348,7 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -11001,15 +12358,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -11018,9 +12376,10 @@ } }, "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==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -11040,9 +12399,9 @@ } }, "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", @@ -11058,14 +12417,16 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -11088,6 +12449,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -11101,15 +12463,84 @@ "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", "license": "MIT" }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "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/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/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==", + "license": "MIT" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { - "boolbase": "^1.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/object-assign": { @@ -11121,9 +12552,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11135,18 +12567,22 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", "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==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -11159,12 +12595,14 @@ "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -11176,6 +12614,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11192,6 +12631,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -11222,6 +12662,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", "bin": { "opener": "bin/opener-bin.js" } @@ -11238,6 +12679,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -11252,6 +12694,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -11280,6 +12723,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" @@ -11334,13 +12778,12 @@ } }, "node_modules/parse-entities": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", - "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", - "character-entities": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", @@ -11383,24 +12826,24 @@ "license": "ISC" }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "entities": "^4.5.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "license": "MIT", "dependencies": { - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "parse5": "^7.0.0" }, "funding": { @@ -11411,6 +12854,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11428,6 +12872,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -11443,7 +12888,8 @@ "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { "version": "3.1.1", @@ -11459,9 +12905,10 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", "dependencies": { "isarray": "0.0.1" } @@ -11474,21 +12921,11 @@ "node": ">=8" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -11501,40 +12938,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "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/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-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", "dependencies": { "find-up": "^6.3.0" }, @@ -11613,9 +13021,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -11630,19 +13038,59 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-calc": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0" @@ -11654,42 +13102,277 @@ "postcss": "^8.2.2" } }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.7.tgz", + "integrity": "sha512-EZvAHsvyASX63vXnyXOIynkxhaHRSsdb7z6yiXKIovGXAolW4cMZ3qoh7k3VdTsLBS6VGdksGfIo3r6+waLoOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-colormin": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", + "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", + "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", + "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=4" } }, "node_modules/postcss-discard-comments": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11701,6 +13384,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11712,6 +13396,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11723,6 +13408,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11745,10 +13431,200 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", + "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.7.tgz", + "integrity": "sha512-+ONj2bpOQfsCKZE2T9VGMyVVdGcGUpr7u3SVfvkJlvhTRmDCfY25k4Jc8fubB9DclAPR4+w8uVtDZmdRgdAHig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-loader": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "license": "MIT", "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", @@ -11766,6 +13642,31 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-logical": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz", + "integrity": "sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-merge-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", @@ -11786,6 +13687,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^6.1.1" @@ -11801,6 +13703,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -11818,6 +13721,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -11832,6 +13736,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^4.0.2", @@ -11848,6 +13753,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "cssnano-utils": "^4.0.2", @@ -11864,6 +13770,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -11875,9 +13782,10 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -11886,53 +13794,167 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", + "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss-selector-parser": "^7.0.0" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=4" } }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11944,6 +13966,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -11958,6 +13981,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -11972,6 +13996,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -11986,6 +14011,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12000,6 +14026,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12014,6 +14041,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -12029,6 +14057,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12043,6 +14072,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12053,10 +14083,33 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-ordered-values": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -12068,6 +14121,190 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.3.tgz", + "integrity": "sha512-9qzVhcMFU/MnwYHyYpJz4JhGku/4+xEiPTmhn0hj3IxnUYlEF9vbh7OC1KoLAnenS6Fgg43TKNp9xcuMeAi4Zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^5.0.1", + "@csstools/postcss-color-function": "^4.0.7", + "@csstools/postcss-color-mix-function": "^3.0.7", + "@csstools/postcss-content-alt-text": "^2.0.4", + "@csstools/postcss-exponential-functions": "^2.0.6", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.7", + "@csstools/postcss-gradients-interpolation-method": "^5.0.7", + "@csstools/postcss-hwb-function": "^4.0.7", + "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-initial": "^2.0.0", + "@csstools/postcss-is-pseudo-class": "^5.0.1", + "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.3", + "@csstools/postcss-media-minmax": "^2.0.6", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.7", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-random-function": "^1.0.2", + "@csstools/postcss-relative-color-syntax": "^3.0.7", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.1", + "@csstools/postcss-stepped-value-functions": "^4.0.6", + "@csstools/postcss-text-decoration-shorthand": "^4.0.1", + "@csstools/postcss-trigonometric-functions": "^4.0.6", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.2", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.2.3", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.7", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.5", + "postcss-custom-properties": "^14.0.4", + "postcss-custom-selectors": "^8.0.4", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.0", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.7", + "postcss-logical": "^8.0.0", + "postcss-nesting": "^13.0.1", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-reduce-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", @@ -12087,6 +14324,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" @@ -12102,6 +14340,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12112,10 +14351,58 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -12143,6 +14430,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^3.2.0" @@ -12158,6 +14446,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -12171,7 +14460,8 @@ "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==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/postcss-zindex": { "version": "6.0.2", @@ -12198,14 +14488,16 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -12226,7 +14518,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==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/prompts": { "version": "2.4.2", @@ -12269,6 +14562,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -12281,15 +14575,11 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -12308,6 +14598,7 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -12369,6 +14660,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12377,6 +14669,7 @@ "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==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12391,6 +14684,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12680,6 +14974,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12696,25 +14991,89 @@ "dependencies": { "picomatch": "^2.2.1" }, - "engines": { - "node": ">=8.10.0" + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", + "license": "MIT" + }, + "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==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/reading-time": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", - "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", - "license": "MIT" - }, - "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==", + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", "dependencies": { - "resolve": "^1.1.6" + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">= 0.10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/recursive-readdir": { @@ -12731,12 +15090,14 @@ "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==" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, "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==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -12753,19 +15114,21 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } }, "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==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -12798,23 +15161,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "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==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/rehype-raw": { @@ -12832,6 +15206,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -12907,9 +15296,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", - "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", "license": "MIT", "dependencies": { "mdast-util-mdx": "^3.0.0", @@ -12937,9 +15326,9 @@ } }, "node_modules/remark-rehype": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", - "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -13061,10 +15450,20 @@ "entities": "^2.0.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "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==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13080,7 +15479,8 @@ "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==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", @@ -13134,6 +15534,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -13161,11 +15562,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rtl-detect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" - }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -13228,7 +15624,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sax": { "version": "1.4.1", @@ -13245,9 +15642,10 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -13255,7 +15653,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -13263,9 +15661,9 @@ } }, "node_modules/search-insights": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.0.tgz", - "integrity": "sha512-AskayU3QNsXQzSL6v4LTYST7NNfs2HWyHHB+sdORP9chsytAhro5XRfToAMI/LAVYgNbzowVZTMfBRodgbUHKg==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "license": "MIT", "peer": true }, @@ -13285,12 +15683,14 @@ "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" @@ -13347,6 +15747,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13370,6 +15771,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13377,12 +15779,14 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/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==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13390,12 +15794,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/send/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13409,29 +15815,31 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -13449,6 +15857,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13457,6 +15866,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13465,6 +15875,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -13478,22 +15889,26 @@ "node_modules/serve-index/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" }, "node_modules/serve-index/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13502,6 +15917,7 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -13516,6 +15932,7 @@ "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==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -13531,12 +15948,14 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -13593,14 +16012,69 @@ } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13618,6 +16092,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -13691,6 +16166,7 @@ "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", @@ -13716,9 +16192,10 @@ } }, "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==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -13754,6 +16231,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -13769,6 +16247,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -13800,19 +16279,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -13910,6 +16392,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -13948,18 +16431,19 @@ } }, "node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.1.1" + "inline-style-parser": "0.2.4" } }, "node_modules/stylehacks": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.16" @@ -14003,6 +16487,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "license": "MIT", "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -14027,6 +16512,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -14174,7 +16660,8 @@ "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" }, "node_modules/tiny-invariant": { "version": "1.3.1", @@ -14186,14 +16673,6 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "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==", - "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", @@ -14209,6 +16688,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -14217,6 +16697,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -14282,6 +16763,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -14294,6 +16776,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14302,6 +16785,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -14336,9 +16820,10 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "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==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14356,6 +16841,7 @@ "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==", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -14365,9 +16851,10 @@ } }, "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14376,6 +16863,7 @@ "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==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14506,14 +16994,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -14528,9 +17017,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -14629,6 +17119,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "mime-types": "^2.1.27", @@ -14655,6 +17146,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14670,6 +17162,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -14677,12 +17170,14 @@ "node_modules/url-loader/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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/url-loader/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14691,6 +17186,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -14702,6 +17198,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -14718,7 +17215,8 @@ "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==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utila": { "version": "0.4.0", @@ -14738,6 +17236,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -14746,6 +17245,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -14759,6 +17259,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14821,6 +17322,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -14836,17 +17338,18 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -14881,9 +17384,10 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", @@ -14893,7 +17397,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -14910,6 +17413,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -14918,6 +17422,7 @@ "version": "5.3.4", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -14940,6 +17445,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14948,6 +17454,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -14959,14 +17466,16 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -14996,7 +17505,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -15025,6 +17534,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -15042,16 +17552,17 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -15127,26 +17638,82 @@ } }, "node_modules/webpackbar": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", - "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.3", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.0.1" + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.21.3" }, "peerDependencies": { "webpack": "3 || 4 || 5" } }, + "node_modules/webpackbar/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==", + "license": "MIT" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/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==", + "license": "MIT", + "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/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -15160,6 +17727,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } @@ -15195,7 +17763,8 @@ "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", @@ -15269,6 +17838,7 @@ "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -15311,7 +17881,8 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", @@ -15322,9 +17893,10 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "license": "MIT", "engines": { "node": ">=12.20" }, diff --git a/docs/package.json b/docs/package.json index 2c6dfe13..28e498ea 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,16 +15,16 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/preset-classic": "3.5.2", - "@mdx-js/react": "3.0.1", - "prism-react-renderer": "2.3.1", + "@docusaurus/core": "3.7.0", + "@docusaurus/preset-classic": "3.7.0", + "@mdx-js/react": "3.1.0", + "prism-react-renderer": "2.4.1", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.5.2", - "gh-pages": "6.1.1" + "@docusaurus/module-type-aliases": "3.7.0", + "gh-pages": "6.3.0" }, "browserslist": { "production": [ diff --git a/src/Benchmarks/ArgumentsBenchmarks.cs b/src/Benchmarks/ArgumentsBenchmarks.cs index 1deb80d8..5ae3ce96 100644 --- a/src/Benchmarks/ArgumentsBenchmarks.cs +++ b/src/Benchmarks/ArgumentsBenchmarks.cs @@ -3,94 +3,93 @@ using EntityGraphQL; using EntityGraphQL.Compiler.Util; -namespace Benchmarks +namespace Benchmarks; + +/// +/// Was testing the performance of using reflection to build the correct types from variables or using System.Text.Json +/// +/// Reflection wins +/// | Method | Job | Toolchain | IterationCount | LaunchCount | WarmupCount | Mean | Error | StdDev | +/// |---------------------------- |----------- |----------------------- |--------------- |------------ |------------ |------------:|-------------:|-----------:| +/// | ObjectWithJsonSerialization | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 7,524.18 ms | 146.326 ms | 227.812 ms | +/// | ObjectWithReflection | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 21.77 ms | 0.355 ms | 0.332 ms | +/// | ListWithJsonSerialization | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 9,686.72 ms | 193.058 ms | 327.827 ms | +/// | ListWithReflection | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 44.22 ms | 0.202 ms | 0.189 ms | +/// | ObjectWithJsonSerialization | ShortRun | Default | 3 | 1 | 3 | 7,278.49 ms | 5,676.616 ms | 311.154 ms | +/// | ObjectWithReflection | ShortRun | Default | 3 | 1 | 3 | 20.41 ms | 1.685 ms | 0.092 ms | +/// | ListWithJsonSerialization | ShortRun | Default | 3 | 1 | 3 | 9,877.59 ms | 2,168.229 ms | 118.848 ms | +/// | ListWithReflection | ShortRun | Default | 3 | 1 | 3 | 43.14 ms | 36.734 ms | 2.013 ms | +/// +[ShortRunJob] +public class ArgumentsBenchmarks { - /// - /// Was testing the performance of using reflection to build the correct types from variables or using System.Text.Json - /// - /// Reflection wins - /// | Method | Job | Toolchain | IterationCount | LaunchCount | WarmupCount | Mean | Error | StdDev | - /// |---------------------------- |----------- |----------------------- |--------------- |------------ |------------ |------------:|-------------:|-----------:| - /// | ObjectWithJsonSerialization | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 7,524.18 ms | 146.326 ms | 227.812 ms | - /// | ObjectWithReflection | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 21.77 ms | 0.355 ms | 0.332 ms | - /// | ListWithJsonSerialization | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 9,686.72 ms | 193.058 ms | 327.827 ms | - /// | ListWithReflection | Job-DOGPPG | InProcessEmitToolchain | Default | Default | Default | 44.22 ms | 0.202 ms | 0.189 ms | - /// | ObjectWithJsonSerialization | ShortRun | Default | 3 | 1 | 3 | 7,278.49 ms | 5,676.616 ms | 311.154 ms | - /// | ObjectWithReflection | ShortRun | Default | 3 | 1 | 3 | 20.41 ms | 1.685 ms | 0.092 ms | - /// | ListWithJsonSerialization | ShortRun | Default | 3 | 1 | 3 | 9,877.59 ms | 2,168.229 ms | 118.848 ms | - /// | ListWithReflection | ShortRun | Default | 3 | 1 | 3 | 43.14 ms | 36.734 ms | 2.013 ms | - /// - [ShortRunJob] - public class ArgumentsBenchmarks - { - // [Benchmark] - // public void ObjectWithJsonSerialization() - // { - // var variables = new QueryVariables { - // { "names", new { Name = "Lisa", LastName = "Simpson" } } - // }; + // [Benchmark] + // public void ObjectWithJsonSerialization() + // { + // var variables = new QueryVariables { + // { "names", new { Name = "Lisa", LastName = "Simpson" } } + // }; - // for (int i = 0; i < 10000; i++) - // { - // var val = ExpressionUtil.ChangeType(variables["names"], typeof(InputType), true); - // } - // } - // [Benchmark] - // public void ListWithJsonSerialization() - // { - // var variables = new QueryVariables { - // { "names", new List{new InputType2{ Name = "Lisa", LastName = "Simpson" } }} - // }; + // for (int i = 0; i < 10000; i++) + // { + // var val = ExpressionUtil.ChangeType(variables["names"], typeof(InputType), true); + // } + // } + // [Benchmark] + // public void ListWithJsonSerialization() + // { + // var variables = new QueryVariables { + // { "names", new List{new InputType2{ Name = "Lisa", LastName = "Simpson" } }} + // }; - // for (int i = 0; i < 10000; i++) - // { - // var val = ExpressionUtil.ChangeType(variables["names"], typeof(List), true); - // } - // } + // for (int i = 0; i < 10000; i++) + // { + // var val = ExpressionUtil.ChangeType(variables["names"], typeof(List), true); + // } + // } - [Benchmark] - public static void ObjectWithReflection() - { - var variables = new QueryVariables { { "names", new { Name = "Lisa", LastName = "Simpson" } } }; + [Benchmark] + public static void ObjectWithReflection() + { + var variables = new QueryVariables { { "names", new { Name = "Lisa", LastName = "Simpson" } } }; - for (int i = 0; i < 10000; i++) - { - ExpressionUtil.ConvertObjectType(variables["names"], typeof(InputType), null, null); - } + for (int i = 0; i < 10000; i++) + { + ExpressionUtil.ConvertObjectType(variables["names"], typeof(InputType), null, null); } + } - [Benchmark] - public static void ListWithReflection() + [Benchmark] + public static void ListWithReflection() + { + var variables = new QueryVariables { - var variables = new QueryVariables { + "names", + new List { - "names", - new List - { - new InputType2 { Name = "Lisa", LastName = "Simpson" } - } + new InputType2 { Name = "Lisa", LastName = "Simpson" }, } - }; + }, + }; - for (int i = 0; i < 10000; i++) - { - ExpressionUtil.ConvertObjectType(variables["names"], typeof(List), null, null); - } + for (int i = 0; i < 10000; i++) + { + ExpressionUtil.ConvertObjectType(variables["names"], typeof(List), null, null); } } +} - // this would be the anonymous class created in compiling the query - internal class InputType - { - public string name = string.Empty; - public string lastName = string.Empty; - } +// this would be the anonymous class created in compiling the query +internal class InputType +{ + public string name = string.Empty; + public string lastName = string.Empty; +} - // This would be the class they pass in that matches the schema but is different from the anonymous class - internal class InputType2 - { - public string Name = string.Empty; - public string LastName = string.Empty; - } +// This would be the class they pass in that matches the schema but is different from the anonymous class +internal class InputType2 +{ + public string Name = string.Empty; + public string LastName = string.Empty; } diff --git a/src/Benchmarks/BaseBenchmark.cs b/src/Benchmarks/BaseBenchmark.cs index ee88bdbb..1ffa559f 100644 --- a/src/Benchmarks/BaseBenchmark.cs +++ b/src/Benchmarks/BaseBenchmark.cs @@ -4,55 +4,54 @@ using EntityGraphQL.Schema; using Microsoft.Extensions.DependencyInjection; -namespace Benchmarks +namespace Benchmarks; + +public abstract class BaseBenchmark { - public abstract class BaseBenchmark + protected ServiceProvider Services { get; } + protected SchemaProvider Schema { get; } + + public BaseBenchmark() { - protected ServiceProvider Services { get; } - protected SchemaProvider Schema { get; } + var servicesCollection = new ServiceCollection(); + ConfigureServices(servicesCollection); + Services = servicesCollection.BuildServiceProvider(); + Schema = Services.GetRequiredService>(); + + DataLoader.EnsureDbCreated(Services.GetRequiredService()); + } - public BaseBenchmark() + protected virtual SchemaProvider BuildSchema() + { + var schema = SchemaBuilder.FromObject(); + schema.UpdateType(type => { - var servicesCollection = new ServiceCollection(); - ConfigureServices(servicesCollection); - Services = servicesCollection.BuildServiceProvider(); - Schema = Services.GetRequiredService>(); + type.AddField("name", person => $"{person.FirstName} {person.LastName}", "Person's full name"); + }); + return schema; + } - DataLoader.EnsureDbCreated(Services.GetRequiredService()); - } + private void ConfigureServices(IServiceCollection services) + { + services.AddDbContext().AddSingleton(BuildSchema()); + } - protected virtual SchemaProvider BuildSchema() - { - var schema = SchemaBuilder.FromObject(); - schema.UpdateType(type => - { - type.AddField("name", person => $"{person.FirstName} {person.LastName}", "Person's full name"); - }); - return schema; - } - - private void ConfigureServices(IServiceCollection services) - { - services.AddDbContext().AddSingleton(BuildSchema()); - } + protected BenchmarkContext GetContext() + { + return Services.GetRequiredService(); + } - protected BenchmarkContext GetContext() - { - return Services.GetRequiredService(); - } - - /// - /// Run query - /// - /// - /// - /// - /// - protected void RunQuery(BenchmarkContext context, QueryRequest query, ExecutionOptions? options = null) - { - var result = Schema.ExecuteRequestWithContext(query, context, Services, null, options); - if (result.Errors != null) - throw new InvalidOperationException("query failed: " + string.Join("\n", result.Errors.Select(m => m.Message))); - } + /// + /// Run query + /// + /// + /// + /// + /// + protected void RunQuery(BenchmarkContext context, QueryRequest query, ExecutionOptions? options = null) + { + var result = Schema.ExecuteRequestWithContext(query, context, Services, null, options); + if (result.Errors != null) + throw new InvalidOperationException("query failed: " + string.Join("\n", result.Errors.Select(m => m.Message))); } } diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj index bbf7ce36..03e97437 100644 --- a/src/Benchmarks/Benchmarks.csproj +++ b/src/Benchmarks/Benchmarks.csproj @@ -2,18 +2,19 @@ Exe - net8.0 + net8.0;net9.0 + 13.0 false enable - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/Benchmarks/CacheBenchmarks.cs b/src/Benchmarks/CacheBenchmarks.cs deleted file mode 100644 index a52ece2d..00000000 --- a/src/Benchmarks/CacheBenchmarks.cs +++ /dev/null @@ -1,53 +0,0 @@ -using BenchmarkDotNet.Attributes; -using EntityGraphQL; -using EntityGraphQL.Compiler; -using EntityGraphQL.Schema; - -namespace Benchmarks -{ - /// - /// Was testing the difference between always doing the first stage compile vs. a hash and look up for caching purposes - /// - /// Hash and look up is quicker of course - /// - [ShortRunJob] - public class CacheBenchmarks : BaseBenchmark - { - private readonly string query = - @"{ - movie(id: ""077b3041-307a-42ba-9ffe-1121fcfc918b"") { - id name released - director { - id name dob - } - actors { - id name dob - } - } - }"; - private readonly GraphQLCompiler graphQLCompiler; - private readonly QueryRequest gql; - private readonly QueryCache queryCache; - - public CacheBenchmarks() - { - graphQLCompiler = new GraphQLCompiler(Schema); - gql = new QueryRequest { Query = query }; - - queryCache = new QueryCache(); - queryCache.AddCompiledQuery(query, new GraphQLDocument(Schema)); - } - - [Benchmark] - public void FirstStageCompile() - { - graphQLCompiler.Compile(gql); - } - - [Benchmark] - public void HashAndLookup() - { - queryCache.GetCompiledQuery(query, null); - } - } -} diff --git a/src/Benchmarks/CompileStagesBenchmarks.cs b/src/Benchmarks/CompileAllStagesBenchmarks.cs similarity index 59% rename from src/Benchmarks/CompileStagesBenchmarks.cs rename to src/Benchmarks/CompileAllStagesBenchmarks.cs index 9746e0dd..953eb92d 100644 --- a/src/Benchmarks/CompileStagesBenchmarks.cs +++ b/src/Benchmarks/CompileAllStagesBenchmarks.cs @@ -7,19 +7,7 @@ namespace Benchmarks; /// /// Comparing the speed and allocation of compiling queries with and without caching /// On a Apple M1 Max 64GB ram -/// Command: `dotnet run -c Release` -/// -/// 4.1.2 -/// | Method | Mean | Error | StdDev | Gen 0 | Allocated | -/// |--------------- |----------:|---------:|---------:|--------:|----------:| -/// | CompileNoCache | 104.25 us | 0.439 us | 0.389 us | 36.2549 | 74 KB | -/// | CompileCache | 80.00 us | 0.236 us | 0.221 us | 27.5879 | 57 KB | -/// -/// 4.3.1 (4.2 was basically the same) -/// | Method | Mean | Error | StdDev | Gen 0 | Allocated | -/// |--------------- |---------:|--------:|--------:|--------:|----------:| -/// | CompileNoCache | 142.4 us | 1.37 us | 1.28 us | 49.8047 | 102 KB | -/// | CompileCache | 109.3 us | 0.47 us | 0.44 us | 38.8184 | 79 KB | +/// Command: `dotnet run -c Debug --framework net8.0` to skip execution /// /// 5.2.1 (5.0 & 5.1 were basically the same) /// | Method | Mean | Error | StdDev | Gen 0 | Allocated | @@ -32,30 +20,39 @@ namespace Benchmarks; /// |--------------- |----------:|---------:|---------:|--------:|----------:| /// | CompileNoCache | 125.85 us | 0.650 us | 0.543 us | 44.4336 | 91 KB | /// | CompileCache | 97.15 us | 0.396 us | 0.371 us | 33.9355 | 69 KB | +/// +/// 5.6.0 with net9.0 +/// On a Apple M1 Max 64GB ram +/// Command: `dotnet run -c Debug --framework net8.0` to skip execution +/// +/// | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +/// |--------------- |---------:|---------:|---------:|--------:|-------:|----------:| +/// | CompileNoCache | 97.04 us | 0.795 us | 0.744 us | 15.6250 | 0.9766 | 95.79 KB | +/// | CompileCache | 80.42 us | 0.190 us | 0.177 us | 12.5732 | 0.4883 | 77.08 KB | /// [MemoryDiagnoser] -public class CompileStagesBenchmarks : BaseBenchmark +public class CompileAllStagesBenchmarks : BaseBenchmark { private readonly string query = @"{ - movie(id: ""077b3041-307a-42ba-9ffe-1121fcfc918b"") { + movie(id: ""077b3041-307a-42ba-9ffe-1121fcfc918b"") { + id name released + director { + id name dob + directorOf { id name released - director { - id name dob - directorOf { - id name released - } - } - actors { - id name dob - } } - }"; + } + actors { + id name dob + } + } + }"; private readonly QueryRequest gql; private readonly BenchmarkContext context; - public CompileStagesBenchmarks() + public CompileAllStagesBenchmarks() { gql = new QueryRequest { Query = query }; context = GetContext(); diff --git a/src/Benchmarks/CompileFiltersBenchmarks.cs b/src/Benchmarks/CompileFiltersBenchmarks.cs index 89f685cf..057611a9 100644 --- a/src/Benchmarks/CompileFiltersBenchmarks.cs +++ b/src/Benchmarks/CompileFiltersBenchmarks.cs @@ -26,6 +26,14 @@ namespace Benchmarks; /// | PlainDbSet | 20.73 us | 0.079 us | 0.066 us | 7.9346 | 16 KB | /// | SetOfBasicWhereStatements | 158.70 us | 0.547 us | 0.511 us | 41.5039 | 85 KB | /// | LargerSetOfWhereWhens | 2,515.82 us | 5.926 us | 5.543 us | 457.0313 | 934 KB | +/// +/// 5.6.0 with net9.0 +/// +/// | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +/// |-------------------------- |------------:|---------:|---------:|---------:|--------:|----------:| +/// | PlainDbSet | 18.98 us | 0.091 us | 0.200 us | 3.2959 | - | 20.83 KB | +/// | SetOfBasicWhereStatements | 70.65 us | 0.208 us | 0.194 us | 11.9629 | 0.3662 | 73.73 KB | +/// | LargerSetOfWhereWhens | 1,055.51 us | 1.878 us | 1.757 us | 123.0469 | 23.4375 | 765.25 KB | /// [MemoryDiagnoser] public class CompileFiltersBenchmarks : BaseBenchmark diff --git a/src/Benchmarks/CompileGqlDocumentOnlyBenchmarks.cs b/src/Benchmarks/CompileGqlDocumentOnlyBenchmarks.cs new file mode 100644 index 00000000..0999d85e --- /dev/null +++ b/src/Benchmarks/CompileGqlDocumentOnlyBenchmarks.cs @@ -0,0 +1,129 @@ +using System.Linq; +using BenchmarkDotNet.Attributes; +using EntityGraphQL; +using EntityGraphQL.Compiler; +using EntityGraphQL.Extensions; + +namespace Benchmarks; + +/// +/// Benchmarks to test just the string graphql document to EntityGraphQL IGraphQLNode compilation. +/// Not to the expression that will be executed +/// +/// BenchmarkDotNet v0.14.0, macOS Sequoia 15.1 (24B83) [Darwin 24.1.0] +/// Apple M1 Max, 1 CPU, 10 logical and 10 physical cores +/// .NET SDK 9.0.100 +/// +/// 5.6.0 - HotChocolate.Language 13.9.11 +/// +/// | Method | Job | Toolchain | IterationCount | LaunchCount | WarmupCount | Mean | Error | StdDev | +/// |-------------------------------------------------- |----------- |----------------------- |--------------- |------------ |------------ |---------:|----------:|----------:| +/// | Query_SingleObjectWithArg | Job-KXSWTB | InProcessEmitToolchain | Default | Default | Default | 5.065 us | 0.0098 us | 0.0092 us | +/// | Query_SingleObjectWithArg_IncludeSubObject | Job-KXSWTB | InProcessEmitToolchain | Default | Default | Default | 6.470 us | 0.0129 us | 0.0108 us | +/// | Query_SingleObjectWithArg_IncludeSubObjectAndList | Job-KXSWTB | InProcessEmitToolchain | Default | Default | Default | 7.895 us | 0.0152 us | 0.0127 us | +/// | Query_List | Job-KXSWTB | InProcessEmitToolchain | Default | Default | Default | 2.462 us | 0.0069 us | 0.0061 us | +/// | Query_ListWithTakeArg | Job-KXSWTB | InProcessEmitToolchain | Default | Default | Default | 8.604 us | 0.0080 us | 0.0075 us | +/// | Query_SingleObjectWithArg | ShortRun | Default | 3 | 1 | 3 | 4.910 us | 0.3644 us | 0.0200 us | +/// | Query_SingleObjectWithArg_IncludeSubObject | ShortRun | Default | 3 | 1 | 3 | 6.357 us | 0.2872 us | 0.0157 us | +/// | Query_SingleObjectWithArg_IncludeSubObjectAndList | ShortRun | Default | 3 | 1 | 3 | 7.760 us | 0.0774 us | 0.0042 us | +/// | Query_List | ShortRun | Default | 3 | 1 | 3 | 2.547 us | 0.0216 us | 0.0012 us | +/// | Query_ListWithTakeArg | ShortRun | Default | 3 | 1 | 3 | 8.600 us | 1.3116 us | 0.0719 us | +/// +[ShortRunJob] +public class CompileGqlDocumentOnlyBenchmarks : BaseBenchmark +{ + [Benchmark] + public void Query_SingleObjectWithArg() + { + new GraphQLCompiler(Schema).Compile( + new QueryRequest + { + Query = + @"{ + movie(id: ""433f8132-a7a5-40c9-96c2-e2122fb72e68"") { + id name released + } + }", + } + ); + } + + [Benchmark] + public void Query_SingleObjectWithArg_IncludeSubObject() + { + new GraphQLCompiler(Schema).Compile( + new QueryRequest + { + Query = + @"{ + movie(id: ""1deb79a1-59b1-4360-8d95-04bd7107ad8c"") { + id name released + director { + id name dob + } + } + }", + } + ); + } + + [Benchmark] + public void Query_SingleObjectWithArg_IncludeSubObjectAndList() + { + new GraphQLCompiler(Schema).Compile( + new QueryRequest + { + Query = + @"{ + movie(id: ""077b3041-307a-42ba-9ffe-1121fcfc918b"") { + id name released + director { + id name dob + } + actors { + id name dob + } + } + }", + } + ); + } + + [Benchmark] + public void Query_List() + { + new GraphQLCompiler(Schema).Compile( + new QueryRequest + { + Query = + @"{ + movies { + id name released + } + }", + } + ); + } + + [GlobalSetup(Target = nameof(Query_ListWithTakeArg))] + public void ModifyField() + { + Schema.Query().ReplaceField("movies", new { take = (int?)null }, (ctx, args) => ctx.Movies.Take(args.take), "List of movies"); + } + + [Benchmark] + public void Query_ListWithTakeArg() + { + new GraphQLCompiler(Schema).Compile( + new QueryRequest + { + Query = + @"{ + movies(take: 10) { + id name released + } + }", + } + ); + } +} diff --git a/src/Benchmarks/DataLoader/DataLoader.cs b/src/Benchmarks/DataLoader/DataLoader.cs index 8d00f590..e23d403e 100644 --- a/src/Benchmarks/DataLoader/DataLoader.cs +++ b/src/Benchmarks/DataLoader/DataLoader.cs @@ -2,70 +2,69 @@ using System.Collections.Generic; using System.Linq; -namespace Benchmarks +namespace Benchmarks; + +public class DataLoader { - public class DataLoader + public static void EnsureDbCreated(BenchmarkContext db) { - public static void EnsureDbCreated(BenchmarkContext db) - { - if (db == null) - throw new ArgumentNullException(nameof(db)); + if (db == null) + throw new ArgumentNullException(nameof(db)); - // db.Database.EnsureDeleted(); - // db.Database.EnsureCreated(); - // var movieData = JsonConvert.DeserializeObject>(File.ReadAllText("./DataLoader/moviedata.json")); - // foreach (var movie in movieData.OrderByDescending(m => m.Info.Rating).Take(1000)) - // { - // db.Movies.Add(new Movie - // { - // Id = Guid.NewGuid(), - // Name = movie.Title, - // Released = movie.Info.ReleaseDate, - // Rating = movie.Info.Rating, - // Genre = movie.Info.Genres != null ? GetOrMakeGenre(db, movie.Info.Genres[0]) : null, - // Actors = MakePersons(db, movie.Info.Actors), - // Director = MakePerson(db, movie.Info.Directors?.FirstOrDefault()), - // }); - // db.SaveChanges(); - // } - // var movies = db.Movies.OrderByDescending(m => m.Rating).Take(10).ToList(); - // var top = movies.FirstOrDefault(); - } + // db.Database.EnsureDeleted(); + // db.Database.EnsureCreated(); + // var movieData = JsonConvert.DeserializeObject>(File.ReadAllText("./DataLoader/moviedata.json")); + // foreach (var movie in movieData.OrderByDescending(m => m.Info.Rating).Take(1000)) + // { + // db.Movies.Add(new Movie + // { + // Id = Guid.NewGuid(), + // Name = movie.Title, + // Released = movie.Info.ReleaseDate, + // Rating = movie.Info.Rating, + // Genre = movie.Info.Genres != null ? GetOrMakeGenre(db, movie.Info.Genres[0]) : null, + // Actors = MakePersons(db, movie.Info.Actors), + // Director = MakePerson(db, movie.Info.Directors?.FirstOrDefault()), + // }); + // db.SaveChanges(); + // } + // var movies = db.Movies.OrderByDescending(m => m.Rating).Take(10).ToList(); + // var top = movies.FirstOrDefault(); + } - private static MovieGenre GetOrMakeGenre(BenchmarkContext db, string name) + private static MovieGenre GetOrMakeGenre(BenchmarkContext db, string name) + { + var g = db.Genres.FirstOrDefault(i => i.Name == name); + if (g == null) { - var g = db.Genres.FirstOrDefault(i => i.Name == name); - if (g == null) - { - g = new MovieGenre(name); - db.Genres.Add(g); - } - return g; + g = new MovieGenre(name); + db.Genres.Add(g); } + return g; + } - private static List? MakePersons(BenchmarkContext db, List actors) - { - if (actors == null) - return null; - var actorData = actors.Select(p => MakePerson(db, p)); - return actorData.Where(p => p != null).ToList()!; - } + private static List? MakePersons(BenchmarkContext db, List actors) + { + if (actors == null) + return null; + var actorData = actors.Select(p => MakePerson(db, p)); + return actorData.Where(p => p != null).ToList()!; + } - private static Person? MakePerson(BenchmarkContext db, string fullName) - { - if (fullName == null) - return null; + private static Person? MakePerson(BenchmarkContext db, string fullName) + { + if (fullName == null) + return null; - string[] split = fullName.Split(' '); - string fName = split[0]; - string lName = split.Length > 1 ? split[1] : ""; - var person = db.People.FirstOrDefault(p => p.FirstName == fName && p.LastName == lName); - if (person == null) - { - person = new Person(Guid.NewGuid(), fName, lName, new DateTime(1957, 2, 2), new List()); - db.People.Add(person); - } - return person; + string[] split = fullName.Split(' '); + string fName = split[0]; + string lName = split.Length > 1 ? split[1] : ""; + var person = db.People.FirstOrDefault(p => p.FirstName == fName && p.LastName == lName); + if (person == null) + { + person = new Person(Guid.NewGuid(), fName, lName, new DateTime(1957, 2, 2), new List()); + db.People.Add(person); } + return person; } } diff --git a/src/Benchmarks/DataLoader/MovieData.cs b/src/Benchmarks/DataLoader/MovieData.cs index 21870684..bc28baf7 100644 --- a/src/Benchmarks/DataLoader/MovieData.cs +++ b/src/Benchmarks/DataLoader/MovieData.cs @@ -2,49 +2,48 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Benchmarks -{ - public class MovieData - { - public int Year { get; set; } - public string Title { get; set; } +namespace Benchmarks; - public MovieData(string title, MoveInfo info) - { - Title = title; - Info = info; - } +public class MovieData +{ + public int Year { get; set; } + public string Title { get; set; } - public MoveInfo Info { get; set; } + public MovieData(string title, MoveInfo info) + { + Title = title; + Info = info; } - public class MoveInfo + public MoveInfo Info { get; set; } +} + +public class MoveInfo +{ + public MoveInfo(List directors, DateTime releaseDate, float rating, List genres, string imageUrl, string plot, int rank, int runningTimeSecs, List actors) { - public MoveInfo(List directors, DateTime releaseDate, float rating, List genres, string imageUrl, string plot, int rank, int runningTimeSecs, List actors) - { - Directors = directors; - ReleaseDate = releaseDate; - Rating = rating; - Genres = genres; - ImageUrl = imageUrl; - Plot = plot; - Rank = rank; - RunningTimeSecs = runningTimeSecs; - Actors = actors; - } - - public List Directors { get; set; } - - [JsonPropertyName("release_date")] - public DateTime ReleaseDate { get; set; } - public float Rating { get; set; } - public List Genres { get; set; } - public string ImageUrl { get; set; } - public string Plot { get; set; } - public int Rank { get; set; } - - [JsonPropertyName("running_time_secs")] - public int RunningTimeSecs { get; set; } - public List Actors { get; set; } + Directors = directors; + ReleaseDate = releaseDate; + Rating = rating; + Genres = genres; + ImageUrl = imageUrl; + Plot = plot; + Rank = rank; + RunningTimeSecs = runningTimeSecs; + Actors = actors; } + + public List Directors { get; set; } + + [JsonPropertyName("release_date")] + public DateTime ReleaseDate { get; set; } + public float Rating { get; set; } + public List Genres { get; set; } + public string ImageUrl { get; set; } + public string Plot { get; set; } + public int Rank { get; set; } + + [JsonPropertyName("running_time_secs")] + public int RunningTimeSecs { get; set; } + public List Actors { get; set; } } diff --git a/src/Benchmarks/EqlBenchmarks.cs b/src/Benchmarks/EqlBenchmarks.cs index 0fc9a393..1884de93 100644 --- a/src/Benchmarks/EqlBenchmarks.cs +++ b/src/Benchmarks/EqlBenchmarks.cs @@ -22,6 +22,17 @@ namespace Benchmarks; /// | SimpleExpression | Default | 3 | 1 | 3 | 6.380 us | 0.2524 us | 0.0138 us | /// | ComplexExpression | Default | 3 | 1 | 3 | 24.810 us | 2.7608 us | 0.1513 us | /// | ComplexWithMethodCallExpression | Default | 3 | 1 | 3 | 45.103 us | 5.0855 us | 0.2788 us | +/// +/// 5.6.0 with net9.0 Parlot 1.1.0 +/// +/// | Method | Toolchain | IterationCount | LaunchCount | WarmupCount | Mean | Error | StdDev | +/// |-------------------------------- |----------------------- |--------------- |------------ |------------ |---------:|----------:|---------:| +/// | SimpleExpression | InProcessEmitToolchain | Default | Default | Default | 11.09 us | 0.075 us | 0.070 us | +/// | ComplexExpression | InProcessEmitToolchain | Default | Default | Default | 32.07 us | 0.233 us | 0.218 us | +/// | ComplexWithMethodCallExpression | InProcessEmitToolchain | Default | Default | Default | 54.36 us | 0.181 us | 0.160 us | +/// | SimpleExpression | Default | 3 | 1 | 3 | 10.62 us | 3.848 us | 0.211 us | +/// | ComplexExpression | Default | 3 | 1 | 3 | 32.84 us | 23.009 us | 1.261 us | +/// | ComplexWithMethodCallExpression | Default | 3 | 1 | 3 | 53.69 us | 9.651 us | 0.529 us | /// [ShortRunJob] public class EqlBenchmarks : BaseBenchmark diff --git a/src/Benchmarks/Migrations/20210823055744_init.cs b/src/Benchmarks/Migrations/20210823055744_init.cs index e9d87db4..364feb1a 100644 --- a/src/Benchmarks/Migrations/20210823055744_init.cs +++ b/src/Benchmarks/Migrations/20210823055744_init.cs @@ -1,83 +1,82 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; -namespace Benchmarks.Migrations +namespace Benchmarks.Migrations; + +public partial class Init : Migration { - public partial class Init : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Genres", - columns: table => new { Name = table.Column(type: "TEXT", nullable: false) }, - constraints: table => - { - table.PrimaryKey("PK_Genres", x => x.Name); - } - ); + migrationBuilder.CreateTable( + name: "Genres", + columns: table => new { Name = table.Column(type: "TEXT", nullable: false) }, + constraints: table => + { + table.PrimaryKey("PK_Genres", x => x.Name); + } + ); - migrationBuilder.CreateTable( - name: "Movies", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - Rating = table.Column(type: "REAL", nullable: false), - Released = table.Column(type: "TEXT", nullable: false), - DirectorId = table.Column(type: "TEXT", nullable: true), - GenreName = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Movies", x => x.Id); - table.ForeignKey(name: "FK_Movies_Genres_GenreName", column: x => x.GenreName, principalTable: "Genres", principalColumn: "Name", onDelete: ReferentialAction.Restrict); - } - ); + migrationBuilder.CreateTable( + name: "Movies", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Rating = table.Column(type: "REAL", nullable: false), + Released = table.Column(type: "TEXT", nullable: false), + DirectorId = table.Column(type: "TEXT", nullable: true), + GenreName = table.Column(type: "TEXT", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_Movies", x => x.Id); + table.ForeignKey(name: "FK_Movies_Genres_GenreName", column: x => x.GenreName, principalTable: "Genres", principalColumn: "Name", onDelete: ReferentialAction.Restrict); + } + ); - migrationBuilder.CreateTable( - name: "People", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - FirstName = table.Column(type: "TEXT", nullable: true), - LastName = table.Column(type: "TEXT", nullable: true), - Dob = table.Column(type: "TEXT", nullable: false), - MovieId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_People", x => x.Id); - table.ForeignKey(name: "FK_People_Movies_MovieId", column: x => x.MovieId, principalTable: "Movies", principalColumn: "Id", onDelete: ReferentialAction.Restrict); - } - ); + migrationBuilder.CreateTable( + name: "People", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", nullable: true), + LastName = table.Column(type: "TEXT", nullable: true), + Dob = table.Column(type: "TEXT", nullable: false), + MovieId = table.Column(type: "TEXT", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_People", x => x.Id); + table.ForeignKey(name: "FK_People_Movies_MovieId", column: x => x.MovieId, principalTable: "Movies", principalColumn: "Id", onDelete: ReferentialAction.Restrict); + } + ); - migrationBuilder.CreateIndex(name: "IX_Movies_DirectorId", table: "Movies", column: "DirectorId"); + migrationBuilder.CreateIndex(name: "IX_Movies_DirectorId", table: "Movies", column: "DirectorId"); - migrationBuilder.CreateIndex(name: "IX_Movies_GenreName", table: "Movies", column: "GenreName"); + migrationBuilder.CreateIndex(name: "IX_Movies_GenreName", table: "Movies", column: "GenreName"); - migrationBuilder.CreateIndex(name: "IX_People_MovieId", table: "People", column: "MovieId"); + migrationBuilder.CreateIndex(name: "IX_People_MovieId", table: "People", column: "MovieId"); - migrationBuilder.AddForeignKey( - name: "FK_Movies_People_DirectorId", - table: "Movies", - column: "DirectorId", - principalTable: "People", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict - ); - } + migrationBuilder.AddForeignKey( + name: "FK_Movies_People_DirectorId", + table: "Movies", + column: "DirectorId", + principalTable: "People", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict + ); + } - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey(name: "FK_Movies_Genres_GenreName", table: "Movies"); + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey(name: "FK_Movies_Genres_GenreName", table: "Movies"); - migrationBuilder.DropForeignKey(name: "FK_Movies_People_DirectorId", table: "Movies"); + migrationBuilder.DropForeignKey(name: "FK_Movies_People_DirectorId", table: "Movies"); - migrationBuilder.DropTable(name: "Genres"); + migrationBuilder.DropTable(name: "Genres"); - migrationBuilder.DropTable(name: "People"); + migrationBuilder.DropTable(name: "People"); - migrationBuilder.DropTable(name: "Movies"); - } + migrationBuilder.DropTable(name: "Movies"); } } diff --git a/src/Benchmarks/Model/BenchmarkContext.cs b/src/Benchmarks/Model/BenchmarkContext.cs index ef70aabf..68e3493c 100644 --- a/src/Benchmarks/Model/BenchmarkContext.cs +++ b/src/Benchmarks/Model/BenchmarkContext.cs @@ -1,26 +1,25 @@ using Microsoft.EntityFrameworkCore; -namespace Benchmarks +namespace Benchmarks; + +public class BenchmarkContext : DbContext { - public class BenchmarkContext : DbContext - { - public DbSet Movies => Set(); - public DbSet People => Set(); - public DbSet Genres => Set(); + public DbSet Movies => Set(); + public DbSet People => Set(); + public DbSet Genres => Set(); - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=movies.db"); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder.UseSqlite("Data Source=movies.db"); + } - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity().HasKey(d => d.Id); - builder.Entity().HasKey(d => d.Name); - builder.Entity().HasKey(d => d.Id); - builder.Entity().HasOne(d => d.Director); - builder.Entity().HasMany(d => d.DirectorOf); - } + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().HasKey(d => d.Id); + builder.Entity().HasKey(d => d.Name); + builder.Entity().HasKey(d => d.Id); + builder.Entity().HasOne(d => d.Director); + builder.Entity().HasMany(d => d.DirectorOf); } } diff --git a/src/Benchmarks/Model/Movie.cs b/src/Benchmarks/Model/Movie.cs index 4ad743b6..b020ce6c 100644 --- a/src/Benchmarks/Model/Movie.cs +++ b/src/Benchmarks/Model/Movie.cs @@ -1,27 +1,33 @@ using System; using System.Collections.Generic; -namespace Benchmarks +namespace Benchmarks; + +public class Movie { - public class Movie + public Movie() { - public Movie(Guid id, string name, float rating, DateTime released, Person director, List actors, MovieGenre genre) - { - Id = id; - Name = name; - Rating = rating; - Released = released; - Director = director; - Actors = actors; - Genre = genre; - } + Name = string.Empty; + Director = new Person(); + Genre = new MovieGenre("Unknown"); + } - public Guid Id { get; set; } - public string Name { get; set; } - public float Rating { get; set; } - public DateTime Released { get; set; } - public Person Director { get; set; } - public List Actors { get; set; } - public MovieGenre Genre { get; set; } + public Movie(Guid id, string name, float rating, DateTime released, Person director, List actors, MovieGenre genre) + { + Id = id; + Name = name; + Rating = rating; + Released = released; + Director = director; + Actors = actors; + Genre = genre; } + + public Guid Id { get; set; } + public string Name { get; set; } + public float Rating { get; set; } + public DateTime Released { get; set; } + public Person Director { get; set; } + public List Actors { get; set; } = []; + public MovieGenre Genre { get; set; } } diff --git a/src/Benchmarks/Model/MovieGenre.cs b/src/Benchmarks/Model/MovieGenre.cs index 0ab538fe..b7f53b94 100644 --- a/src/Benchmarks/Model/MovieGenre.cs +++ b/src/Benchmarks/Model/MovieGenre.cs @@ -1,12 +1,11 @@ -namespace Benchmarks +namespace Benchmarks; + +public class MovieGenre { - public class MovieGenre + public MovieGenre(string name) { - public MovieGenre(string name) - { - Name = name; - } - - public string Name { get; set; } + Name = name; } + + public string Name { get; set; } } diff --git a/src/Benchmarks/Model/Person.cs b/src/Benchmarks/Model/Person.cs index 6d4a4b4e..ca34dd52 100644 --- a/src/Benchmarks/Model/Person.cs +++ b/src/Benchmarks/Model/Person.cs @@ -1,23 +1,28 @@ using System; using System.Collections.Generic; -namespace Benchmarks +namespace Benchmarks; + +public class Person { - public class Person + public Person() { - public Person(Guid id, string firstName, string lastName, DateTime dob, IEnumerable directorOf) - { - Id = id; - FirstName = firstName; - LastName = lastName; - Dob = dob; - DirectorOf = directorOf; - } + FirstName = string.Empty; + LastName = string.Empty; + } - public Guid Id { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public DateTime Dob { get; set; } - public IEnumerable DirectorOf { get; set; } + public Person(Guid id, string firstName, string lastName, DateTime dob, IEnumerable directorOf) + { + Id = id; + FirstName = firstName; + LastName = lastName; + Dob = dob; + DirectorOf = directorOf; } + + public Guid Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public DateTime Dob { get; set; } + public IEnumerable DirectorOf { get; set; } = []; } diff --git a/src/Benchmarks/PagingBenchmarks.cs b/src/Benchmarks/PagingBenchmarks.cs index 44b0a15b..fe07a9e0 100644 --- a/src/Benchmarks/PagingBenchmarks.cs +++ b/src/Benchmarks/PagingBenchmarks.cs @@ -4,50 +4,50 @@ using EntityGraphQL.Extensions; using EntityGraphQL.Schema.FieldExtensions; -namespace Benchmarks +namespace Benchmarks; + +/// +/// Testing different ways of paging data +/// +[ShortRunJob] +public class PagingBenchmarks : BaseBenchmark { - /// - /// Testing different ways of paging data - /// - [ShortRunJob] - public class PagingBenchmarks : BaseBenchmark + [GlobalSetup] + public void GlobalSetup() { - [GlobalSetup] - public void GlobalSetup() - { - Schema.Query().AddField("moviesTakeSkip", new { take = (int?)null, skip = (int?)null, }, (ctx, args) => ctx.Movies.OrderBy(i => i.Id).Skip(args.skip).Take(args.take), "Movies"); + Schema.Query().AddField("moviesTakeSkip", new { take = (int?)null, skip = (int?)null }, (ctx, args) => ctx.Movies.OrderBy(i => i.Id).Skip(args.skip).Take(args.take), "Movies"); - Schema.Query().AddField("moviesConnection", ctx => ctx.Movies.OrderBy(i => i.Id), "Movies").UseConnectionPaging(); + Schema.Query().AddField("moviesConnection", ctx => ctx.Movies.OrderBy(i => i.Id), "Movies").UseConnectionPaging(); - Schema.Query().ReplaceField("moviesOffset", ctx => ctx.Movies.OrderBy(i => i.Id), "Movies").UseOffsetPaging(); - } + Schema.Query().ReplaceField("moviesOffset", ctx => ctx.Movies.OrderBy(i => i.Id), "Movies").UseOffsetPaging(); + } - [Benchmark] - public void NoExtension() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ + [Benchmark] + public void NoExtension() + { + RunQuery( + GetContext(), + new QueryRequest + { + Query = + @"{ moviesTakeSkip(skip: 1 take: 1) { name id } - }" - } - ); - } + }", + } + ); + } - [Benchmark] - public void ConnectionPaging() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ + [Benchmark] + public void ConnectionPaging() + { + RunQuery( + GetContext(), + new QueryRequest + { + Query = + @"{ moviesConnection(last: 3 before: ""NA=="") { edges { node { @@ -63,20 +63,20 @@ name id } totalCount } - }" - } - ); - } + }", + } + ); + } - [Benchmark] - public void OffsetPaging() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ + [Benchmark] + public void OffsetPaging() + { + RunQuery( + GetContext(), + new QueryRequest + { + Query = + @"{ moviesOffset(skip: 1 take: 1) { items { name id @@ -85,9 +85,8 @@ name id hasNextPage hasPreviousPage } - }" - } - ); - } + }", + } + ); } } diff --git a/src/Benchmarks/ParameterReplacerBenchmarks.cs b/src/Benchmarks/ParameterReplacerBenchmarks.cs index da100d14..17921415 100644 --- a/src/Benchmarks/ParameterReplacerBenchmarks.cs +++ b/src/Benchmarks/ParameterReplacerBenchmarks.cs @@ -7,114 +7,113 @@ using EntityGraphQL.Extensions; using Microsoft.EntityFrameworkCore; -namespace Benchmarks +namespace Benchmarks; + +[MemoryDiagnoser] +public class ParameterReplacerBenchmarks : BaseBenchmark { - [MemoryDiagnoser] - public class ParameterReplacerBenchmarks : BaseBenchmark + public class Args + { + public string? Name { get; set; } + public float? RatingMin { get; set; } + public float? RatingMax { get; set; } + public DateTime? ReleasedBefore { get; set; } + public DateTime? ReleasedAfter { get; set; } + public Guid? DirectorId { get; set; } + public string? DirectorName { get; set; } + public Guid? ActorId { get; set; } + public string? ActorName { get; set; } + public string[] Genres { get; set; } = Array.Empty(); + } + + readonly Expression>> _node; + readonly Expression>> _node2; + readonly Expression>> _node3; + readonly Expression>> _node4; + + public ParameterReplacerBenchmarks() + { + _node = (ctx) => ctx.Movies; + + _node2 = (ctx) => + ctx.Movies.Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true); + + _node3 = (ctx, args) => + ctx + .Movies.WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) + .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) + .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), !string.IsNullOrWhiteSpace(args.ActorName)) + .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) + .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) + .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) + .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) + .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) + .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres != null); + + _node4 = (ctx, args) => + ctx.Set() + .AsSplitQuery() + .AsNoTracking() + .IgnoreQueryFilters() + .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) + .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) + .WhereWhen(i => i.Director.Id == args.DirectorId, args.DirectorId.HasValue) + .WhereWhen(i => i.Actors.Any(x => x.Id == args.ActorId), !string.IsNullOrWhiteSpace(args.ActorName)) + .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), args.ActorId.HasValue) + .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) + .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) + .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) + .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) + .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres.Length > 0) + .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) + .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) + .WhereWhen(i => i.Director.Id == args.DirectorId, args.DirectorId.HasValue) + .WhereWhen(i => i.Actors.Any(x => x.Id == args.ActorId), !string.IsNullOrWhiteSpace(args.ActorName)) + .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), args.ActorId.HasValue) + .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) + .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) + .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) + .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) + .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres.Length > 0); + } + + [Benchmark] + public void PlainDbSet() + { + var replacer = new ParameterReplacer(); + + var newParam = Expression.Parameter(typeof(BenchmarkContext)); + + replacer.Replace(_node, _node.Parameters.First(), newParam); + } + + [Benchmark] + public void SetOfBasicWhereStatements() { - public class Args - { - public string? Name { get; set; } - public float? RatingMin { get; set; } - public float? RatingMax { get; set; } - public DateTime? ReleasedBefore { get; set; } - public DateTime? ReleasedAfter { get; set; } - public Guid? DirectorId { get; set; } - public string? DirectorName { get; set; } - public Guid? ActorId { get; set; } - public string? ActorName { get; set; } - public string[] Genres { get; set; } = Array.Empty(); - } - - readonly Expression>> _node; - readonly Expression>> _node2; - readonly Expression>> _node3; - readonly Expression>> _node4; - - public ParameterReplacerBenchmarks() - { - _node = (ctx) => ctx.Movies; - - _node2 = (ctx) => - ctx.Movies.Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true).Where(x => true); - - _node3 = (ctx, args) => - ctx - .Movies.WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) - .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) - .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), !string.IsNullOrWhiteSpace(args.ActorName)) - .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) - .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) - .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) - .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) - .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) - .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres != null); - - _node4 = (ctx, args) => - ctx.Set() - .AsSplitQuery() - .AsNoTracking() - .IgnoreQueryFilters() - .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) - .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) - .WhereWhen(i => i.Director.Id == args.DirectorId, args.DirectorId.HasValue) - .WhereWhen(i => i.Actors.Any(x => x.Id == args.ActorId), !string.IsNullOrWhiteSpace(args.ActorName)) - .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), args.ActorId.HasValue) - .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) - .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) - .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) - .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) - .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres.Length > 0) - .WhereWhen(i => i.Name == args.Name, !string.IsNullOrWhiteSpace(args.Name)) - .WhereWhen(i => i.Director.FirstName == args.DirectorName, !string.IsNullOrWhiteSpace(args.DirectorName)) - .WhereWhen(i => i.Director.Id == args.DirectorId, args.DirectorId.HasValue) - .WhereWhen(i => i.Actors.Any(x => x.Id == args.ActorId), !string.IsNullOrWhiteSpace(args.ActorName)) - .WhereWhen(i => i.Actors.Any(x => x.FirstName == args.ActorName), args.ActorId.HasValue) - .WhereWhen(i => i.Rating > args.RatingMin, args.RatingMin.HasValue) - .WhereWhen(i => i.Rating < args.RatingMax, args.RatingMax.HasValue) - .WhereWhen(i => i.Released > args.ReleasedAfter, args.ReleasedAfter.HasValue) - .WhereWhen(i => i.Released < args.ReleasedBefore, args.ReleasedBefore.HasValue) - .WhereWhen(i => args.Genres.Contains(i.Genre.Name), args.Genres.Length > 0); - } - - [Benchmark] - public void PlainDbSet() - { - var replacer = new ParameterReplacer(); - - var newParam = Expression.Parameter(typeof(BenchmarkContext)); - - replacer.Replace(_node, _node.Parameters.First(), newParam); - } - - [Benchmark] - public void SetOfBasicWhereStatements() - { - var replacer = new ParameterReplacer(); - - var newParam = Expression.Parameter(typeof(BenchmarkContext)); - - replacer.Replace(_node2, _node2.Parameters.First(), newParam); - } - - [Benchmark] - public void SetOfRealisticWhereWhens() - { - var replacer = new ParameterReplacer(); - - var newParam = Expression.Parameter(typeof(BenchmarkContext)); - - replacer.Replace(_node3, _node3.Parameters.First(), newParam); - } - - [Benchmark] - public void LargerSetOfWhereWhens() - { - var replacer = new ParameterReplacer(); - - var newParam = Expression.Parameter(typeof(BenchmarkContext)); - - replacer.Replace(_node4, _node4.Parameters.First(), newParam); - } + var replacer = new ParameterReplacer(); + + var newParam = Expression.Parameter(typeof(BenchmarkContext)); + + replacer.Replace(_node2, _node2.Parameters.First(), newParam); + } + + [Benchmark] + public void SetOfRealisticWhereWhens() + { + var replacer = new ParameterReplacer(); + + var newParam = Expression.Parameter(typeof(BenchmarkContext)); + + replacer.Replace(_node3, _node3.Parameters.First(), newParam); + } + + [Benchmark] + public void LargerSetOfWhereWhens() + { + var replacer = new ParameterReplacer(); + + var newParam = Expression.Parameter(typeof(BenchmarkContext)); + + replacer.Replace(_node4, _node4.Parameters.First(), newParam); } } diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs index ebd94371..84db66d6 100644 --- a/src/Benchmarks/Program.cs +++ b/src/Benchmarks/Program.cs @@ -1,5 +1,4 @@ -using System; -using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; namespace Benchmarks; diff --git a/src/Benchmarks/QueryBenchmarks.cs b/src/Benchmarks/QueryBenchmarks.cs deleted file mode 100644 index 6e57ba91..00000000 --- a/src/Benchmarks/QueryBenchmarks.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Linq; -using BenchmarkDotNet.Attributes; -using EntityGraphQL; -using EntityGraphQL.Extensions; -using EntityGraphQL.Schema; - -namespace Benchmarks; - -/// -/// Was used to test if the replacement of the Antlr4 parser of the GraphQL query document was faster. Yes it was! -/// -[ShortRunJob] -public class QueryBenchmarks : BaseBenchmark -{ - [Benchmark] - public void Query_SingleObjectWithArg() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ - movie(id: ""433f8132-a7a5-40c9-96c2-e2122fb72e68"") { - id name released - } - }" - }, - new ExecutionOptions - { -#if DEBUG - NoExecution = true -#endif - } - ); - } - - [Benchmark] - public void Query_SingleObjectWithArg_IncludeSubObject() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ - movie(id: ""1deb79a1-59b1-4360-8d95-04bd7107ad8c"") { - id name released - director { - id name dob - } - } - }" - }, - new ExecutionOptions - { -#if DEBUG - NoExecution = true -#endif - } - ); - } - - [Benchmark] - public void Query_SingleObjectWithArg_IncludeSubObjectAndList() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ - movie(id: ""077b3041-307a-42ba-9ffe-1121fcfc918b"") { - id name released - director { - id name dob - } - actors { - id name dob - } - } - }" - }, - new ExecutionOptions - { -#if DEBUG - NoExecution = true -#endif - } - ); - } - - [Benchmark] - public void Query_List() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ - movies { - id name released - } - }" - }, - new ExecutionOptions - { -#if DEBUG - NoExecution = true -#endif - } - ); - } - - [GlobalSetup(Target = nameof(Query_ListWithTakeArg))] - public void ModifyField() - { - Schema.Query().ReplaceField("movies", new { take = (int?)null }, (ctx, args) => ctx.Movies.Take(args.take), "List of movies"); - } - - [Benchmark] - public void Query_ListWithTakeArg() - { - RunQuery( - GetContext(), - new QueryRequest - { - Query = - @"{ - movies(take: 10) { - id name released - } - }" - }, - new ExecutionOptions - { -#if DEBUG - NoExecution = true -#endif - } - ); - } -} diff --git a/src/EntityGraphQL.AspNet/EntityGraphQL.AspNet.csproj b/src/EntityGraphQL.AspNet/EntityGraphQL.AspNet.csproj index abfe829c..734f265e 100644 --- a/src/EntityGraphQL.AspNet/EntityGraphQL.AspNet.csproj +++ b/src/EntityGraphQL.AspNet/EntityGraphQL.AspNet.csproj @@ -1,11 +1,11 @@ - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 EntityGraphQL.AspNet EntityGraphQL.AspNet - 12 - 5.5.3 + 13 + 5.6.0 Contains ASP.NET extensions and middleware for EntityGraphQL Luke Murray https://github.com/lukemurray/EntityGraphQL @@ -17,6 +17,8 @@ enable Recommended true + + true diff --git a/src/EntityGraphQL.AspNet/Extensions/AddGraphQLOptions.cs b/src/EntityGraphQL.AspNet/Extensions/AddGraphQLOptions.cs index 9c3cc8a4..96e7a369 100644 --- a/src/EntityGraphQL.AspNet/Extensions/AddGraphQLOptions.cs +++ b/src/EntityGraphQL.AspNet/Extensions/AddGraphQLOptions.cs @@ -1,32 +1,31 @@ using System; using EntityGraphQL.Schema; -namespace EntityGraphQL.AspNet +namespace EntityGraphQL.AspNet; + +public class AddGraphQLOptions : SchemaBuilderOptions { - public class AddGraphQLOptions : SchemaBuilderOptions - { - /// - /// If true the schema will be built via reflection on the context type. You can customise this with the properties inherited from SchemaBuilderOptions - /// If false the schema will be created with the TSchemaContext as it's context but will be empty of fields/types. - /// You can fully populate it in the ConfigureSchema callback - /// - public bool AutoBuildSchemaFromContext { get; set; } = true; + /// + /// If true the schema will be built via reflection on the context type. You can customise this with the properties inherited from SchemaBuilderOptions + /// If false the schema will be created with the TSchemaContext as it's context but will be empty of fields/types. + /// You can fully populate it in the ConfigureSchema callback + /// + public bool AutoBuildSchemaFromContext { get; set; } = true; - /// - /// Overwrite the default field naming convention. (Default is lowerCaseFields) - /// - public Func FieldNamer { get; set; } = SchemaBuilderSchemaOptions.DefaultFieldNamer; + /// + /// Overwrite the default field naming convention. (Default is lowerCaseFields) + /// + public Func FieldNamer { get; set; } = SchemaBuilderSchemaOptions.DefaultFieldNamer; - /// - /// Called after the schema object is created but before the context is reflected into it. Use for set up of type mappings or - /// anything that may be needed for the schema to be built correctly. - /// - public Action>? PreBuildSchemaFromContext { get; set; } + /// + /// Called after the schema object is created but before the context is reflected into it. Use for set up of type mappings or + /// anything that may be needed for the schema to be built correctly. + /// + public Action>? PreBuildSchemaFromContext { get; set; } - /// - /// Called after the context has been reflected into a schema to allow further customisation. - /// Or use this to configure the whole schema if AutoBuildSchemaFromContext is false. - /// - public Action>? ConfigureSchema { get; set; } - } + /// + /// Called after the context has been reflected into a schema to allow further customisation. + /// Or use this to configure the whole schema if AutoBuildSchemaFromContext is false. + /// + public Action>? ConfigureSchema { get; set; } } diff --git a/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLRequestDeserializer.cs b/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLRequestDeserializer.cs index e8d7a6e5..d1bb5686 100644 --- a/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLRequestDeserializer.cs +++ b/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLRequestDeserializer.cs @@ -19,7 +19,7 @@ public DefaultGraphQLRequestDeserializer(JsonSerializerOptions? jsonOptions = nu this.jsonOptions = jsonOptions; else { - this.jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + this.jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; this.jsonOptions.Converters.Add(new JsonStringEnumConverter()); } } diff --git a/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLResponseSerializer.cs b/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLResponseSerializer.cs index ceff6294..e0591d3b 100644 --- a/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLResponseSerializer.cs +++ b/src/EntityGraphQL.AspNet/Extensions/DefaultGraphQLResponseSerializer.cs @@ -19,7 +19,7 @@ public DefaultGraphQLResponseSerializer(JsonSerializerOptions? jsonOptions = nul this.jsonOptions = jsonOptions; else { - this.jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + this.jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; this.jsonOptions.Converters.Add(new JsonStringEnumConverter()); this.jsonOptions.Converters.Add(new RuntimeTypeJsonConverter()); } diff --git a/src/EntityGraphQL.AspNet/Extensions/EntityGraphQLEndpointRouteExtensions.cs b/src/EntityGraphQL.AspNet/Extensions/EntityGraphQLEndpointRouteExtensions.cs index d5d8cf2e..53a1157c 100644 --- a/src/EntityGraphQL.AspNet/Extensions/EntityGraphQLEndpointRouteExtensions.cs +++ b/src/EntityGraphQL.AspNet/Extensions/EntityGraphQLEndpointRouteExtensions.cs @@ -1,50 +1,73 @@ using System; +using System.Linq; using EntityGraphQL.Schema; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -namespace EntityGraphQL.AspNet -{ - public static class EntityGraphQLEndpointRouteExtensions - { - private const string APP_JSON_TYPE_START = "application/json"; +namespace EntityGraphQL.AspNet; - // private const string APP_GQL_TYPE_START = "application/graphql-response+json"; +public static class EntityGraphQLEndpointRouteExtensions +{ + private const string APP_JSON_TYPE_START = "application/json"; + private const string APP_GQL_TYPE_START = "application/graphql-response+json"; - public static IEndpointRouteBuilder MapGraphQL( - this IEndpointRouteBuilder builder, - string path = "graphql", - ExecutionOptions? options = null, - Action? configureEndpoint = null - ) - { - path = path.TrimEnd('/'); - var requestPipeline = builder.CreateApplicationBuilder(); - var postEndpoint = builder.MapPost( - path, - async context => + /// + /// Add the GraphQL endpoint to the route builder + /// + /// The base query type to build the schema from + /// The IEndpointRouteBuilder + /// The path to create the route at. Defaults to `graphql` + /// ExecutionOptions to use when executing queries + /// Callback to continue modifying the endpoint via the IEndpointConventionBuilder interface + /// Defaults to false. If true it will return status code 200 for queries with + /// errors as per https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md + /// + /// + public static IEndpointRouteBuilder MapGraphQL( + this IEndpointRouteBuilder builder, + string path = "graphql", + ExecutionOptions? options = null, + Action? configureEndpoint = null, + bool followSpec = false + ) + { + path = path.TrimEnd('/'); + var postEndpoint = builder.MapPost( + path, + async context => + { + var acceptedContentType = context.Request.Headers.Accept; + if (followSpec) { - // var acceptedContentType = context.Request.Headers.Accept; // https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md - // if (!requestedContentType.Contains(APP_JSON_TYPE) && !requestedContentType.Contains(APP_GQL_TYPE)) - // { - // context.Response.StatusCode = StatusCodes.Status406NotAcceptable; - // return; - // } - if (context.Request.ContentType?.StartsWith(APP_JSON_TYPE_START, StringComparison.InvariantCulture) == false) + // "May reply with error if not supplied" choosing not to + if ( + acceptedContentType.Count > 0 + && !acceptedContentType.Any(h => h?.StartsWith(APP_JSON_TYPE_START, StringComparison.InvariantCulture) == true) + && !acceptedContentType.Any(h => h?.StartsWith(APP_GQL_TYPE_START, StringComparison.InvariantCulture) == true) + ) { - context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; - return; - } - if (context.Request.ContentLength == null || context.Request.ContentLength == 0) - { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + context.Response.StatusCode = StatusCodes.Status406NotAcceptable; return; } + } + // checking for ContentType == null is technically a breaking change so only do it for the followSpec case until 6.0 + if ((followSpec && context.Request.ContentType == null) || context.Request.ContentType?.StartsWith(APP_JSON_TYPE_START, StringComparison.InvariantCulture) == false) + { + context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return; + } + if (context.Request.ContentLength == null || context.Request.ContentLength == 0) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + return; + } - var deserializer = context.RequestServices.GetRequiredService(); + var deserializer = context.RequestServices.GetRequiredService(); + try + { var query = await deserializer.DeserializeAsync(context.Request.Body); var schema = @@ -52,24 +75,54 @@ public static IEndpointRouteBuilder MapGraphQL( ?? throw new InvalidOperationException( "No SchemaProvider found in the service collection. Make sure you set up your Startup.ConfigureServices() to call AddGraphQLSchema()." ); - var data = await schema.ExecuteRequestAsync(query, context.RequestServices, context.User, options); - // var requestedType = acceptedContentType.FirstOrDefault(t => t?.StartsWith(APP_JSON_TYPE_START, StringComparison.InvariantCulture) == true || t?.StartsWith(APP_GQL_TYPE_START, StringComparison.InvariantCulture) == true); - // TODO 2025-01-01. if this goes forward https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md - // change default to application/graphql-response+json; charset=utf-8 - // context.Response.ContentType = requestedType ?? "application/json; charset=utf-8"; - context.Response.ContentType = "application/json; charset=utf-8"; - if (data.Errors?.Count > 0) + var gqlResult = await schema.ExecuteRequestAsync(query, context.RequestServices, context.User, options); + + if (followSpec) + { + var requestedType = acceptedContentType.FirstOrDefault(t => + t?.StartsWith(APP_JSON_TYPE_START, StringComparison.InvariantCulture) == true || t?.StartsWith(APP_GQL_TYPE_START, StringComparison.InvariantCulture) == true + ); + context.Response.ContentType = requestedType ?? $"{APP_GQL_TYPE_START}; charset=utf-8"; + } + else { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + context.Response.ContentType = $"{APP_JSON_TYPE_START}; charset=utf-8"; + } + + if (gqlResult.Errors?.Count > 0) + { + // TODO: change with 6.0. This is here as changing how errors are thrown would be a breaking change + // But following the spec this is not a valid request and should be a 400 + if (followSpec && gqlResult.Errors.Count == 1 && gqlResult.Errors[0].Message == "Please provide a persisted query hash or a query string") + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + return; + } + context.Response.StatusCode = followSpec ? StatusCodes.Status200OK : StatusCodes.Status400BadRequest; } var serializer = context.RequestServices.GetRequiredService(); - await serializer.SerializeAsync(context.Response.Body, data); + await serializer.SerializeAsync(context.Response.Body, gqlResult); + } + catch (Exception) + { + // only exceptions we should get are ones that mean the request is invalid, e.g. deserialization errors + // all other graphql specific errors should be in the response data + if (followSpec) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + return; + } + else + { + // keep the old behavior for v 5.x + throw; + } } - ); + } + ); - configureEndpoint?.Invoke(postEndpoint); + configureEndpoint?.Invoke(postEndpoint); - return builder; - } + return builder; } } diff --git a/src/EntityGraphQL.AspNet/Extensions/IGraphQLRequestDeserializer.cs b/src/EntityGraphQL.AspNet/Extensions/IGraphQLRequestDeserializer.cs index ff4d794b..ce9bc2ba 100644 --- a/src/EntityGraphQL.AspNet/Extensions/IGraphQLRequestDeserializer.cs +++ b/src/EntityGraphQL.AspNet/Extensions/IGraphQLRequestDeserializer.cs @@ -1,13 +1,12 @@ using System.IO; using System.Threading.Tasks; -namespace EntityGraphQL.AspNet +namespace EntityGraphQL.AspNet; + +/// +/// Deserializes GraphQL requests into a QueryRequest object. +/// +public interface IGraphQLRequestDeserializer { - /// - /// Deserializes GraphQL requests into a QueryRequest object. - /// - public interface IGraphQLRequestDeserializer - { - Task DeserializeAsync(Stream body); - } + Task DeserializeAsync(Stream body); } diff --git a/src/EntityGraphQL.AspNet/Extensions/IGraphQLResponseSerializer.cs b/src/EntityGraphQL.AspNet/Extensions/IGraphQLResponseSerializer.cs index 682b67aa..d7822717 100644 --- a/src/EntityGraphQL.AspNet/Extensions/IGraphQLResponseSerializer.cs +++ b/src/EntityGraphQL.AspNet/Extensions/IGraphQLResponseSerializer.cs @@ -1,13 +1,12 @@ using System.IO; using System.Threading.Tasks; -namespace EntityGraphQL.AspNet +namespace EntityGraphQL.AspNet; + +/// +/// Serializes GraphQL responses into a response format. +/// +public interface IGraphQLResponseSerializer { - /// - /// Serializes GraphQL responses into a response format. - /// - public interface IGraphQLResponseSerializer - { - Task SerializeAsync(Stream body, T data); - } + Task SerializeAsync(Stream body, T data); } diff --git a/src/EntityGraphQL.AspNet/Extensions/RuntimeTypeJsonConverter.cs b/src/EntityGraphQL.AspNet/Extensions/RuntimeTypeJsonConverter.cs index daa72d48..77404f7e 100644 --- a/src/EntityGraphQL.AspNet/Extensions/RuntimeTypeJsonConverter.cs +++ b/src/EntityGraphQL.AspNet/Extensions/RuntimeTypeJsonConverter.cs @@ -27,7 +27,7 @@ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerO } // Leave as much as we can to JsonSerializer.Serialize - var isNotString = value is string == false; + var isNotString = value is not string; if (value is IDictionary dictionary) { WriteDictionary(writer, dictionary, ref options); diff --git a/src/EntityGraphQL.AspNet/GraphQLAuthorizePolicyAttribute.cs b/src/EntityGraphQL.AspNet/GraphQLAuthorizePolicyAttribute.cs index b86040b5..d1b5b888 100644 --- a/src/EntityGraphQL.AspNet/GraphQLAuthorizePolicyAttribute.cs +++ b/src/EntityGraphQL.AspNet/GraphQLAuthorizePolicyAttribute.cs @@ -2,31 +2,30 @@ using System.Collections.Generic; using System.Linq; -namespace EntityGraphQL.AspNet +namespace EntityGraphQL.AspNet; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public class GraphQLAuthorizePolicyAttribute : Attribute { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class GraphQLAuthorizePolicyAttribute : Attribute + /// + /// Initializes a new instance of the GraphQLAuthorizeAttribute class + /// + public GraphQLAuthorizePolicyAttribute() { - /// - /// Initializes a new instance of the GraphQLAuthorizeAttribute class - /// - public GraphQLAuthorizePolicyAttribute() - { - Policies = new List(); - } - - /// - /// Initializes a new instance of the GraphQLAuthorizePolicyAttribute class with the specified policies. - /// - /// - public GraphQLAuthorizePolicyAttribute(params string[] policies) - { - Policies = policies.ToList(); - } + Policies = []; + } - /// - /// Gets or sets the policies name that determines access to the resource. - /// - public List Policies { get; set; } + /// + /// Initializes a new instance of the GraphQLAuthorizePolicyAttribute class with the specified policies. + /// + /// + public GraphQLAuthorizePolicyAttribute(params string[] policies) + { + Policies = policies.ToList(); } + + /// + /// Gets or sets the policies name that determines access to the resource. + /// + public List Policies { get; set; } } diff --git a/src/EntityGraphQL.AspNet/PolicyOrRoleBasedAuthorization.cs b/src/EntityGraphQL.AspNet/PolicyOrRoleBasedAuthorization.cs index 9ba34fa1..03c2e2ce 100644 --- a/src/EntityGraphQL.AspNet/PolicyOrRoleBasedAuthorization.cs +++ b/src/EntityGraphQL.AspNet/PolicyOrRoleBasedAuthorization.cs @@ -6,95 +6,95 @@ using EntityGraphQL.Schema; using Microsoft.AspNetCore.Authorization; -namespace EntityGraphQL.AspNet +namespace EntityGraphQL.AspNet; + +/// +/// Checks if the executing user has the required policies to access the requested part of the GraphQL schema +/// +public class PolicyOrRoleBasedAuthorization : RoleBasedAuthorization { + private readonly IAuthorizationService? authService; + + public PolicyOrRoleBasedAuthorization(IAuthorizationService? authService) + { + this.authService = authService; + } + /// - /// Checks if the executing user has the required policies to access the requested part of the GraphQL schema + /// Check if this user has the right security claims, roles or policies to access the request type/field /// - public class PolicyOrRoleBasedAuthorization : RoleBasedAuthorization + /// The user to check against + /// The required auth for the field or type you want to check against the user + /// + public override bool IsAuthorized(ClaimsPrincipal? user, RequiredAuthorization? requiredAuthorization) { - private readonly IAuthorizationService? authService; - - public PolicyOrRoleBasedAuthorization(IAuthorizationService? authService) + // if the list is empty it means identity.IsAuthenticated needs to be true, if full it requires certain authorization + if (requiredAuthorization != null && requiredAuthorization.Any()) { - this.authService = authService; - } - - /// - /// Check if this user has the right security claims, roles or policies to access the request type/field - /// - /// The required auth for the field or type you want to check against the user - /// - public override bool IsAuthorized(ClaimsPrincipal? user, RequiredAuthorization? requiredAuthorization) - { - // if the list is empty it means identity.IsAuthenticated needs to be true, if full it requires certain authorization - if (requiredAuthorization != null && requiredAuthorization.Any()) + // check polices if principal with used + if (authService != null && user != null) { - // check polices if principal with used - if (authService != null && user != null) + var allPoliciesValid = true; + foreach (var policy in requiredAuthorization.Policies) { - var allPoliciesValid = true; - foreach (var policy in requiredAuthorization.Policies) - { - // each policy now is an OR - var hasValidPolicy = policy.Any(p => authService.AuthorizeAsync(user, p).GetAwaiter().GetResult().Succeeded); - allPoliciesValid = allPoliciesValid && hasValidPolicy; - if (!allPoliciesValid) - break; - } + // each policy now is an OR + var hasValidPolicy = policy.Any(p => authService.AuthorizeAsync(user, p).GetAwaiter().GetResult().Succeeded); + allPoliciesValid = allPoliciesValid && hasValidPolicy; if (!allPoliciesValid) - return false; + break; } - - // check roles - return base.IsAuthorized(user, requiredAuthorization); + if (!allPoliciesValid) + return false; } - return true; + + // check roles + return base.IsAuthorized(user, requiredAuthorization); } + return true; + } - private static RequiredAuthorization GetRequiredAuth(RequiredAuthorization? requiredAuth, ICustomAttributeProvider thing) - { - var attributes = thing.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast(); - var requiredRoles = attributes.Where(c => !string.IsNullOrEmpty(c.Roles)).Select(c => c.Roles!.Split(",").ToList()).ToList(); - var requiredPolicies = attributes.Where(c => !string.IsNullOrEmpty(c.Policy)).Select(c => c.Policy!.Split(",").ToList()).ToList(); - var newAuth = new RequiredAuthorization(requiredRoles, requiredPolicies); - if (requiredAuth != null) - requiredAuth = requiredAuth.Concat(newAuth); - else - requiredAuth = newAuth; + private static RequiredAuthorization GetRequiredAuth(RequiredAuthorization? requiredAuth, ICustomAttributeProvider thing) + { + var attributes = thing.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast(); + var requiredRoles = attributes.Where(c => !string.IsNullOrEmpty(c.Roles)).Select(c => c.Roles!.Split(",").ToList()).ToList(); + var requiredPolicies = attributes.Where(c => !string.IsNullOrEmpty(c.Policy)).Select(c => c.Policy!.Split(",").ToList()).ToList(); + var newAuth = new RequiredAuthorization(requiredRoles, requiredPolicies); + if (requiredAuth != null) + requiredAuth = requiredAuth.Concat(newAuth); + else + requiredAuth = newAuth; - var attributes2 = thing.GetCustomAttributes(typeof(GraphQLAuthorizePolicyAttribute), true).Cast(); + var attributes2 = thing.GetCustomAttributes(typeof(GraphQLAuthorizePolicyAttribute), true).Cast(); - requiredPolicies = attributes2.Where(c => c.Policies?.Count > 0).Select(c => c.Policies.ToList()).ToList(); - requiredAuth = requiredAuth.Concat(new RequiredAuthorization(null, requiredPolicies)); - return requiredAuth; - } + requiredPolicies = attributes2.Where(c => c.Policies?.Count > 0).Select(c => c.Policies.ToList()).ToList(); + requiredAuth = requiredAuth.Concat(new RequiredAuthorization(null, requiredPolicies)); + return requiredAuth; + } - public override RequiredAuthorization? GetRequiredAuthFromExpression(LambdaExpression fieldSelection) + public override RequiredAuthorization? GetRequiredAuthFromExpression(LambdaExpression fieldSelection) + { + var requiredAuth = base.GetRequiredAuthFromExpression(fieldSelection); + if (fieldSelection.Body.NodeType == ExpressionType.MemberAccess) { - var requiredAuth = base.GetRequiredAuthFromExpression(fieldSelection); - if (fieldSelection.Body.NodeType == ExpressionType.MemberAccess) - { - requiredAuth = GetRequiredAuth(requiredAuth, ((MemberExpression)fieldSelection.Body).Member); - } - - return requiredAuth; + requiredAuth = GetRequiredAuth(requiredAuth, ((MemberExpression)fieldSelection.Body).Member); } - public override RequiredAuthorization GetRequiredAuthFromMember(MemberInfo field) - { - var requiredAuth = base.GetRequiredAuthFromMember(field); - requiredAuth = GetRequiredAuth(requiredAuth, field); + return requiredAuth; + } - return requiredAuth; - } + public override RequiredAuthorization GetRequiredAuthFromMember(MemberInfo field) + { + var requiredAuth = base.GetRequiredAuthFromMember(field); + requiredAuth = GetRequiredAuth(requiredAuth, field); - public override RequiredAuthorization GetRequiredAuthFromType(Type type) - { - var requiredAuth = base.GetRequiredAuthFromType(type); - requiredAuth = GetRequiredAuth(requiredAuth, type); + return requiredAuth; + } - return requiredAuth; - } + public override RequiredAuthorization GetRequiredAuthFromType(Type type) + { + var requiredAuth = base.GetRequiredAuthFromType(type); + requiredAuth = GetRequiredAuth(requiredAuth, type); + + return requiredAuth; } } diff --git a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessage.cs b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessage.cs index 303b219a..88565082 100644 --- a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessage.cs +++ b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessage.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; namespace EntityGraphQL.AspNet.WebSockets; diff --git a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessageType.cs b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessageType.cs index f3de3221..fe5ca430 100644 --- a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessageType.cs +++ b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWSMessageType.cs @@ -1,17 +1,16 @@ -namespace EntityGraphQL.AspNet.WebSockets +namespace EntityGraphQL.AspNet.WebSockets; + +/// +/// Protocol message type. +/// +public static class GraphQLWSMessageType { - /// - /// Protocol message type. - /// - public static class GraphQLWSMessageType - { - public const string ConnectionInit = "connection_init"; - public const string ConnectionAck = "connection_ack"; - public const string Ping = "ping"; - public const string Pong = "pong"; - public const string Error = "error"; - public const string Complete = "complete"; - public const string Subscribe = "subscribe"; - public const string Next = "next"; - } + public const string ConnectionInit = "connection_init"; + public const string ConnectionAck = "connection_ack"; + public const string Ping = "ping"; + public const string Pong = "pong"; + public const string Error = "error"; + public const string Complete = "complete"; + public const string Subscribe = "subscribe"; + public const string Next = "next"; } diff --git a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWebSocketServer.cs b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWebSocketServer.cs index 686b238b..bc662592 100644 --- a/src/EntityGraphQL.AspNet/WebSockets/GraphQLWebSocketServer.cs +++ b/src/EntityGraphQL.AspNet/WebSockets/GraphQLWebSocketServer.cs @@ -23,11 +23,11 @@ public class GraphQLWebSocketServer : IGraphQLWebSocketServer /// /// These are the subscriptions/clients that are currently active with this server. /// - private readonly Dictionary subscriptions = new(); + private readonly Dictionary subscriptions = []; private readonly WebSocket webSocket; private readonly ExecutionOptions options; private bool initialised; - private readonly JsonSerializerOptions jsonOptions = new() { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + private readonly JsonSerializerOptions jsonOptions = new() { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public HttpContext Context { get; } @@ -164,7 +164,7 @@ await CloseConnectionAsync( await SendNextAsync(graphQLWSMessage.Id, result); } // send complete after next or error above - await SendAsync(new BaseWithIdGraphQLWSResponse { Type = GraphQLWSMessageType.Complete, Id = graphQLWSMessage.Id, }); + await SendAsync(new BaseWithIdGraphQLWSResponse { Type = GraphQLWSMessageType.Complete, Id = graphQLWSMessage.Id }); } } } @@ -172,7 +172,7 @@ await CloseConnectionAsync( public async Task SendErrorAsync(string id, Exception exception) { - await SendErrorAsync(id, new List { new GraphQLError(exception.Message, null) }); + await SendErrorAsync(id, new List { new(exception.Message, null) }); } public Task SendErrorAsync(string id, IEnumerable errors) diff --git a/src/EntityGraphQL.AspNet/WebSockets/WebSocketSubscription.cs b/src/EntityGraphQL.AspNet/WebSockets/WebSocketSubscription.cs index 94c337c2..283bdbf1 100644 --- a/src/EntityGraphQL.AspNet/WebSockets/WebSocketSubscription.cs +++ b/src/EntityGraphQL.AspNet/WebSockets/WebSocketSubscription.cs @@ -8,7 +8,8 @@ namespace EntityGraphQL.AspNet.WebSockets; /// /// Ties the GraphQL subscription to the WebSocket connection. /// -/// +/// The main GraphQL query context type +/// The type of the subscription result public sealed class WebSocketSubscription : IDisposable, IObserver { /// diff --git a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs index 8d77566b..ed13497d 100644 --- a/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs +++ b/src/EntityGraphQL/Compiler/EntityGraphQLQueryWalker.cs @@ -8,500 +8,494 @@ using EntityGraphQL.Schema; using HotChocolate.Language; -namespace EntityGraphQL.Compiler +namespace EntityGraphQL.Compiler; + +/// +/// Visits nodes of a GraphQL request to build a representation of the query against the context objects via LINQ methods. +/// +internal sealed class EntityGraphQLQueryWalker : QuerySyntaxWalker { + private readonly ISchemaProvider schemaProvider; + private readonly QueryVariables variables; + private ExecutableGraphQLStatement? currentOperation; + /// - /// Visits nodes of a GraphQL request to build a representation of the query against the context objects via LINQ methods. + /// The root - the query document. This is what we "return" /// - /// - internal sealed class EntityGraphQLQueryWalker : QuerySyntaxWalker + /// + public GraphQLDocument? Document { get; private set; } + + public EntityGraphQLQueryWalker(ISchemaProvider schemaProvider, QueryVariables? variables) { - private readonly ISchemaProvider schemaProvider; - private readonly QueryVariables variables; - private ExecutableGraphQLStatement? currentOperation; + this.schemaProvider = schemaProvider; + variables ??= []; + this.variables = variables; + } - /// - /// The root - the query document. This is what we "return" - /// - /// - public GraphQLDocument? Document { get; private set; } + /// + /// This is out TOP level GQL document + /// + /// + /// + protected override void VisitDocument(DocumentNode node, IGraphQLNode? context) + { + if (context != null) + throw new ArgumentException("context should be null", nameof(context)); + + Document = new GraphQLDocument(schemaProvider); + base.VisitDocument(node, context); + } + + protected override void VisitOperationDefinition(OperationDefinitionNode node, IGraphQLNode? context) + { + if (Document == null) + throw new EntityGraphQLCompilerException("Document should not be null visiting operation definition"); - public EntityGraphQLQueryWalker(ISchemaProvider schemaProvider, QueryVariables? variables) + // these are the variables that can change each request for the same query + var operationVariables = ProcessVariableDefinitions(node); + + if (node.Operation == OperationType.Query) { - this.schemaProvider = schemaProvider; - variables ??= new QueryVariables(); - this.variables = variables; + var rootParameterContext = Expression.Parameter(schemaProvider.QueryContextType, $"query_ctx"); + context = new GraphQLQueryStatement(schemaProvider, node.Name?.Value, rootParameterContext, rootParameterContext, operationVariables); + if (node.Directives?.Any() == true) + context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.QUERY, node.Directives)); + currentOperation = (GraphQLQueryStatement)context; } - - /// - /// This is out TOP level GQL document - /// - /// - /// - protected override void VisitDocument(DocumentNode node, IGraphQLNode? context) + else if (node.Operation == OperationType.Mutation) { - if (context != null) - throw new ArgumentException("context should be null", nameof(context)); - - Document = new GraphQLDocument(schemaProvider); - base.VisitDocument(node, context); + // we never build expression from this parameter but the type is used to look up the ISchemaType + var rootParameterContext = Expression.Parameter(schemaProvider.MutationType, $"mut_ctx"); + context = new GraphQLMutationStatement(schemaProvider, node.Name?.Value, rootParameterContext, rootParameterContext, operationVariables); + if (node.Directives?.Any() == true) + context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.MUTATION, node.Directives)); + currentOperation = (GraphQLMutationStatement)context; } - - protected override void VisitOperationDefinition(OperationDefinitionNode node, IGraphQLNode? context) + else if (node.Operation == OperationType.Subscription) { - if (Document == null) - throw new EntityGraphQLCompilerException("Document should not be null visiting operation definition"); - - // these are the variables that can change each request for the same query - var operationVariables = ProcessVariableDefinitions(node); - - if (node.Operation == OperationType.Query) - { - var rootParameterContext = Expression.Parameter(schemaProvider.QueryContextType, $"query_ctx"); - context = new GraphQLQueryStatement(schemaProvider, node.Name?.Value, rootParameterContext, rootParameterContext, operationVariables); - if (node.Directives?.Any() == true) - context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.QUERY, node.Directives)); - currentOperation = (GraphQLQueryStatement)context; - } - else if (node.Operation == OperationType.Mutation) - { - // we never build expression from this parameter but the type is used to look up the ISchemaType - var rootParameterContext = Expression.Parameter(schemaProvider.MutationType, $"mut_ctx"); - context = new GraphQLMutationStatement(schemaProvider, node.Name?.Value, rootParameterContext, rootParameterContext, operationVariables); - if (node.Directives?.Any() == true) - context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.MUTATION, node.Directives)); - currentOperation = (GraphQLMutationStatement)context; - } - else if (node.Operation == OperationType.Subscription) - { - // we never build expression from this parameter but the type is used to look up the ISchemaType - var rootParameterContext = Expression.Parameter(schemaProvider.SubscriptionType, $"sub_ctx"); - context = new GraphQLSubscriptionStatement(schemaProvider, node.Name?.Value, rootParameterContext, operationVariables); - if (node.Directives?.Any() == true) - context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.SUBSCRIPTION, node.Directives)); - currentOperation = (GraphQLSubscriptionStatement)context; - } - - if (context != null) - { - Document.Operations.Add((ExecutableGraphQLStatement)context); - base.VisitOperationDefinition(node, context); - } + // we never build expression from this parameter but the type is used to look up the ISchemaType + var rootParameterContext = Expression.Parameter(schemaProvider.SubscriptionType, $"sub_ctx"); + context = new GraphQLSubscriptionStatement(schemaProvider, node.Name?.Value, rootParameterContext, operationVariables); + if (node.Directives?.Any() == true) + context.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.SUBSCRIPTION, node.Directives)); + currentOperation = (GraphQLSubscriptionStatement)context; } - private Dictionary ProcessVariableDefinitions(OperationDefinitionNode node) + if (context != null) { - if (Document == null) - throw new EntityGraphQLCompilerException("Document should not be null visiting operation definition"); + Document.Operations.Add((ExecutableGraphQLStatement)context); + base.VisitOperationDefinition(node, context); + } + } - var documentVariables = new Dictionary(); + private Dictionary ProcessVariableDefinitions(OperationDefinitionNode node) + { + if (Document == null) + throw new EntityGraphQLCompilerException("Document should not be null visiting operation definition"); - foreach (var item in node.VariableDefinitions) - { - var argName = item.Variable.Name.Value; - object? defaultValue = null; - (var gqlTypeName, var isList, var isRequired) = GetGqlType(item.Type); - - var schemaType = schemaProvider.GetSchemaType(gqlTypeName, null); - var varTypeInSchema = schemaType.TypeDotnet ?? throw new EntityGraphQLCompilerException($"Variable {argName} has no type"); - if (!isRequired && (varTypeInSchema.IsValueType || varTypeInSchema.IsEnum)) - varTypeInSchema = typeof(Nullable<>).MakeGenericType(varTypeInSchema); - - if (isList) - varTypeInSchema = typeof(List<>).MakeGenericType(varTypeInSchema); - - if (item.DefaultValue != null) - defaultValue = Expression - .Lambda(Expression.Constant(QueryWalkerHelper.ProcessArgumentValue(schemaProvider, item.DefaultValue, argName, varTypeInSchema))) - .Compile() - .DynamicInvoke(); - - documentVariables.Add( - argName, - new ArgType( - gqlTypeName, - schemaType.TypeDotnet.Name, - new GqlTypeInfo(() => schemaType, varTypeInSchema) { TypeNotNullable = isRequired, ElementTypeNullable = !isRequired }, - null, - varTypeInSchema - ) - { - DefaultValue = defaultValue, - IsRequired = isRequired - } - ); + var documentVariables = new Dictionary(); - if (item.Directives?.Any() == true) + foreach (var item in node.VariableDefinitions) + { + var argName = item.Variable.Name.Value; + object? defaultValue = null; + (var gqlTypeName, var isList, var isRequired) = GetGqlType(item.Type); + + var schemaType = schemaProvider.GetSchemaType(gqlTypeName, null); + var varTypeInSchema = schemaType.TypeDotnet ?? throw new EntityGraphQLCompilerException($"Variable {argName} has no type"); + if (!isRequired && (varTypeInSchema.IsValueType || varTypeInSchema.IsEnum)) + varTypeInSchema = typeof(Nullable<>).MakeGenericType(varTypeInSchema); + + if (isList) + varTypeInSchema = typeof(List<>).MakeGenericType(varTypeInSchema); + + if (item.DefaultValue != null) + defaultValue = Expression.Lambda(Expression.Constant(QueryWalkerHelper.ProcessArgumentValue(schemaProvider, item.DefaultValue, argName, varTypeInSchema))).Compile().DynamicInvoke(); + + documentVariables.Add( + argName, + new ArgType( + gqlTypeName, + schemaType.TypeDotnet.Name, + new GqlTypeInfo(() => schemaType, varTypeInSchema) { TypeNotNullable = isRequired, ElementTypeNullable = !isRequired }, + null, + varTypeInSchema + ) { - var directives = ProcessFieldDirectives(ExecutableDirectiveLocation.VARIABLE_DEFINITION, item.Directives); - foreach (var directive in directives) - { - directive.VisitNode(ExecutableDirectiveLocation.VARIABLE_DEFINITION, schemaProvider, null, new Dictionary(), null, null); - } + DefaultValue = defaultValue, + IsRequired = isRequired, } + ); - if (item.Type.Kind == SyntaxKind.NonNullType && variables.ContainsKey(argName) == false) + if (item.Directives?.Any() == true) + { + var directives = ProcessFieldDirectives(ExecutableDirectiveLocation.VARIABLE_DEFINITION, item.Directives); + foreach (var directive in directives) { - throw new EntityGraphQLCompilerException($"Missing required variable '{argName}' on operation '{node.Name?.Value}'"); + directive.VisitNode(ExecutableDirectiveLocation.VARIABLE_DEFINITION, schemaProvider, null, new Dictionary(), null, null); } } - return documentVariables; + + if (item.Type.Kind == SyntaxKind.NonNullType && !variables.ContainsKey(argName)) + { + throw new EntityGraphQLCompilerException($"Missing required variable '{argName}' on operation '{node.Name?.Value}'"); + } } + return documentVariables; + } - private static (string typeName, bool isList, bool isRequired) GetGqlType(ITypeNode item) + private static (string typeName, bool isList, bool isRequired) GetGqlType(ITypeNode item) + { + switch (item.Kind) { - switch (item.Kind) + case SyntaxKind.NamedType: + return (((NamedTypeNode)item).Name.Value, false, false); + case SyntaxKind.NonNullType: { - case SyntaxKind.NamedType: - return (((NamedTypeNode)item).Name.Value, false, false); - case SyntaxKind.NonNullType: - { - var (_, isList, _) = GetGqlType(((NonNullTypeNode)item).Type); - return (((NonNullTypeNode)item).NamedType().Name.Value, isList, true); - } - case SyntaxKind.ListType: - return (((ListTypeNode)item).Type.NamedType().Name.Value, true, false); - default: - throw new EntityGraphQLCompilerException($"Unexpected node kind {item.Kind}"); + var (_, isList, _) = GetGqlType(((NonNullTypeNode)item).Type); + return (((NonNullTypeNode)item).NamedType().Name.Value, isList, true); } - ; + case SyntaxKind.ListType: + return (((ListTypeNode)item).Type.NamedType().Name.Value, true, false); + default: + throw new EntityGraphQLCompilerException($"Unexpected node kind {item.Kind}"); } + ; + } - protected override void VisitField(FieldNode node, IGraphQLNode? context) - { - if (context == null) - throw new EntityGraphQLCompilerException("context should not be null visiting field"); - if (context.NextFieldContext == null) - throw new EntityGraphQLCompilerException("context.NextFieldContext should not be null visiting field"); + protected override void VisitField(FieldNode node, IGraphQLNode? context) + { + if (context == null) + throw new EntityGraphQLCompilerException("context should not be null visiting field"); + if (context.NextFieldContext == null) + throw new EntityGraphQLCompilerException("context.NextFieldContext should not be null visiting field"); - var schemaType = context.Field?.ReturnType.SchemaType ?? schemaProvider.GetSchemaType(context.NextFieldContext.Type, context.Field?.FromType.GqlType == GqlTypes.InputObject, null); - var actualField = schemaType.GetField(node.Name.Value, null); + var schemaType = context.Field?.ReturnType.SchemaType ?? schemaProvider.GetSchemaType(context.NextFieldContext.Type, context.Field?.FromType.GqlType == GqlTypes.InputObject, null); + var actualField = schemaType.GetField(node.Name.Value, null); - var args = node.Arguments != null ? ProcessArguments(actualField, node.Arguments) : null; - var resultName = node.Alias?.Value ?? actualField.Name; + var args = node.Arguments != null ? ProcessArguments(actualField, node.Arguments) : null; + var resultName = node.Alias?.Value ?? actualField.Name; - if (actualField.FieldType == GraphQLQueryFieldType.Mutation) - { - var mutationField = (MutationField)actualField; + if (actualField.FieldType == GraphQLQueryFieldType.Mutation) + { + var mutationField = (MutationField)actualField; - var nextContextParam = Expression.Parameter(mutationField.ReturnType.TypeDotnet, $"mut_{actualField.Name}"); - var graphqlMutationField = new GraphQLMutationField(schemaProvider, resultName, mutationField, args, nextContextParam, nextContextParam, context); + var nextContextParam = Expression.Parameter(mutationField.ReturnType.TypeDotnet, $"mut_{actualField.Name}"); + var graphqlMutationField = new GraphQLMutationField(schemaProvider, resultName, mutationField, args, nextContextParam, nextContextParam, context); - if (node.SelectionSet != null) + if (node.SelectionSet != null) + { + var select = ParseFieldSelect(nextContextParam, actualField, resultName, graphqlMutationField, node.SelectionSet, args); + if (mutationField.ReturnType.IsList) { - var select = ParseFieldSelect(nextContextParam, actualField, resultName, graphqlMutationField, node.SelectionSet, args); - if (mutationField.ReturnType.IsList) + // nulls are not known until mutation is executed. Will be handled in GraphQLMutationStatement + var newSelect = new GraphQLListSelectionField( + schemaProvider, + actualField, + resultName, + (ParameterExpression)select.NextFieldContext!, + select.RootParameter, + select.RootParameter!, + context, + args + ); + foreach (var queryField in select.QueryFields) { - // nulls are not known until mutation is executed. Will be handled in GraphQLMutationStatement - var newSelect = new GraphQLListSelectionField( - schemaProvider, - actualField, - resultName, - (ParameterExpression)select.NextFieldContext!, - select.RootParameter, - select.RootParameter!, - context, - args - ); - foreach (var queryField in select.QueryFields) - { - newSelect.AddField(queryField); - } - select = newSelect; + newSelect.AddField(queryField); } - graphqlMutationField.ResultSelection = select; - } - if (node.Directives?.Any() == true) - { - graphqlMutationField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); + select = newSelect; } - - context.AddField(graphqlMutationField); + graphqlMutationField.ResultSelection = select; } - else if (actualField.FieldType == GraphQLQueryFieldType.Subscription) + if (node.Directives?.Any() == true) { - var subscriptionField = (SubscriptionField)actualField; + graphqlMutationField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); + } + + context.AddField(graphqlMutationField); + } + else if (actualField.FieldType == GraphQLQueryFieldType.Subscription) + { + var subscriptionField = (SubscriptionField)actualField; - var nextContextParam = Expression.Parameter(subscriptionField.ReturnType.TypeDotnet, $"sub_{actualField.Name}"); - var graphqlSubscriptionField = new GraphQLSubscriptionField(schemaProvider, resultName, subscriptionField, args, nextContextParam, nextContextParam, context); + var nextContextParam = Expression.Parameter(subscriptionField.ReturnType.TypeDotnet, $"sub_{actualField.Name}"); + var graphqlSubscriptionField = new GraphQLSubscriptionField(schemaProvider, resultName, subscriptionField, args, nextContextParam, nextContextParam, context); - if (node.SelectionSet != null) + if (node.SelectionSet != null) + { + var select = ParseFieldSelect(nextContextParam, actualField, resultName, graphqlSubscriptionField, node.SelectionSet, args); + if (subscriptionField.ReturnType.IsList) { - var select = ParseFieldSelect(nextContextParam, actualField, resultName, graphqlSubscriptionField, node.SelectionSet, args); - if (subscriptionField.ReturnType.IsList) + // nulls are not known until subscription is executed. Will be handled in GraphQLSubscriptionStatement + var newSelect = new GraphQLListSelectionField( + schemaProvider, + actualField, + resultName, + (ParameterExpression)select.NextFieldContext!, + select.RootParameter, + select.RootParameter!, + context, + args + ); + foreach (var queryField in select.QueryFields) { - // nulls are not known until subscription is executed. Will be handled in GraphQLSubscriptionStatement - var newSelect = new GraphQLListSelectionField( - schemaProvider, - actualField, - resultName, - (ParameterExpression)select.NextFieldContext!, - select.RootParameter, - select.RootParameter!, - context, - args - ); - foreach (var queryField in select.QueryFields) - { - newSelect.AddField(queryField); - } - select = newSelect; + newSelect.AddField(queryField); } - graphqlSubscriptionField.ResultSelection = select; - } - if (node.Directives?.Any() == true) - { - graphqlSubscriptionField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); + select = newSelect; } - - context.AddField(graphqlSubscriptionField); + graphqlSubscriptionField.ResultSelection = select; } - else + if (node.Directives?.Any() == true) { - BaseGraphQLField? fieldResult; - - if (node.SelectionSet != null) - { - fieldResult = ParseFieldSelect(actualField.ResolveExpression!, actualField, resultName, context, node.SelectionSet, args); - } - else if (actualField.ReturnType.SchemaType.RequiresSelection) - { - // wild card query - select out all the fields for the object - throw new EntityGraphQLCompilerException($"Field '{actualField.Name}' requires a selection set defining the fields you would like to select."); - } - else - { - var rootParam = context.NextFieldContext?.NodeType == ExpressionType.Parameter ? actualField.FieldParam : context.RootParameter; - fieldResult = new GraphQLScalarField(schemaProvider, actualField, resultName, actualField.ResolveExpression!, rootParam, context, args); - } - - if (node.Directives?.Any() == true) - { - fieldResult.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); - } - if (fieldResult != null) - { - context.AddField(fieldResult); - } + graphqlSubscriptionField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); } - } - public BaseGraphQLQueryField ParseFieldSelect(Expression fieldExp, IField fieldContext, string name, IGraphQLNode context, SelectionSetNode selection, Dictionary? arguments) + context.AddField(graphqlSubscriptionField); + } + else { - if (fieldContext.ReturnType.IsList) + BaseGraphQLField? fieldResult; + + if (node.SelectionSet != null) { - return BuildDynamicSelectOnCollection(fieldContext, fieldExp, fieldContext.ReturnType.SchemaType, name, context, selection, arguments); + fieldResult = ParseFieldSelect(actualField.ResolveExpression!, actualField, resultName, context, node.SelectionSet, args); + } + else if (actualField.ReturnType.SchemaType.RequiresSelection) + { + // wild card query - select out all the fields for the object + throw new EntityGraphQLCompilerException($"Field '{actualField.Name}' requires a selection set defining the fields you would like to select."); + } + else + { + var rootParam = context.NextFieldContext?.NodeType == ExpressionType.Parameter ? actualField.FieldParam : context.RootParameter; + fieldResult = new GraphQLScalarField(schemaProvider, actualField, resultName, actualField.ResolveExpression!, rootParam, context, args); } - var graphQLNode = BuildDynamicSelectForObjectGraph(fieldContext, fieldExp, context, name, selection, arguments); - // Could be a list.First().Blah that we need to turn into a select, or - // other levels are object selection. e.g. from the top level people query I am selecting all their children { field1, etc. } - // Can we turn a list.First().Blah into and list.Select(i => new {i.Blah}).First() - var listExp = ExpressionUtil.FindEnumerable(fieldExp); - if (listExp.Item1 != null && listExp.Item2 != null) + if (node.Directives?.Any() == true) + { + fieldResult.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FIELD, node.Directives)); + } + if (fieldResult != null) { - // yes we can - // rebuild the Expression so we keep any ConstantParameters - var returnType = schemaProvider.GetSchemaType(listExp.Item1.Type.GetEnumerableOrArrayType()!, context.Field?.FromType.GqlType == GqlTypes.InputObject, null); - // TODO this doubles the field visit - var collectionNode = BuildDynamicSelectOnCollection(fieldContext, listExp.Item1, returnType, name, context, selection, arguments); - return new GraphQLCollectionToSingleField(schemaProvider, collectionNode, graphQLNode, listExp.Item2!); + context.AddField(fieldResult); } - return graphQLNode; } + } - /// - /// Given a syntax of someCollection { fields, to, selection, from, object } - /// it will build a select assuming 'someCollection' is an IEnumerable - /// - private GraphQLListSelectionField BuildDynamicSelectOnCollection( - IField actualField, - Expression nodeExpression, - ISchemaType returnType, - string resultName, - IGraphQLNode context, - SelectionSetNode selection, - Dictionary? arguments - ) + public BaseGraphQLQueryField ParseFieldSelect(Expression fieldExp, IField fieldContext, string name, IGraphQLNode context, SelectionSetNode selection, Dictionary? arguments) + { + if (fieldContext.ReturnType.IsList) { - if (context == null) - throw new EntityGraphQLCompilerException("context should not be null building select on collection"); + return BuildDynamicSelectOnCollection(fieldContext, fieldExp, fieldContext.ReturnType.SchemaType, name, context, selection, arguments); + } - var elementType = returnType.TypeDotnet; - var fieldParam = Expression.Parameter(elementType, $"p_{elementType.Name}"); + var graphQLNode = BuildDynamicSelectForObjectGraph(fieldContext, fieldExp, context, name, selection, arguments); + // Could be a list.First().Blah that we need to turn into a select, or + // other levels are object selection. e.g. from the top level people query I am selecting all their children { field1, etc. } + // Can we turn a list.First().Blah into and list.Select(i => new {i.Blah}).First() + var listExp = ExpressionUtil.FindEnumerable(fieldExp); + if (listExp.Item1 != null && listExp.Item2 != null) + { + // yes we can + // rebuild the Expression so we keep any ConstantParameters + var returnType = schemaProvider.GetSchemaType(listExp.Item1.Type.GetEnumerableOrArrayType()!, context.Field?.FromType.GqlType == GqlTypes.InputObject, null); + // TODO this doubles the field visit + var collectionNode = BuildDynamicSelectOnCollection(fieldContext, listExp.Item1, returnType, name, context, selection, arguments); + return new GraphQLCollectionToSingleField(schemaProvider, collectionNode, graphQLNode, listExp.Item2!); + } + return graphQLNode; + } - var gqlNode = new GraphQLListSelectionField(schemaProvider, actualField, resultName, fieldParam, actualField.FieldParam ?? context.RootParameter, nodeExpression, context, arguments); + /// + /// Given a syntax of someCollection { fields, to, selection, from, object } + /// it will build a select assuming 'someCollection' is an IEnumerable + /// + private GraphQLListSelectionField BuildDynamicSelectOnCollection( + IField actualField, + Expression nodeExpression, + ISchemaType returnType, + string resultName, + IGraphQLNode context, + SelectionSetNode selection, + Dictionary? arguments + ) + { + if (context == null) + throw new EntityGraphQLCompilerException("context should not be null building select on collection"); - // visit child fields. Will be more fields - base.VisitSelectionSet(selection, gqlNode); - return gqlNode; - } + var elementType = returnType.TypeDotnet; + var fieldParam = Expression.Parameter(elementType, $"p_{elementType.Name}"); - /// - /// Given a syntax of { fields, to, selection, from, object } with a context - /// it will build the correct select statement - /// - /// - /// - /// - /// - private GraphQLObjectProjectionField BuildDynamicSelectForObjectGraph( - IField actualField, - Expression nodeExpression, - IGraphQLNode context, - string name, - SelectionSetNode selection, - Dictionary? arguments - ) - { - if (context == null) - throw new EntityGraphQLCompilerException("context should not be null visiting field"); - if (context.NextFieldContext == null && context.RootParameter == null) - throw new EntityGraphQLCompilerException("context.NextFieldContext and context.RootParameter should not be null visiting field"); + var gqlNode = new GraphQLListSelectionField(schemaProvider, actualField, resultName, fieldParam, actualField.FieldParam ?? context.RootParameter, nodeExpression, context, arguments); - var rootParam = context.NextFieldContext?.NodeType == ExpressionType.Parameter ? actualField.FieldParam : context.RootParameter!; - var graphQLNode = new GraphQLObjectProjectionField(schemaProvider, actualField, name, nodeExpression, rootParam ?? context.RootParameter!, context, arguments); + // visit child fields. Will be more fields + base.VisitSelectionSet(selection, gqlNode); + return gqlNode; + } - base.VisitSelectionSet(selection, graphQLNode); + /// + /// Given a syntax of { fields, to, selection, from, object } with a context + /// it will build the correct select statement + /// + /// + /// + /// + private GraphQLObjectProjectionField BuildDynamicSelectForObjectGraph( + IField actualField, + Expression nodeExpression, + IGraphQLNode context, + string name, + SelectionSetNode selection, + Dictionary? arguments + ) + { + if (context == null) + throw new EntityGraphQLCompilerException("context should not be null visiting field"); + if (context.NextFieldContext == null && context.RootParameter == null) + throw new EntityGraphQLCompilerException("context.NextFieldContext and context.RootParameter should not be null visiting field"); - return graphQLNode; - } + var rootParam = context.NextFieldContext?.NodeType == ExpressionType.Parameter ? actualField.FieldParam : context.RootParameter!; + var graphQLNode = new GraphQLObjectProjectionField(schemaProvider, actualField, name, nodeExpression, rootParam ?? context.RootParameter!, context, arguments); - private Dictionary ProcessArguments(IField field, IEnumerable queryArguments) + base.VisitSelectionSet(selection, graphQLNode); + + return graphQLNode; + } + + private Dictionary ProcessArguments(IField field, IEnumerable queryArguments) + { + var args = new Dictionary(); + foreach (var arg in queryArguments) { - var args = new Dictionary(); - foreach (var arg in queryArguments) + var argName = arg.Name.Value; + if (!field.Arguments.ContainsKey(argName)) { - var argName = arg.Name.Value; - if (!field.Arguments.ContainsKey(argName)) - { - throw new EntityGraphQLCompilerException($"No argument '{argName}' found on field '{field.Name}'"); - } - var r = ParseArgument(argName, field, arg); - if (r != null) - args.Add(argName, r); + throw new EntityGraphQLCompilerException($"No argument '{argName}' found on field '{field.Name}'"); } - return args; + var r = ParseArgument(argName, field, arg); + if (r != null) + args.Add(argName, r); } + return args; + } - private object? ParseArgument(string argName, IField fieldArgumentContext, ArgumentNode argument) - { - if (Document == null) - throw new EntityGraphQLCompilerException("Document should not be null when visiting arguments"); + private object? ParseArgument(string argName, IField fieldArgumentContext, ArgumentNode argument) + { + if (Document == null) + throw new EntityGraphQLCompilerException("Document should not be null when visiting arguments"); - var argType = fieldArgumentContext.GetArgumentType(argName); - var argVal = ProcessArgumentOrVariable(argName, schemaProvider, argument, argType.Type.TypeDotnet); + var argType = fieldArgumentContext.GetArgumentType(argName); + var argVal = ProcessArgumentOrVariable(argName, schemaProvider, argument, argType.Type.TypeDotnet); - return argVal; - } + return argVal; + } - /// - /// Build the expression for the argument. A Variable ($name) will be a Expression.Parameter - /// A inline value will be a Expression.Constant - /// - private object? ProcessArgumentOrVariable(string argName, ISchemaProvider schema, ArgumentNode argument, Type argType) - { - if (currentOperation == null) - throw new EntityGraphQLCompilerException("currentOperation should not be null when visiting arguments"); + /// + /// Build the expression for the argument. A Variable ($name) will be a Expression.Parameter + /// A inline value will be a Expression.Constant + /// + private object? ProcessArgumentOrVariable(string argName, ISchemaProvider schema, ArgumentNode argument, Type argType) + { + if (currentOperation == null) + throw new EntityGraphQLCompilerException("currentOperation should not be null when visiting arguments"); - if (argument.Value.Kind == SyntaxKind.Variable) - { - return Expression.PropertyOrField(currentOperation.OpVariableParameter!, ((VariableNode)argument.Value).Name.Value); - } - return QueryWalkerHelper.ProcessArgumentValue(schema, argument.Value, argName, argType); + if (argument.Value.Kind == SyntaxKind.Variable) + { + return Expression.PropertyOrField(currentOperation.OpVariableParameter!, ((VariableNode)argument.Value).Name.Value); } + return QueryWalkerHelper.ProcessArgumentValue(schema, argument.Value, argName, argType); + } - private List ProcessFieldDirectives(ExecutableDirectiveLocation location, IEnumerable directives) + private List ProcessFieldDirectives(ExecutableDirectiveLocation location, IEnumerable directives) + { + var result = new List(directives.Count()); + foreach (var directive in directives) { - var result = new List(directives.Count()); - foreach (var directive in directives) + var processor = schemaProvider.GetDirective(directive.Name.Value); + if (!processor.Location.Contains(location)) + throw new EntityGraphQLCompilerException($"Directive '{directive.Name.Value}' can not be used on '{location}'"); + var argTypes = processor.GetArguments(schemaProvider); + var args = new Dictionary(); + foreach (var arg in directive.Arguments) { - var processor = schemaProvider.GetDirective(directive.Name.Value); - if (!processor.Location.Contains(location)) - throw new EntityGraphQLCompilerException($"Directive '{directive.Name.Value}' can not be used on '{location}'"); - var argTypes = processor.GetArguments(schemaProvider); - var args = new Dictionary(); - foreach (var arg in directive.Arguments) - { - var argVal = ProcessArgumentOrVariable(arg.Name.Value, schemaProvider, arg, argTypes[arg.Name.Value].RawType); - if (argVal != null) - args.Add(arg.Name.Value, argVal); - } - result.Add(new GraphQLDirective(directive.Name.Value, processor, args)); + var argVal = ProcessArgumentOrVariable(arg.Name.Value, schemaProvider, arg, argTypes[arg.Name.Value].RawType); + if (argVal != null) + args.Add(arg.Name.Value, argVal); } - return result; + result.Add(new GraphQLDirective(directive.Name.Value, processor, args)); } + return result; + } - protected override void VisitFragmentDefinition(FragmentDefinitionNode node, IGraphQLNode? context) + protected override void VisitFragmentDefinition(FragmentDefinitionNode node, IGraphQLNode? context) + { + if (Document == null) + throw new EntityGraphQLCompilerException("Document can not be null in VisitFragmentDefinition"); + // top level statement in GQL doc. Defines the fragment fields. + // Add to the fragments and return null + var typeName = node.TypeCondition.Name.Value; + + var fragParameter = Expression.Parameter(schemaProvider.Type(typeName).TypeDotnet, $"frag_{typeName}"); + var fragDef = new GraphQLFragmentStatement(schemaProvider, node.Name.Value, fragParameter, fragParameter); + if (node.Directives?.Any() == true) { - if (Document == null) - throw new EntityGraphQLCompilerException("Document can not be null in VisitFragmentDefinition"); - // top level statement in GQL doc. Defines the fragment fields. - // Add to the fragments and return null - var typeName = node.TypeCondition.Name.Value; - - var fragParameter = Expression.Parameter(schemaProvider.Type(typeName).TypeDotnet, $"frag_{typeName}"); - var fragDef = new GraphQLFragmentStatement(schemaProvider, node.Name.Value, fragParameter, fragParameter); - if (node.Directives?.Any() == true) + foreach (var directive in ProcessFieldDirectives(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, node.Directives)) { - foreach (var directive in ProcessFieldDirectives(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, node.Directives)) - { - directive.VisitNode(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, schemaProvider, fragDef, new Dictionary(), null, null); - } + directive.VisitNode(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, schemaProvider, fragDef, new Dictionary(), null, null); } - - Document.Fragments.Add(fragDef); - - base.VisitFragmentDefinition(node, fragDef); } - protected override void VisitInlineFragment(InlineFragmentNode node, IGraphQLNode? context) - { - if (context == null) - throw new EntityGraphQLCompilerException("context should not be null visiting inline fragment"); + Document.Fragments.Add(fragDef); - if (node.TypeCondition is not null && context is not null) - { - var type = schemaProvider.GetSchemaType(node.TypeCondition.Name.Value, null); - if (type != null) - { - var fragParameter = Expression.Parameter(type.TypeDotnet, $"frag_{type.Name}"); - var newContext = new GraphQLInlineFragmentField(schemaProvider, type.Name, fragParameter, fragParameter, context); + base.VisitFragmentDefinition(node, fragDef); + } - if (node.Directives?.Any() == true) - { - newContext.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.INLINE_FRAGMENT, node.Directives)); - } + protected override void VisitInlineFragment(InlineFragmentNode node, IGraphQLNode? context) + { + if (context == null) + throw new EntityGraphQLCompilerException("context should not be null visiting inline fragment"); - base.VisitInlineFragment(node, newContext); + if (node.TypeCondition is not null && context is not null) + { + var type = schemaProvider.GetSchemaType(node.TypeCondition.Name.Value, null); + if (type != null) + { + var fragParameter = Expression.Parameter(type.TypeDotnet, $"frag_{type.Name}"); + var newContext = new GraphQLInlineFragmentField(schemaProvider, type.Name, fragParameter, fragParameter, context); - context.AddField(newContext); - } - else + if (node.Directives?.Any() == true) { - base.VisitInlineFragment(node, context); + newContext.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.INLINE_FRAGMENT, node.Directives)); } - } - } - protected override void VisitFragmentSpread(FragmentSpreadNode node, IGraphQLNode? context) - { - if (context == null) - throw new EntityGraphQLCompilerException("Context is null in FragmentSpread"); - if (context.RootParameter == null) - throw new EntityGraphQLCompilerException("Fragment spread can only be used inside a selection set (context.RootParameter is null)"); - // later when executing we turn this field into the defined fragment (as the fragment may be defined after use) - // Just store the name to look up when needed - BaseGraphQLField? fragField = new GraphQLFragmentSpreadField(schemaProvider, node.Name.Value, null, context.RootParameter, context); - if (node.Directives?.Any() == true) - { - fragField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FRAGMENT_SPREAD, node.Directives)); + base.VisitInlineFragment(node, newContext); + + context.AddField(newContext); } - if (fragField != null) + else { - base.VisitFragmentSpread(node, fragField); - context.AddField(fragField); + base.VisitInlineFragment(node, context); } } } + + protected override void VisitFragmentSpread(FragmentSpreadNode node, IGraphQLNode? context) + { + if (context == null) + throw new EntityGraphQLCompilerException("Context is null in FragmentSpread"); + if (context.RootParameter == null) + throw new EntityGraphQLCompilerException("Fragment spread can only be used inside a selection set (context.RootParameter is null)"); + // later when executing we turn this field into the defined fragment (as the fragment may be defined after use) + // Just store the name to look up when needed + BaseGraphQLField? fragField = new GraphQLFragmentSpreadField(schemaProvider, node.Name.Value, null, context.RootParameter, context); + if (node.Directives?.Any() == true) + { + fragField.AddDirectives(ProcessFieldDirectives(ExecutableDirectiveLocation.FRAGMENT_SPREAD, node.Directives)); + } + if (fragField != null) + { + base.VisitFragmentSpread(node, fragField); + context.AddField(fragField); + } + } } diff --git a/src/EntityGraphQL/Compiler/EntityGraphQLValidationException.cs b/src/EntityGraphQL/Compiler/EntityGraphQLValidationException.cs index 6a797775..191b9ddb 100644 --- a/src/EntityGraphQL/Compiler/EntityGraphQLValidationException.cs +++ b/src/EntityGraphQL/Compiler/EntityGraphQLValidationException.cs @@ -15,6 +15,6 @@ public EntityGraphQLValidationException(IEnumerable validationErrors) public EntityGraphQLValidationException(string validationError) { - ValidationErrors = new List { validationError }; + ValidationErrors = [validationError]; } } diff --git a/src/EntityGraphQL/Compiler/EntityQuery/CompiledQueryResult.cs b/src/EntityGraphQL/Compiler/EntityQuery/CompiledQueryResult.cs index b8b35fca..ec0bc2c7 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/CompiledQueryResult.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/CompiledQueryResult.cs @@ -2,42 +2,33 @@ using System.Linq; using System.Linq.Expressions; -namespace EntityGraphQL.Compiler.EntityQuery -{ - /// - /// Represents the final result of a single Expression that can be executed. - /// - /// The LambdaExpression will be (context, constParameters, ...) where context is required to be passed in when your call Execute() - /// - public class CompiledQueryResult - { - private readonly List contextParams; +namespace EntityGraphQL.Compiler.EntityQuery; - public LambdaExpression LambdaExpression - { - get { return Expression.Lambda(ExpressionResult, ContextParams.Concat(ConstantParameters.Keys).ToArray()); } - } +/// +/// Represents the final result of a single Expression that can be executed. +/// +/// The LambdaExpression will be (context, constParameters, ...) where context is required to be passed in when your call Execute() +/// +public class CompiledQueryResult +{ + public LambdaExpression LambdaExpression => Expression.Lambda(ExpressionResult, ContextParams.Concat(ConstantParameters.Keys).ToArray()); - public IReadOnlyDictionary ConstantParameters { get; } = new Dictionary(); + public IReadOnlyDictionary ConstantParameters { get; } = new Dictionary(); - public Expression ExpressionResult { get; private set; } + public Expression ExpressionResult { get; private set; } - public List ContextParams - { - get { return contextParams; } - } + public List ContextParams { get; } - public CompiledQueryResult(Expression expressionResult, List contextParams) - { - this.ExpressionResult = expressionResult; - this.contextParams = contextParams; - } + public CompiledQueryResult(Expression expressionResult, List contextParams) + { + this.ExpressionResult = expressionResult; + this.ContextParams = contextParams; + } - public object? Execute(params object[] args) - { - var allArgs = new List(args); - allArgs.AddRange(ConstantParameters.Values); - return LambdaExpression.Compile().DynamicInvoke(allArgs.ToArray()); - } + public object? Execute(params object[] args) + { + var allArgs = new List(args); + allArgs.AddRange(ConstantParameters.Values); + return LambdaExpression.Compile().DynamicInvoke(allArgs.ToArray()); } } diff --git a/src/EntityGraphQL/Compiler/EntityQuery/DefaultMethodProvider.cs b/src/EntityGraphQL/Compiler/EntityQuery/DefaultMethodProvider.cs index 42cafb12..d15ceaad 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/DefaultMethodProvider.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/DefaultMethodProvider.cs @@ -86,7 +86,7 @@ public class DefaultMethodProvider : IMethodProvider || t == typeof(TimeOnly?) #endif , - new(StringComparer.OrdinalIgnoreCase) { { "isAny", MakeIsAnyMethod }, } + new(StringComparer.OrdinalIgnoreCase) { { "isAny", MakeIsAnyMethod } } }, }; @@ -221,14 +221,18 @@ private static Expression MakeIsAnyMethod(Expression context, Expression argCont ExpectArgsCount(1, args, methodName); var array = args.First(); var arrayType = array.Type.GetEnumerableOrArrayType() ?? throw new EntityGraphQLCompilerException("Could not get element type from enumerable/array"); - + var isQueryable = typeof(IQueryable).IsAssignableFrom(array.Type); if (context.Type.IsNullableType()) { - var call = ExpressionUtil.MakeCallOnQueryable(nameof(Enumerable.Contains), [arrayType], array, Expression.Convert(context, arrayType)); + var call = isQueryable ? + ExpressionUtil.MakeCallOnQueryable(nameof(Enumerable.Contains), [arrayType], array, Expression.Convert(context, arrayType)) : + ExpressionUtil.MakeCallOnEnumerable(nameof(Enumerable.Contains), [arrayType], array, Expression.Convert(context, arrayType)); return Expression.Condition(Expression.Equal(context, Expression.Constant(null, context.Type)), Expression.Constant(false), call); } - return ExpressionUtil.MakeCallOnQueryable(nameof(Enumerable.Contains), [arrayType], array, context); + return isQueryable ? + ExpressionUtil.MakeCallOnQueryable(nameof(Enumerable.Contains), [arrayType], array, Expression.Convert(context, arrayType)) : + ExpressionUtil.MakeCallOnEnumerable(nameof(Enumerable.Contains), [arrayType], array, Expression.Convert(context, arrayType)); } private static Expression MakeStringContainsMethod(Expression context, Expression argContext, string methodName, Expression[] args) => diff --git a/src/EntityGraphQL/Compiler/EntityQuery/EntityQueryCompiler.cs b/src/EntityGraphQL/Compiler/EntityQuery/EntityQueryCompiler.cs index c42ba774..566989cf 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/EntityQueryCompiler.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/EntityQueryCompiler.cs @@ -5,86 +5,82 @@ using EntityGraphQL.Compiler.EntityQuery.Grammar; using EntityGraphQL.Schema; -namespace EntityGraphQL.Compiler.EntityQuery +namespace EntityGraphQL.Compiler.EntityQuery; + +/// Simple language to write queries against an object schema. +/// +/// myEntity.where(field = 'value') +/// +/// (primary_key) - e.g. myEntity(12) +/// Binary Operators +/// =, !=, <, <=, >, >=, +, -, *, %, /, in +/// Unary Operators +/// not(), ! +public static class EntityQueryCompiler { - /// Simple language to write queries against an object schema. - /// - /// myEntity.where(field = 'value') - /// - /// (primary_key) - e.g. myEntity(12) - /// Binary Operators - /// =, !=, <, <=, >, >=, +, -, *, %, /, in - /// Unary Operators - /// not(), ! - public static class EntityQueryCompiler + public static CompiledQueryResult Compile(string query, ExecutionOptions executionOptions) { - public static CompiledQueryResult Compile(string query, ExecutionOptions executionOptions) - { - return Compile(query, null, executionOptions, new DefaultMethodProvider()); - } + return Compile(query, null, executionOptions, new DefaultMethodProvider()); + } - /// - /// Compile a query. - /// - /// The query text - /// - /// - /// - public static CompiledQueryResult Compile(string query, ISchemaProvider? schemaProvider, ExecutionOptions executionOptions, IMethodProvider? methodProvider = null) - { + /// + /// Compile a query. + /// + /// The query text + /// + /// + /// + public static CompiledQueryResult Compile(string query, ISchemaProvider? schemaProvider, ExecutionOptions executionOptions, IMethodProvider? methodProvider = null) + { #if NET8_0_OR_GREATER - ArgumentNullException.ThrowIfNull(query, nameof(query)); + ArgumentNullException.ThrowIfNull(query, nameof(query)); #else - if (query == null) - throw new ArgumentNullException(nameof(query)); + if (query == null) + throw new ArgumentNullException(nameof(query)); #endif - ParameterExpression? contextParam = null; - - methodProvider ??= new DefaultMethodProvider(); + ParameterExpression? contextParam = null; - if (schemaProvider != null) - contextParam = Expression.Parameter(schemaProvider.QueryContextType, $"cxt_{schemaProvider.QueryContextType.Name}"); + methodProvider ??= new DefaultMethodProvider(); - var expression = CompileQuery(query, contextParam, schemaProvider, new QueryRequestContext(null, null), methodProvider, executionOptions); + if (schemaProvider != null) + contextParam = Expression.Parameter(schemaProvider.QueryContextType, $"cxt_{schemaProvider.QueryContextType.Name}"); - var contextParams = new List(); - if (contextParam != null) - contextParams.Add(contextParam); - return new CompiledQueryResult(expression, contextParams); - } + var expression = CompileQuery(query, contextParam, schemaProvider, new QueryRequestContext(null, null), methodProvider, executionOptions); - public static CompiledQueryResult CompileWith( - string query, - Expression context, - ISchemaProvider schemaProvider, - QueryRequestContext requestContext, - ExecutionOptions executionOptions, - IMethodProvider? methodProvider = null - ) - { - methodProvider ??= new DefaultMethodProvider(); - var expression = CompileQuery(query, context, schemaProvider, requestContext, methodProvider, executionOptions); - if (expression == null) - throw new EntityGraphQLCompilerException("Failed to compile expression"); + var contextParams = new List(); + if (contextParam != null) + contextParams.Add(contextParam); + return new CompiledQueryResult(expression, contextParams); + } - var parameters = expression.NodeType == ExpressionType.Lambda ? ((LambdaExpression)expression).Parameters.ToList() : new List(); - return new CompiledQueryResult(expression, parameters); - } + public static CompiledQueryResult CompileWith( + string query, + Expression context, + ISchemaProvider schemaProvider, + QueryRequestContext requestContext, + ExecutionOptions executionOptions, + IMethodProvider? methodProvider = null + ) + { + methodProvider ??= new DefaultMethodProvider(); + var expression = CompileQuery(query, context, schemaProvider, requestContext, methodProvider, executionOptions) ?? throw new EntityGraphQLCompilerException("Failed to compile expression"); + var parameters = expression.NodeType == ExpressionType.Lambda ? ((LambdaExpression)expression).Parameters.ToList() : []; + return new CompiledQueryResult(expression, parameters); + } - private static Expression CompileQuery( - string query, - Expression? context, - ISchemaProvider? schemaProvider, - QueryRequestContext requestContext, - IMethodProvider methodProvider, - ExecutionOptions executionOptions - ) - { - var compileContext = new CompileContext(executionOptions, null, requestContext); - var expressionParser = new EntityQueryParser(context, schemaProvider, requestContext, methodProvider, compileContext); - var expression = expressionParser.Parse(query); - return expression; - } + private static Expression CompileQuery( + string query, + Expression? context, + ISchemaProvider? schemaProvider, + QueryRequestContext requestContext, + IMethodProvider methodProvider, + ExecutionOptions executionOptions + ) + { + var compileContext = new CompileContext(executionOptions, null, requestContext); + var expressionParser = new EntityQueryParser(context, schemaProvider, requestContext, methodProvider, compileContext); + var expression = expressionParser.Parse(query); + return expression; } } diff --git a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/EntityQueryParser.cs b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/EntityQueryParser.cs index 0e98cfcd..952045dd 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/EntityQueryParser.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/EntityQueryParser.cs @@ -67,9 +67,6 @@ public sealed class EntityQueryParser .And(dot) .And(Terms.Integer(NumberOptions.None)) .Then(static d => new EqlExpression(Expression.Constant(decimal.Parse($"{d.Item1}.{d.Item3}", NumberStyles.Number, CultureInfo.InvariantCulture)))); - private static readonly Parser nullExp = Terms.Text("null").Then(static _ => new EqlExpression(Expression.Constant(null))); - private static readonly Parser trueExp = Terms.Text("true").Then(static _ => new EqlExpression(Expression.Constant(true))); - private static readonly Parser falseExp = Terms.Text("false").Then(static _ => new EqlExpression(Expression.Constant(false))); private static readonly Parser strExp = SkipWhiteSpace(new StringLiteral(StringLiteralQuotes.SingleOrDouble)) .Then(static s => new EqlExpression(Expression.Constant(s.ToString()))); private readonly Expression? context; @@ -99,6 +96,10 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ var callPath = Separated(dot, OneOf(call, constArray, identifier)).Then(p => new CallPath(p, compileContext)); + var nullExp = Terms.Text("null").AndSkip(Not(identifier)).Then(static _ => new EqlExpression(Expression.Constant(null))); + var trueExp = Terms.Text("true").AndSkip(Not(identifier)).Then(static _ => new EqlExpression(Expression.Constant(true))); + var falseExp = Terms.Text("false").AndSkip(Not(identifier)).Then(static _ => new EqlExpression(Expression.Constant(false))); + // primary => NUMBER | "(" expression ")"; var primary = decimalExp.Or(longExp).Or(strExp).Or(trueExp).Or(falseExp).Or(nullExp).Or(callPath).Or(groupExpression).Or(constArray); @@ -110,14 +111,14 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ // factor => unary ( ( "*" | "/" | ... ) unary )* ; var mathOps = OneOf(multiply, divide, mod, plus, minus, power); - var mathExp = unary.And(ZeroOrMany(mathOps.And(unary))).Then((x) => HandleBinary(x, context)); + var mathExp = unary.And(ZeroOrMany(mathOps.And(unary))).Then(HandleBinary); // expression => mathExp ( ( "==" | "&&" | ... ) mathExp )* ; var compareOps = OneOf(lessThanOrEqual, greaterThanOrEqual, lessThan, greaterThan, equals, notEquals); - var compareExp = mathExp.And(ZeroOrMany(compareOps.And(mathExp))).Then((x) => HandleBinary(x, context)); + var compareExp = mathExp.And(ZeroOrMany(compareOps.And(mathExp))).Then(HandleBinary); var logicalOps = OneOf(andWord, andSymbol, orWord, orSymbol); - var logicalBinary = compareExp.And(ZeroOrMany(logicalOps.And(compareExp))).Then((x) => HandleBinary(x, context)); + var logicalBinary = compareExp.And(ZeroOrMany(logicalOps.And(compareExp))).Then(HandleBinary); var conditional = OneOf( logicalBinary @@ -156,7 +157,7 @@ public EntityQueryParser(Expression? context, ISchemaProvider? schema, QueryRequ this.methodProvider = methodProvider; } - private static IExpression HandleBinary((IExpression, IReadOnlyList<(string, IExpression)>) x, Expression? context) + private static IExpression HandleBinary((IExpression, IReadOnlyList<(string, IExpression)>) x) { var left = x.Item1; var binaryExp = left; @@ -184,7 +185,7 @@ private static IExpression HandleBinary((IExpression, IReadOnlyList<(string, IEx AndStr => ExpressionType.AndAlso, OrWord => ExpressionType.OrElse, OrStr => ExpressionType.OrElse, - _ => throw new NotSupportedException() + _ => throw new NotSupportedException(), }; binaryExp = new Binary(op, left, right); diff --git a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs index 6e98bb3b..fbad1b92 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/Grammar/IdentityExpression.cs @@ -8,15 +8,13 @@ namespace EntityGraphQL.Compiler.EntityQuery.Grammar; public class IdentityExpression(string name, CompileContext compileContext) : IExpression { - private readonly string name = name; - public Type Type => throw new NotImplementedException(); - public string Name => name; + public string Name { get; } = name; public Expression Compile(Expression? context, ISchemaProvider? schema, QueryRequestContext requestContext, IMethodProvider methodProvider) { - return MakePropertyCall(context!, schema, name, requestContext, compileContext); + return MakePropertyCall(context!, schema, Name, requestContext, compileContext); } internal static Expression MakePropertyCall(Expression context, ISchemaProvider? schema, string name, QueryRequestContext requestContext, CompileContext compileContext) diff --git a/src/EntityGraphQL/Compiler/EntityQuery/IMethodProvider.cs b/src/EntityGraphQL/Compiler/EntityQuery/IMethodProvider.cs index b526acd5..0516edcb 100644 --- a/src/EntityGraphQL/Compiler/EntityQuery/IMethodProvider.cs +++ b/src/EntityGraphQL/Compiler/EntityQuery/IMethodProvider.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.Linq.Expressions; -namespace EntityGraphQL.Compiler.EntityQuery +namespace EntityGraphQL.Compiler.EntityQuery; + +public interface IMethodProvider { - public interface IMethodProvider - { - bool EntityTypeHasMethod(Type context, string methodName); - Expression GetMethodContext(Expression context, string methodName); - Expression MakeCall(Expression context, Expression argContext, string methodName, IEnumerable? args, Type type); - } + bool EntityTypeHasMethod(Type context, string methodName); + Expression GetMethodContext(Expression context, string methodName); + Expression MakeCall(Expression context, Expression argContext, string methodName, IEnumerable? args, Type type); } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs b/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs index 3bd141fe..a641d2ca 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/CompileContext.cs @@ -9,9 +9,8 @@ namespace EntityGraphQL.Compiler; /// public class CompileContext { - private readonly List servicesCollected = new(); - private readonly Dictionary constantParameters = new(); - private readonly Dictionary constantParametersForField = new(); + private readonly Dictionary constantParameters = []; + private readonly Dictionary constantParametersForField = []; public CompileContext(ExecutionOptions options, Dictionary? bulkData, QueryRequestContext requestContext) { @@ -21,15 +20,9 @@ public CompileContext(ExecutionOptions options, Dictionary? bulk RequestContext = requestContext; } - public List Services - { - get => servicesCollected; - } - public IReadOnlyDictionary ConstantParameters - { - get => constantParameters; - } - public List BulkResolvers { get; private set; } = new(); + public List Services { get; } = []; + public IReadOnlyDictionary ConstantParameters => constantParameters; + public List BulkResolvers { get; private set; } = []; public Dictionary? BulkData { get; } public ParameterExpression? BulkParameter { get; } public ExecutionOptions ExecutionOptions { get; } @@ -39,7 +32,7 @@ public void AddServices(IEnumerable services) { foreach (var service in services) { - servicesCollected.Add(service); + Services.Add(service); } } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs index 15d33a99..7d2cb2bc 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/ExecutableGraphQLStatement.cs @@ -25,16 +25,13 @@ public abstract class ExecutableGraphQLStatement : IGraphQLNode /// /// Variables that are expected to be passed in to execute this query /// - protected Dictionary OpDefinedVariables { get; set; } = new(); + protected Dictionary OpDefinedVariables { get; set; } = []; public ISchemaProvider Schema { get; protected set; } public ParameterExpression? OpVariableParameter { get; } public IField? Field { get; } - public bool HasServices - { - get => Field?.Services.Count > 0; - } + public bool HasServices => Field?.Services.Count > 0; public IReadOnlyDictionary Arguments { get; } @@ -109,6 +106,11 @@ QueryRequestContext requestContext } #endif + // often use return null if mutation failed and added errors to validation + // don't include it if it is not a nullable field + if (data == null && fieldNode.Field?.ReturnType.TypeNotNullable == true) + continue; + if (didExecute) result[fieldNode.Name] = data; } @@ -143,7 +145,7 @@ protected static TContext GetContextToUse(TContext? context, IServiceP if (OpDefinedVariables.Count > 0 && OpVariableParameter != null) { - variables ??= new QueryVariables(); + variables ??= []; variablesToUse = Activator.CreateInstance(OpVariableParameter.Type); foreach (var (name, argType) in OpDefinedVariables) { @@ -192,7 +194,7 @@ protected static TContext GetContextToUse(TContext? context, IServiceP Expression? expression = null; var contextParam = node.RootParameter; - if (node.HasServicesAtOrBelow(fragments) && compileContext.ExecutionOptions.ExecuteServiceFieldsSeparately == true) + if (node.HasServicesAtOrBelow(fragments) && compileContext.ExecutionOptions.ExecuteServiceFieldsSeparately) { // build this first as NodeExpression may modify ConstantParameters // this is without fields that require services @@ -233,11 +235,13 @@ protected static TContext GetContextToUse(TContext? context, IServiceP } } +#pragma warning disable IDE0074 // Use compound assignment if (expression == null) { // just do things normally expression = node.GetNodeExpression(compileContext, serviceProvider, fragments, OpVariableParameter, docVariables, contextParam, false, null, null, contextChanged: false, replacer); } +#pragma warning restore IDE0074 // Use compound assignment var data = await ExecuteExpressionAsync(expression, runningContext, contextParam, serviceProvider, replacer, compileContext, node, true); return data; diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDocument.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDocument.cs index aa2ee4e2..65f52adb 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDocument.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLDocument.cs @@ -47,21 +47,15 @@ public class GraphQLDocument : IGraphQLNode public GraphQLDocument(ISchemaProvider schema) { Schema = schema; - Operations = new List(); - Fragments = new List(); + Operations = []; + Fragments = []; Arguments = new Dictionary(); } - public string Name - { - get => "Query Request Root"; - } + public string Name => "Query Request Root"; public IField? Field { get; } - public bool HasServices - { - get => Field?.Services.Count > 0; - } + public bool HasServices => Field?.Services.Count > 0; public IReadOnlyDictionary Arguments { get; } @@ -125,6 +119,9 @@ await op.ExecuteAsync( if (validator?.Errors.Count > 0) result.AddErrors(validator.Errors); + if (result.Data?.Count == 0 && result.HasErrorKey()) + result.RemoveDataKey(); + return result; } diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentSpreadField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentSpreadField.cs index be8fe9bd..cec1e9d6 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentSpreadField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentSpreadField.cs @@ -55,7 +55,7 @@ internal override IEnumerable ExpandFromServices(bool withoutS return Field.ExtractedFieldsFromServices.ToList(); // we do not want to return the fragment field - return withoutServiceFields && HasServices ? new List() : (field != null ? new List { field } : new List()); + return withoutServiceFields && HasServices ? [] : (field != null ? [field] : []); } private static void GetServices(CompileContext compileContext, BaseGraphQLField gqlField) diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs index 6c90dc85..d047c9cb 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLFragmentStatement.cs @@ -12,16 +12,13 @@ public class GraphQLFragmentStatement : IGraphQLNode public ParameterExpression? RootParameter { get; } public IField? Field { get; } - public bool HasServices - { - get => Field?.Services.Count > 0; - } + public bool HasServices => Field?.Services.Count > 0; public IReadOnlyDictionary Arguments { get; } public string Name { get; } - public List QueryFields { get; } = new List(); + public List QueryFields { get; } = []; public ISchemaProvider Schema { get; } public bool IsRootField => false; diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLInlineFragmentField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLInlineFragmentField.cs index 45401ba5..76d4316e 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLInlineFragmentField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLInlineFragmentField.cs @@ -39,7 +39,7 @@ internal override IEnumerable ExpandFromServices(bool withoutS return Field.ExtractedFieldsFromServices.ToList(); // we do not want to return the fragment field - return withoutServiceFields && HasServices ? new List() : (field != null ? new List { field } : new List()); + return withoutServiceFields && HasServices ? [] : (field != null ? [field] : []); } private static void GetServices(CompileContext compileContext, BaseGraphQLField gqlField) diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationStatement.cs index 0edc8424..49ef44f2 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLMutationStatement.cs @@ -64,7 +64,6 @@ QueryRequestContext requestContext #endif var contextToUse = GetContextToUse(context, serviceProvider!, field)!; - var data = await ExecuteAsync(compileContext, node, contextToUse, serviceProvider, fragments, options, docVariables); #if DEBUG if (options.IncludeDebugInfo) @@ -73,6 +72,10 @@ QueryRequestContext requestContext result[$"__{node.Name}_timeMs"] = timer?.ElapsedMilliseconds; } #endif + // often use return null if mutation failed and added errors to validation + // don't include it if it is not a nullable field + if (data == null && node.Field!.ReturnType.TypeNotNullable) + continue; result[node.Name] = data; } } @@ -97,7 +100,6 @@ QueryRequestContext requestContext /// /// The mutation field to execute /// The context instance that will be used - /// Error validator, passed to mutations /// A service provider to look up any dependencies /// /// Execution options diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs index 09c52dc7..fb910f61 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLObjectProjectionField.cs @@ -180,12 +180,21 @@ ParameterReplacer replacer if (contextChanged) { + HashSet propsOrFields = [.. nullWrapParam.Type.GetProperties().Select(i => i.Name), .. nullWrapParam.Type.GetFields().Select(i => i.Name)]; foreach (var item in selectionFields) { if (item.Value.Field.HasServices || item.Key.Name == "__typename") item.Value.Expression = replacer.ReplaceByType(item.Value.Expression, nextFieldContext.Type, nullWrapParam); else - item.Value.Expression = Expression.PropertyOrField(nullWrapParam, item.Key.Name); + { + // if we can just use Expression.PropertyOrField that is faster + if (propsOrFields.Contains(item.Key.Name, StringComparer.OrdinalIgnoreCase)) + item.Value.Expression = Expression.PropertyOrField(nullWrapParam, item.Key.Name); + else + // selecting from a dotnet type (not a anonymous result type) we need to replace the base call not use the field name (item.Key.Name) + // e.g. if we have renamed the field schema.Type().AddField("username", u => u.Name) + item.Value.Expression = replacer.Replace(item.Value.Expression, nextFieldContext, nullWrapParam); + } } } else diff --git a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionStatement.cs b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionStatement.cs index a1f3fb3e..f1dfeeb2 100644 --- a/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionStatement.cs +++ b/src/EntityGraphQL/Compiler/GqlNodes/GraphQLSubscriptionStatement.cs @@ -77,6 +77,12 @@ QueryRequestContext requestContext result[$"__{node.Name}_timeMs"] = timer?.ElapsedMilliseconds; } #endif + + // often use return null if mutation failed and added errors to validation + // don't include it if it is not a nullable field + if (data == null && node.Field!.ReturnType.TypeNotNullable) + continue; + result[node.Name] = data; } } @@ -117,9 +123,8 @@ QueryRequestContext requestContext throw new EntityGraphQLExecutionException($"Subscription {node.Name} returned null. It must return an IObservable"); // result == IObservable - var returnType = result.GetType().GetGenericArgument(typeof(IObservable<>)); - if (returnType == null) - throw new EntityGraphQLExecutionException($"Subscription {node.Name} return type does not implement IObservable"); + var returnType = + result.GetType().GetGenericArgument(typeof(IObservable<>)) ?? throw new EntityGraphQLExecutionException($"Subscription {node.Name} return type does not implement IObservable"); return new GraphQLSubscribeResult(returnType, result, this, node); } diff --git a/src/EntityGraphQL/Compiler/GraphQLCompiler.cs b/src/EntityGraphQL/Compiler/GraphQLCompiler.cs index d15da533..bc02fc8b 100644 --- a/src/EntityGraphQL/Compiler/GraphQLCompiler.cs +++ b/src/EntityGraphQL/Compiler/GraphQLCompiler.cs @@ -28,10 +28,7 @@ public GraphQLCompiler(ISchemaProvider schemaProvider) /// The returned DataQueryNode is a root node, it's Fields are the top level data queries public GraphQLDocument Compile(string query, QueryVariables? variables = null) { - if (variables == null) - { - variables = new QueryVariables(); - } + variables ??= []; return Compile(new QueryRequest { Query = query, Variables = variables }); } diff --git a/src/EntityGraphQL/Compiler/QueryWalkerHelper.cs b/src/EntityGraphQL/Compiler/QueryWalkerHelper.cs index 76f3aeb0..ee5598ec 100644 --- a/src/EntityGraphQL/Compiler/QueryWalkerHelper.cs +++ b/src/EntityGraphQL/Compiler/QueryWalkerHelper.cs @@ -10,157 +10,147 @@ using EntityGraphQL.Schema; using HotChocolate.Language; -namespace EntityGraphQL.Compiler +namespace EntityGraphQL.Compiler; + +public static class QueryWalkerHelper { - public static class QueryWalkerHelper - { - public static readonly Regex GuidRegex = new(@"^[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}$", RegexOptions.IgnoreCase); + public static readonly Regex GuidRegex = new(@"^[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}$", RegexOptions.IgnoreCase); - public static object? ProcessArgumentValue(ISchemaProvider schema, IValueNode argumentValue, string argName, Type argType) + public static object? ProcessArgumentValue(ISchemaProvider schema, IValueNode argumentValue, string argName, Type argType) + { + object? argValue = null; + if (argumentValue.Value != null) { - object? argValue = null; - if (argumentValue.Value != null) + switch (argumentValue.Kind) { - switch (argumentValue.Kind) - { - case SyntaxKind.IntValue: - argValue = argType switch - { - _ when argType == typeof(short) || argType == typeof(short?) => short.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(ushort) || argType == typeof(ushort?) => ushort.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(int) || argType == typeof(int?) => int.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(uint) || argType == typeof(uint?) => uint.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(long) || argType == typeof(long?) => long.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(ulong) || argType == typeof(ulong?) => ulong.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(float) || argType == typeof(float?) => float.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(decimal) || argType == typeof(decimal?) => decimal.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(double) || argType == typeof(double?) => double.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ => argValue - }; - break; - // these ones are the correct type - case SyntaxKind.StringValue: - argValue = (string)argumentValue.Value; - break; - case SyntaxKind.BooleanValue: - argValue = argumentValue.Value; - break; - case SyntaxKind.NullValue: - argValue = null; - break; - case SyntaxKind.EnumValue: - argValue = (string)argumentValue.Value; - break; - case SyntaxKind.ListValue: - argValue = ProcessListArgument(schema, (List)argumentValue.Value, argName, argType); - break; - case SyntaxKind.ObjectValue: - argValue = ProcessObjectValue(schema, argumentValue, argName, argType); - break; - case SyntaxKind.FloatValue: - argValue = argType switch - { - _ when argType == typeof(float) || argType == typeof(float?) => float.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(decimal) || argType == typeof(decimal?) => decimal.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ when argType == typeof(double) || argType == typeof(double?) => double.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), - _ => argValue - }; - break; - } + case SyntaxKind.IntValue: + argValue = argType switch + { + _ when argType == typeof(short) || argType == typeof(short?) => short.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(ushort) || argType == typeof(ushort?) => ushort.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(int) || argType == typeof(int?) => int.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(uint) || argType == typeof(uint?) => uint.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(long) || argType == typeof(long?) => long.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(ulong) || argType == typeof(ulong?) => ulong.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(float) || argType == typeof(float?) => float.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(decimal) || argType == typeof(decimal?) => decimal.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(double) || argType == typeof(double?) => double.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ => argValue, + }; + break; + // these ones are the correct type + case SyntaxKind.StringValue: + argValue = (string)argumentValue.Value; + break; + case SyntaxKind.BooleanValue: + argValue = argumentValue.Value; + break; + case SyntaxKind.NullValue: + argValue = null; + break; + case SyntaxKind.EnumValue: + argValue = (string)argumentValue.Value; + break; + case SyntaxKind.ListValue: + argValue = ProcessListArgument(schema, (List)argumentValue.Value, argName, argType); + break; + case SyntaxKind.ObjectValue: + argValue = ProcessObjectValue(schema, argumentValue, argName, argType); + break; + case SyntaxKind.FloatValue: + argValue = argType switch + { + _ when argType == typeof(float) || argType == typeof(float?) => float.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(decimal) || argType == typeof(decimal?) => decimal.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ when argType == typeof(double) || argType == typeof(double?) => double.Parse(argumentValue.Value.ToString()!, CultureInfo.InvariantCulture), + _ => argValue, + }; + break; } - - return ExpressionUtil.ConvertObjectType(argValue, argType, schema, null); } - private static object ProcessObjectValue(ISchemaProvider schema, IValueNode argumentValue, string argName, Type argType) - { - // this should be an Input type - // see if it has an empty constructor or a constructor that matches the fields - var objectValues = argumentValue.Value as List; - if (objectValues == null) - throw new EntityGraphQLCompilerException($"Argument {argName} is not an object"); - - var constructor = argType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0 || c.GetParameters().Length == objectValues.Count); - // make object - if (constructor == null) - throw new EntityGraphQLCompilerException($"No constructor found for object argument {argName}"); - - var constructorParameters = constructor.GetParameters(); - if (constructorParameters.Length > 0) - { - object[] constructorArgs = new object[constructorParameters.Length]; - - // objectValue.Fields can be looked up by the constructor parameter name - for (int i = 0; i < constructorParameters.Length; i++) - { - var field = objectValues.FirstOrDefault(f => f.Name.Value == constructorParameters[i].Name); - if (field == null) - throw new EntityGraphQLCompilerException($"Field '{constructorParameters[i].Name}' not found in argument object {argName}"); - - constructorArgs[i] = - ProcessArgumentValue(schema, field.Value, argName, constructorParameters[i].ParameterType) - ?? throw new EntityGraphQLCompilerException($"Field '{constructorParameters[i].Name}' is null in argument object {argName}"); - } - - // Create the object using the specific constructor - var argObj = constructor.Invoke(constructorArgs); - return argObj; - } + return ExpressionUtil.ConvertObjectType(argValue, argType, schema, null); + } - var obj = Activator.CreateInstance(argType)!; + private static object ProcessObjectValue(ISchemaProvider schema, IValueNode argumentValue, string argName, Type argType) + { + // this should be an Input type + // see if it has an empty constructor or a constructor that matches the fields + if (argumentValue.Value is not List objectValues) + throw new EntityGraphQLCompilerException($"Argument {argName} is not an object"); + + var constructor = + argType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0 || c.GetParameters().Length == objectValues.Count) + ?? throw new EntityGraphQLCompilerException($"No constructor found for object argument {argName}"); + var constructorParameters = constructor.GetParameters(); + if (constructorParameters.Length > 0) + { + object[] constructorArgs = new object[constructorParameters.Length]; - object argValue; - var schemaType = schema.GetSchemaType(argType, true, null); - foreach (var item in objectValues) + // objectValue.Fields can be looked up by the constructor parameter name + for (int i = 0; i < constructorParameters.Length; i++) { - if (!schemaType.HasField(item.Name.Value, null)) - throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' not found of type '{schemaType.Name}'"); - var schemaField = schemaType.GetField(item.Name.Value, null); - - if (schemaField.ResolveExpression == null) - throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' on type '{schemaType.Name}' has no resolve expression"); - - var nameFromType = ((MemberExpression)schemaField.ResolveExpression).Member.Name; - var prop = argType.GetProperty(nameFromType); - - if (prop == null) - { - var field = argType.GetField(nameFromType); - if (field == null) - throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' not found on object argument"); - field.SetValue(obj, ProcessArgumentValue(schema, item.Value, argName, field.FieldType)); - } - else - { - prop.SetValue(obj, ProcessArgumentValue(schema, item.Value, argName, prop.PropertyType)); - } + var field = + objectValues.FirstOrDefault(f => f.Name.Value == constructorParameters[i].Name) + ?? throw new EntityGraphQLCompilerException($"Field '{constructorParameters[i].Name}' not found in argument object {argName}"); + constructorArgs[i] = + ProcessArgumentValue(schema, field.Value, argName, constructorParameters[i].ParameterType) + ?? throw new EntityGraphQLCompilerException($"Field '{constructorParameters[i].Name}' is null in argument object {argName}"); } - argValue = obj; - return argValue; + + // Create the object using the specific constructor + var argObj = constructor.Invoke(constructorArgs); + return argObj; } - public static object ProcessListArgument(ISchemaProvider schema, List values, string argName, Type fieldArgType) + var obj = Activator.CreateInstance(argType)!; + + object argValue; + var schemaType = schema.GetSchemaType(argType, true, null); + foreach (var item in objectValues) { - IList list; - if (fieldArgType.IsInterface && fieldArgType.IsGenericType && fieldArgType.IsGenericTypeEnumerable()) - list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(fieldArgType.GetEnumerableOrArrayType()!))!; - else - list = (IList)Activator.CreateInstance(fieldArgType, values.Count)!; + if (!schemaType.HasField(item.Name.Value, null)) + throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' not found of type '{schemaType.Name}'"); + var schemaField = schemaType.GetField(item.Name.Value, null); + + if (schemaField.ResolveExpression == null) + throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' on type '{schemaType.Name}' has no resolve expression"); - var listType = list.GetType().GetEnumerableOrArrayType(); - if (listType == null) - throw new EntityGraphQLCompilerException($"Argument {argName} is not a list"); + var nameFromType = ((MemberExpression)schemaField.ResolveExpression).Member.Name; + var prop = argType.GetProperty(nameFromType); - for (int i = 0; i < values.Count; i++) + if (prop == null) + { + var field = argType.GetField(nameFromType) ?? throw new EntityGraphQLCompilerException($"Field '{item.Name.Value}' not found on object argument"); + field.SetValue(obj, ProcessArgumentValue(schema, item.Value, argName, field.FieldType)); + } + else { - IValueNode? item = values[i]; - if (fieldArgType.IsArray) - list[i] = ProcessArgumentValue(schema, item, argName, listType); - else - list.Add(ProcessArgumentValue(schema, item, argName, listType)); + prop.SetValue(obj, ProcessArgumentValue(schema, item.Value, argName, prop.PropertyType)); } + } + argValue = obj; + return argValue; + } - return list; + public static object ProcessListArgument(ISchemaProvider schema, List values, string argName, Type fieldArgType) + { + IList list; + if (fieldArgType.IsInterface && fieldArgType.IsGenericType && fieldArgType.IsGenericTypeEnumerable()) + list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(fieldArgType.GetEnumerableOrArrayType()!))!; + else + list = (IList)Activator.CreateInstance(fieldArgType, values.Count)!; + + var listType = list.GetType().GetEnumerableOrArrayType() ?? throw new EntityGraphQLCompilerException($"Argument {argName} is not a list"); + for (int i = 0; i < values.Count; i++) + { + IValueNode? item = values[i]; + if (fieldArgType.IsArray) + list[i] = ProcessArgumentValue(schema, item, argName, listType); + else + list.Add(ProcessArgumentValue(schema, item, argName, listType)); } + + return list; } } diff --git a/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs b/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs index 586dd6e5..8c90a1c3 100644 --- a/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs +++ b/src/EntityGraphQL/Compiler/Util/ArgumentUtil.cs @@ -26,7 +26,7 @@ List validationErrors { return new(); } - + // get the values for the argument anonymous type object constructor var values = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/EntityGraphQL/Compiler/Util/CompileHelper.cs b/src/EntityGraphQL/Compiler/Util/CompileHelper.cs index 38ad9058..5807c61a 100644 --- a/src/EntityGraphQL/Compiler/Util/CompileHelper.cs +++ b/src/EntityGraphQL/Compiler/Util/CompileHelper.cs @@ -5,72 +5,71 @@ using EntityGraphQL.Compiler.Util; using EntityGraphQL.Schema; -namespace EntityGraphQL.Compiler +namespace EntityGraphQL.Compiler; + +public static class GraphQLHelper { - public static class GraphQLHelper + public static Expression InjectServices( + IServiceProvider serviceProvider, + IEnumerable services, + List allArgs, + Expression expression, + List parameters, + ParameterReplacer replacer + ) { - public static Expression InjectServices( - IServiceProvider serviceProvider, - IEnumerable services, - List allArgs, - Expression expression, - List parameters, - ParameterReplacer replacer - ) + foreach (var serviceParam in services) { - foreach (var serviceParam in services) - { - // We create a new parameter so each time the expression is used the - // serviceProvider.GetService is used and the rules registered with the service provider are used - // e.g. a new instance or a singleton etc. - var srvParam = Expression.Parameter(serviceParam.Type, $"exec_{serviceParam.Name}"); - parameters.Add(srvParam); - var service = serviceProvider.GetService(serviceParam.Type) ?? throw new EntityGraphQLExecutionException($"Service {serviceParam.Type.Name} not found in service provider"); - allArgs.Add(service); + // We create a new parameter so each time the expression is used the + // serviceProvider.GetService is used and the rules registered with the service provider are used + // e.g. a new instance or a singleton etc. + var srvParam = Expression.Parameter(serviceParam.Type, $"exec_{serviceParam.Name}"); + parameters.Add(srvParam); + var service = serviceProvider.GetService(serviceParam.Type) ?? throw new EntityGraphQLExecutionException($"Service {serviceParam.Type.Name} not found in service provider"); + allArgs.Add(service); - expression = replacer.Replace(expression, serviceParam, srvParam); - } - - return expression; + expression = replacer.Replace(expression, serviceParam, srvParam); } - public static Dictionary ExpressionOnly(this Dictionary source) + return expression; + } + + public static Dictionary ExpressionOnly(this Dictionary source) + { + return source.ToDictionary(i => i.Key.Name, i => i.Value.Expression); + } + + public static void ValidateAndReplaceFieldArgs( + IField field, + ParameterExpression? argsParam, + ParameterReplacer replacer, + ref object? argumentValue, + ref Expression result, + List validationErrors, + ParameterExpression? newArgParam + ) + { + // replace the arg param after extensions (don't rely on extensions to do this) + if (argsParam != null && newArgParam != null && argsParam != newArgParam) { - return source.ToDictionary(i => i.Key.Name, i => i.Value.Expression); + result = replacer.Replace(result, argsParam, newArgParam); } - public static void ValidateAndReplaceFieldArgs( - IField field, - ParameterExpression? argsParam, - ParameterReplacer replacer, - ref object? argumentValue, - ref Expression result, - List validationErrors, - ParameterExpression? newArgParam - ) + if (field.Validators.Count > 0) { - // replace the arg param after extensions (don't rely on extensions to do this) - if (argsParam != null && newArgParam != null && argsParam != newArgParam) + var invokeContext = new ArgumentValidatorContext(field, argumentValue); + foreach (var m in field.Validators) { - result = replacer.Replace(result, argsParam, newArgParam); + m(invokeContext); + argumentValue = invokeContext.Arguments; } - if (field.Validators.Count > 0) - { - var invokeContext = new ArgumentValidatorContext(field, argumentValue); - foreach (var m in field.Validators) - { - m(invokeContext); - argumentValue = invokeContext.Arguments; - } - - validationErrors.AddRange(invokeContext.Errors); - } + validationErrors.AddRange(invokeContext.Errors); + } - if (validationErrors.Count > 0) - { - throw new EntityGraphQLValidationException(validationErrors); - } + if (validationErrors.Count > 0) + { + throw new EntityGraphQLValidationException(validationErrors); } } } diff --git a/src/EntityGraphQL/Compiler/Util/ExpressionExtractor.cs b/src/EntityGraphQL/Compiler/Util/ExpressionExtractor.cs index e16d43be..0df2f72b 100644 --- a/src/EntityGraphQL/Compiler/Util/ExpressionExtractor.cs +++ b/src/EntityGraphQL/Compiler/Util/ExpressionExtractor.cs @@ -4,136 +4,135 @@ using System.Text.RegularExpressions; using EntityGraphQL.Extensions; -namespace EntityGraphQL.Compiler.Util +namespace EntityGraphQL.Compiler.Util; + +/// +/// Extracts expression with the root context as the provided ParameterExpression. +/// Useful for getting required fields out of a Resolve() call. +/// For example if the full expression is the below and the root context is ctx +/// myService.CallThis(ctx.Field1, otherService.Call(ctx.Child.Field2)) +/// We extract the following expressions: +/// ctx.Field1 +/// ctx.Child.Field2 +/// +public class ExpressionExtractor : ExpressionVisitor { - /// - /// Extracts expression with the root context as the provided ParameterExpression. - /// Useful for getting required fields out of a Resolve() call. - /// For example if the full expression is the below and the root context is ctx - /// myService.CallThis(ctx.Field1, otherService.Call(ctx.Child.Field2)) - /// We extract the following expressions: - /// ctx.Field1 - /// ctx.Child.Field2 - /// - public class ExpressionExtractor : ExpressionVisitor - { - private readonly Regex pattern = new("[\\.\\(\\)\\!]"); + private readonly Regex pattern = new("[\\.\\(\\)\\!]"); - private Expression? rootContext; + private Expression? rootContext; - // We extract all expression - which may repeat - and we then replace them by matching the expression object - private Dictionary>? extractedExpressions; + // We extract all expression - which may repeat - and we then replace them by matching the expression object + private Dictionary>? extractedExpressions; - /// - /// Current expression we might extract. - /// - private readonly Stack currentExpression = new(); - private bool matchByType; + /// + /// Current expression we might extract. + /// + private readonly Stack currentExpression = new(); + private bool matchByType; - public IDictionary>? Extract(Expression node, Expression rootContext, bool matchByType = false) - { - this.rootContext = rootContext; - extractedExpressions = new Dictionary>(); - currentExpression.Clear(); - this.matchByType = matchByType; - Visit(node); - return extractedExpressions.Count > 0 ? extractedExpressions : null; - } + public IDictionary>? Extract(Expression node, Expression rootContext, bool matchByType = false) + { + this.rootContext = rootContext; + extractedExpressions = []; + currentExpression.Clear(); + this.matchByType = matchByType; + Visit(node); + return extractedExpressions.Count > 0 ? extractedExpressions : null; + } - protected override Expression VisitParameter(ParameterExpression node) - { - if (rootContext == null) - throw new EntityGraphQLCompilerException("Root context not set for ExpressionExtractor"); + protected override Expression VisitParameter(ParameterExpression node) + { + if (rootContext == null) + throw new EntityGraphQLCompilerException("Root context not set for ExpressionExtractor"); - if ((rootContext == node || (matchByType && rootContext.Type == node.Type)) && currentExpression.Count > 0) + if ((rootContext == node || (matchByType && rootContext.Type == node.Type)) && currentExpression.Count > 0) + { + var expressionItem = currentExpression.Peek(); + // use the expression as the extracted field name as it will be unique + var name = "egql__" + pattern.Replace(expressionItem.ToString(), "_"); + if (extractedExpressions!.TryGetValue(name, out var existing)) { - var expressionItem = currentExpression.Peek(); - // use the expression as the extracted field name as it will be unique - var name = pattern.Replace(expressionItem.ToString(), "_"); - if (extractedExpressions!.TryGetValue(name, out var existing)) - { - existing.Add(expressionItem); - } - else - { - extractedExpressions[name] = [expressionItem]; - } + existing.Add(expressionItem); } - return base.VisitParameter(node); - } - - protected override Expression VisitMember(MemberExpression node) - { - // if is is a nullable type we want to extract the nullable field not the nullableField.HasValue/Value - // node.Expression can be null if it is a static member - e.g. DateTime.MaxValue - // if it is empty this is the end of an expression too - if ((currentExpression.Count == 0 && node.Expression?.Type.IsNullableType() == false) || node.Type.IsNullableType() == true) + else { - currentExpression.Push(node); - var result = base.VisitMember(node); - currentExpression.Pop(); - return result; + extractedExpressions[name] = [expressionItem]; } - return base.VisitMember(node); } + return base.VisitParameter(node); + } - protected override Expression VisitBinary(BinaryExpression node) + protected override Expression VisitMember(MemberExpression node) + { + // if is is a nullable type we want to extract the nullable field not the nullableField.HasValue/Value + // node.Expression can be null if it is a static member - e.g. DateTime.MaxValue + // if it is empty this is the end of an expression too + if ((currentExpression.Count == 0 && node.Expression?.Type.IsNullableType() == false) || node.Type.IsNullableType()) { - ProcessPotentialLeaf(node.Left); - ProcessPotentialLeaf(node.Right); + currentExpression.Push(node); + var result = base.VisitMember(node); + currentExpression.Pop(); + return result; + } + return base.VisitMember(node); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + ProcessPotentialLeaf(node.Left); + ProcessPotentialLeaf(node.Right); - return node; + return node; + } + + private void ProcessPotentialLeaf(Expression node) + { + var shouldAddNotAdd = node.NodeType == ExpressionType.MemberAccess && ((MemberExpression)node).Expression?.Type.IsNullableType() == true; + if (shouldAddNotAdd) + Visit(node); + else + { + currentExpression.Push(node); + Visit(node); + currentExpression.Pop(); } + } - private void ProcessPotentialLeaf(Expression node) + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Object != null) { - var shouldAddNotAdd = node.NodeType == ExpressionType.MemberAccess && ((MemberExpression)node).Expression?.Type.IsNullableType() == true; - if (shouldAddNotAdd) - Visit(node); - else + if (currentExpression.Count == 0) { + // only need to extract if this is not part of a larger expression currentExpression.Push(node); - Visit(node); + Visit(node.Object); currentExpression.Pop(); } + else + Visit(node.Object); } - - protected override Expression VisitMethodCall(MethodCallExpression node) + var startAt = 0; + // only need to extract if this is not part of a larger expression + // and only visit args[0] with the whole node if it is an extension method + bool isExtension = node.Method.IsDefined(typeof(ExtensionAttribute), true); + if (node.Object is null && isExtension) { - if (node.Object != null) - { - if (currentExpression.Count == 0) - { - // only need to extract if this is not part of a larger expression - currentExpression.Push(node); - Visit(node.Object); - currentExpression.Pop(); - } - else - Visit(node.Object); - } - var startAt = 0; - // only need to extract if this is not part of a larger expression - // and only visit args[0] with the whole node if it is an extension method - bool isExtension = node.Method.IsDefined(typeof(ExtensionAttribute), true); - if (node.Object is null && isExtension) - { - startAt = 1; - if (currentExpression.Count == 0) - { - currentExpression.Push(node); - Visit(node.Arguments[0]); - currentExpression.Pop(); - } - else - Visit(node.Arguments[0]); - } - for (int i = startAt; i < node.Arguments.Count; i++) + startAt = 1; + if (currentExpression.Count == 0) { - // each arg might be extractable but we should end up back in a acll or member access again - ProcessPotentialLeaf(node.Arguments[i]); + currentExpression.Push(node); + Visit(node.Arguments[0]); + currentExpression.Pop(); } - return node; + else + Visit(node.Arguments[0]); + } + for (int i = startAt; i < node.Arguments.Count; i++) + { + // each arg might be extractable but we should end up back in a acll or member access again + ProcessPotentialLeaf(node.Arguments[i]); } + return node; } } diff --git a/src/EntityGraphQL/Compiler/Util/ExpressionReplacer.cs b/src/EntityGraphQL/Compiler/Util/ExpressionReplacer.cs index ca153358..ad55e70f 100644 --- a/src/EntityGraphQL/Compiler/Util/ExpressionReplacer.cs +++ b/src/EntityGraphQL/Compiler/Util/ExpressionReplacer.cs @@ -2,129 +2,128 @@ using System.Collections.Generic; using System.Linq.Expressions; -namespace EntityGraphQL.Compiler.Util +namespace EntityGraphQL.Compiler.Util; + +/// +/// Used to replace whole expressions in a service field expression +/// +public class ExpressionReplacer : ExpressionVisitor { + private readonly Expression newContext; + private readonly bool replaceInline; + private readonly bool replaceWithNewContext; + private readonly List? possibleNextContextTypes; + private readonly Dictionary expressionsToReplace = []; + /// - /// Used to replace whole expressions in a service field expression + /// /// - public class ExpressionReplacer : ExpressionVisitor + /// + /// + /// If true, the matched expression is replaced. If false it will be rebuilt with potential a new name + public ExpressionReplacer(IEnumerable expressionsToReplace, Expression newContext, bool replaceInline, bool replaceWithNewContext, List? possibleNextContextTypes) { - private readonly Expression newContext; - private readonly bool replaceInline; - private readonly bool replaceWithNewContext; - private readonly List? possibleNextContextTypes; - private readonly Dictionary expressionsToReplace = new(); - - /// - /// - /// - /// - /// - /// If true, the matched expression is replaced. If false it will be rebuilt with potential a new name - public ExpressionReplacer(IEnumerable expressionsToReplace, Expression newContext, bool replaceInline, bool replaceWithNewContext, List? possibleNextContextTypes) + this.newContext = newContext; + this.replaceInline = replaceInline; + this.replaceWithNewContext = replaceWithNewContext; + this.possibleNextContextTypes = possibleNextContextTypes; + foreach (var field in expressionsToReplace) { - this.newContext = newContext; - this.replaceInline = replaceInline; - this.replaceWithNewContext = replaceWithNewContext; - this.possibleNextContextTypes = possibleNextContextTypes; - foreach (var field in expressionsToReplace) + foreach (var exp in field.FieldExpressions) { - foreach (var exp in field.FieldExpressions) - { - this.expressionsToReplace.Add(exp, field); - } + this.expressionsToReplace.Add(exp, field); } } + } - /// - /// Visit the baseExpression and replace any expressions that match the expressionsToReplace with the newContext - /// - /// Expression to visit and look for matching expressions - /// - public Expression Replace(Expression baseExpression) - { - return base.Visit(baseExpression); - } + /// + /// Visit the baseExpression and replace any expressions that match the expressionsToReplace with the newContext + /// + /// Expression to visit and look for matching expressions + /// + public Expression Replace(Expression baseExpression) + { + return base.Visit(baseExpression); + } - protected override Expression VisitParameter(ParameterExpression node) + protected override Expression VisitParameter(ParameterExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline || replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitParameter(node); + if (replaceInline || replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitParameter(node); + } - protected override Expression VisitLambda(Expression node) + protected override Expression VisitLambda(Expression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline || replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitLambda(node); + if (replaceInline || replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitLambda(node); + } - protected override Expression VisitUnary(UnaryExpression node) + protected override Expression VisitUnary(UnaryExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline || replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitUnary(node); + if (replaceInline || replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitUnary(node); + } - protected override Expression VisitMember(MemberExpression node) + protected override Expression VisitMember(MemberExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline) - return Expression.PropertyOrField(newContext, node.Member.Name); - if (replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitMember(node); + if (replaceInline) + return Expression.PropertyOrField(newContext, node.Member.Name); + if (replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitMember(node); + } - protected override Expression VisitConstant(ConstantExpression node) + protected override Expression VisitConstant(ConstantExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline || replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitConstant(node); + if (replaceInline || replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitConstant(node); + } - protected override Expression VisitMethodCall(MethodCallExpression node) + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline) - return Expression.Call(newContext, node.Method); - if (replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitMethodCall(node); + if (replaceInline) + return Expression.Call(newContext, node.Method); + if (replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitMethodCall(node); + } - protected override Expression VisitBinary(BinaryExpression node) + protected override Expression VisitBinary(BinaryExpression node) + { + if (expressionsToReplace.ContainsKey(node)) { - if (expressionsToReplace.ContainsKey(node)) - { - if (replaceInline || replaceWithNewContext) - return newContext; - return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); - } - return base.VisitBinary(node); + if (replaceInline || replaceWithNewContext) + return newContext; + return expressionsToReplace[node].GetNodeExpression(newContext, possibleNextContextTypes); } + return base.VisitBinary(node); } } diff --git a/src/EntityGraphQL/Compiler/Util/ExpressionUtil.cs b/src/EntityGraphQL/Compiler/Util/ExpressionUtil.cs index 3496c3aa..0b25ddc5 100644 --- a/src/EntityGraphQL/Compiler/Util/ExpressionUtil.cs +++ b/src/EntityGraphQL/Compiler/Util/ExpressionUtil.cs @@ -691,7 +691,7 @@ List validTypes /// This wraps the field expression that does the call once /// internal static Expression WrapObjectProjectionFieldForNullCheck( - string fieldDescription, + string fieldName, Expression nullCheckExpression, IEnumerable paramsForFieldExpressions, Dictionary fieldExpressions, @@ -702,7 +702,7 @@ Expression schemaContext { var arguments = new Expression[] { - Expression.Constant(fieldDescription), + Expression.Constant(fieldName), nullCheckExpression, Expression.Constant(nullWrapParam, typeof(ParameterExpression)), Expression.Constant(paramsForFieldExpressions.ToList()), diff --git a/src/EntityGraphQL/Compiler/Util/ParameterReplacer.cs b/src/EntityGraphQL/Compiler/Util/ParameterReplacer.cs index 54b1d739..b60b468c 100644 --- a/src/EntityGraphQL/Compiler/Util/ParameterReplacer.cs +++ b/src/EntityGraphQL/Compiler/Util/ParameterReplacer.cs @@ -2,306 +2,304 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Xml.Linq; using EntityGraphQL.Extensions; -namespace EntityGraphQL.Compiler.Util +namespace EntityGraphQL.Compiler.Util; + +/// +/// As people build schema fields they may be against different parameters, this visitor lets us change it to the one used in compiling the EQL. +/// I.e. in (p_0) => p_0.blah `p_0` needs to be the same object otherwise it expects 2 values passed in. +/// +public class ParameterReplacer : ExpressionVisitor { + private Expression? newParam; + private Type? toReplaceType; + private Expression? toReplace; + private bool finished; + private bool replaceWholeExpression; + private string? newFieldNameForType; + private Dictionary cache = []; + private bool hasNewFieldNameForType; + /// - /// As people build schema fields they may be against different parameters, this visitor lets us change it to the one used in compiling the EQL. - /// I.e. in (p_0) => p_0.blah `p_0` needs to be the same object otherwise it expects 2 values passed in. + /// Rebuilds the expression by replacing toReplace with newParam. + /// Used when rebuilding expressions to happen from the sans-services results /// - public class ParameterReplacer : ExpressionVisitor + /// Expression to search through + /// Expression to match for replacement + /// New expression to be used in the expression tree + /// + /// + public Expression Replace(Expression node, Expression toReplace, Expression newParam, bool replaceWholeExpression = false) { - private Expression? newParam; - private Type? toReplaceType; - private Expression? toReplace; - private bool finished; - private bool replaceWholeExpression; - private string? newFieldNameForType; - private Dictionary cache = []; - private bool hasNewFieldNameForType; + if (node == toReplace) + return newParam; - /// - /// Rebuilds the expression by replacing toReplace with newParam. Optionally looks for newFieldName as it is rebuilding. - /// Used when rebuilding expressions to happen from the sans-services results - /// - /// - /// - /// - /// - /// - public Expression Replace(Expression node, Expression toReplace, Expression newParam, bool replaceWholeExpression = false) - { - if (node == toReplace) - return newParam; + this.newParam = newParam; + this.toReplace = toReplace; + this.toReplaceType = null; + this.newFieldNameForType = null; + finished = false; + this.replaceWholeExpression = replaceWholeExpression; + cache = []; + return Visit(node); + } - this.newParam = newParam; - this.toReplace = toReplace; - this.toReplaceType = null; - this.newFieldNameForType = null; - finished = false; - this.replaceWholeExpression = replaceWholeExpression; - cache = []; - return Visit(node); - } + /// + /// Replace an expression of type with newParam. + /// Try to avoid using this if possible as there might be multiple of the type in the expression that you do not want to replace. + /// + /// + /// + /// + /// + /// + public Expression ReplaceByType(Expression node, Type toReplaceType, Expression newParam, string? newContextFieldName = null) + { + this.newParam = newParam; + this.toReplaceType = toReplaceType; + this.toReplace = null; + finished = false; + this.newFieldNameForType = newContextFieldName; + hasNewFieldNameForType = newFieldNameForType != null; + replaceWholeExpression = false; + cache = []; + return Visit(node); + } - /// - /// Replace an expression of type with newParam. - /// Try to avoid using this if possible as there might be multiple of the type in the expression that you do not want to replace. - /// - /// - /// - /// - /// - /// - public Expression ReplaceByType(Expression node, Type toReplaceType, Expression newParam, string? newContextFieldName = null) - { - this.newParam = newParam; - this.toReplaceType = toReplaceType; - this.toReplace = null; - finished = false; - this.newFieldNameForType = newContextFieldName; - hasNewFieldNameForType = newFieldNameForType != null; - replaceWholeExpression = false; - cache = new Dictionary(); - return Visit(node); - } + protected override Expression VisitParameter(ParameterExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - protected override Expression VisitParameter(ParameterExpression node) - { - if (toReplace != null && toReplace == node) - return newParam!; + if (toReplaceType != null && node.NodeType == ExpressionType.Parameter && toReplaceType == node.Type) + return newParam!; + return node; + } - if (toReplaceType != null && node.NodeType == ExpressionType.Parameter && toReplaceType == node.Type) - return newParam!; - return node; - } + protected override Expression VisitLambda(Expression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - protected override Expression VisitLambda(Expression node) - { - if (toReplace != null && toReplace == node) - return newParam!; + var p = node.Parameters.Select(base.Visit).Cast(); + var body = base.Visit(node.Body); + return Expression.Lambda(body, p); + } - var p = node.Parameters.Select(base.Visit).Cast(); - var body = base.Visit(node.Body); - return Expression.Lambda(body, p); - } + protected override Expression VisitUnary(UnaryExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - protected override Expression VisitUnary(UnaryExpression node) + // RequiredField<> causes a convert + if (node.Operand != null) { - if (toReplace != null && toReplace == node) - return newParam!; - - // RequiredField<> causes a convert - if (node.Operand != null) - { - var newNode = base.Visit(node.Operand); - if (node.NodeType == ExpressionType.Convert && node.Type == newNode.Type) - return newNode; - return Expression.MakeUnary(node.NodeType, newNode, node.Type); - } - return base.VisitUnary(node); + var newNode = base.Visit(node.Operand); + if (node.NodeType == ExpressionType.Convert && node.Type == newNode.Type) + return newNode; + return Expression.MakeUnary(node.NodeType, newNode, node.Type); } + return base.VisitUnary(node); + } - protected override Expression VisitMember(MemberExpression node) - { - if (toReplace != null && toReplace == node) - return newParam!; + protected override Expression VisitMember(MemberExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - // returned expression may have been modified and we need to rebuild - if (node.Expression != null) + // returned expression may have been modified and we need to rebuild + if (node.Expression != null) + { + if (replaceWholeExpression) { - if (replaceWholeExpression) + // RequiredField<> causes a ctx.Field.Value + if (node.Expression == toReplace || (node.Expression.NodeType == ExpressionType.MemberAccess && ((MemberExpression)node.Expression).Expression == toReplace)) { - // RequiredField<> causes a ctx.Field.Value - if (node.Expression == toReplace || (node.Expression.NodeType == ExpressionType.MemberAccess && ((MemberExpression)node.Expression).Expression == toReplace)) - { - return newParam!; - } + return newParam!; } + } - Expression nodeExp; - var fieldName = node.Member.Name; - if (node.Expression.Type == toReplaceType) - { - nodeExp = newParam!; - if (hasNewFieldNameForType) - fieldName = newFieldNameForType!; - } - else - nodeExp = base.Visit(node.Expression); + Expression nodeExp; + var fieldName = node.Member.Name; + if (node.Expression.Type == toReplaceType) + { + nodeExp = newParam!; + if (hasNewFieldNameForType) + fieldName = newFieldNameForType!; + } + else + nodeExp = base.Visit(node.Expression); - if (finished) - return nodeExp; + if (finished) + return nodeExp; - var field = nodeExp.Type.GetField(fieldName); - if (field != null) - nodeExp = Expression.Field(nodeExp, field); + var field = nodeExp.Type.GetField(fieldName); + if (field != null) + nodeExp = Expression.Field(nodeExp, field); + else + { + var prop = nodeExp.Type.GetProperty(fieldName); + if (prop != null) + nodeExp = Expression.Property(nodeExp, prop); else { - var prop = nodeExp.Type.GetProperty(fieldName); - if (prop != null) - nodeExp = Expression.Property(nodeExp, prop); - else + try { - try - { - // If the field is a nullable type we need to get the value - if (nodeExp.Type.IsValueType && nodeExp.Type.IsNullableType()) - nodeExp = Expression.PropertyOrField(nodeExp, nameof(Nullable.Value)); - // extracted fields get flatten as they are selected in the first pass. The new expression can be built - nodeExp = Expression.PropertyOrField(nodeExp, node.Member.Name); - } - catch (ArgumentException) + // If the field is a nullable type we need to get the value + if (nodeExp.Type.IsValueType && nodeExp.Type.IsNullableType()) + nodeExp = Expression.PropertyOrField(nodeExp, nameof(Nullable.Value)); + // extracted fields get flatten as they are selected in the first pass. The new expression can be built + nodeExp = Expression.PropertyOrField(nodeExp, node.Member.Name); + } + catch (ArgumentException) + { + if (nodeExp == null) { - if (nodeExp == null) - { - throw new EntityGraphQLCompilerException($"Could not find field {node.Member.Name} on type {node.Type.Name}"); - } + throw new EntityGraphQLCompilerException($"Could not find field {node.Member.Name} on type {node.Type.Name}"); } } } - return nodeExp; } - return base.VisitMember(node); + return nodeExp; } + return base.VisitMember(node); + } - protected override Expression VisitMemberInit(MemberInitExpression node) - { - if (toReplace != null && toReplace == node) - return newParam!; + protected override Expression VisitMemberInit(MemberInitExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; + + return base.VisitMemberInit(node); + } - return base.VisitMemberInit(node); + protected override Expression VisitBinary(BinaryExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; + + var left = base.Visit(node.Left); + var right = base.Visit(node.Right); + // as we do not replace constants we need to make sure the types match as + // we now have dynamic types and the null might be of the original type + var leftNonNullableType = left.Type.GetNonNullableType(); + var rightNonNullableType = right.Type.GetNonNullableType(); + if (left.NodeType == ExpressionType.Constant && leftNonNullableType != rightNonNullableType) + { + left = Expression.Constant(null, right.Type); } + if (right.NodeType == ExpressionType.Constant && rightNonNullableType != leftNonNullableType) + { + right = Expression.Constant(null, left.Type); + } + var bin = Expression.MakeBinary(node.NodeType, left, right, node.IsLiftedToNull, node.Method); + return bin; + } + + protected override Expression VisitConditional(ConditionalExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - protected override Expression VisitBinary(BinaryExpression node) + var test = base.Visit(node.Test); + var ifTrue = base.Visit(node.IfTrue); + var ifFalse = base.Visit(node.IfFalse); + var type = node.Type; + if (node.Type.IsEnumerableOrArray() && (node.Type != ifTrue.Type || node.Type != ifFalse.Type)) { - if (toReplace != null && toReplace == node) - return newParam!; + // we may have changed a IEnumerable to an IEnumerable where TDynamic is a dynamic type + // built for the graph selection - var left = base.Visit(node.Left); - var right = base.Visit(node.Right); - // as we do not replace constants we need to make sure the types match as - // we now have dynamic types and the null might be of the original type - var leftNonNullableType = left.Type.GetNonNullableType(); - var rightNonNullableType = right.Type.GetNonNullableType(); - if (left.NodeType == ExpressionType.Constant && leftNonNullableType != rightNonNullableType) + // We create a NewArrayInit as part of the bulk service loading + if (ifTrue.NodeType == ExpressionType.NewArrayInit) { - left = Expression.Constant(null, right.Type); + var ifFalseType = ifFalse.Type.GetEnumerableOrArrayType()!; + ifTrue = Expression.NewArrayInit(ifFalseType); + type = typeof(IEnumerable<>).MakeGenericType(ifFalseType); } - if (right.NodeType == ExpressionType.Constant && rightNonNullableType != leftNonNullableType) + if (ifFalse.NodeType == ExpressionType.NewArrayInit) { - right = Expression.Constant(null, left.Type); + var ifTrueType = ifTrue.Type.GetEnumerableOrArrayType()!; + ifFalse = Expression.NewArrayInit(ifTrueType); + type = typeof(IEnumerable<>).MakeGenericType(ifTrueType); } - var bin = Expression.MakeBinary(node.NodeType, left, right, node.IsLiftedToNull, node.Method); - return bin; } + var cond = Expression.Condition(test, ifTrue, ifFalse, type); + return cond; + } - protected override Expression VisitConditional(ConditionalExpression node) - { - if (toReplace != null && toReplace == node) - return newParam!; + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (toReplace != null && toReplace == node) + return newParam!; - var test = base.Visit(node.Test); - var ifTrue = base.Visit(node.IfTrue); - var ifFalse = base.Visit(node.IfFalse); - var type = node.Type; - if (node.Type.IsEnumerableOrArray() && (node.Type != ifTrue.Type || node.Type != ifFalse.Type)) + bool baseCallIsEnumerable = node.Object == null && node.Arguments[0].Type.IsEnumerableOrArray(); + if (node.Object == null && node.Arguments.Count > 1 && node.Method.IsGenericMethod) + { + // Replace expression that are inside method calls that might need parameters updated (.Where() etc.) + var key = node.Arguments[0]; + // if we have already replaced this expression then use the cached version. + // e.g. Extension method where the same expression is passed to each method + cache.TryGetValue(key, out Expression? callBase); + if (callBase == null) { - // we may have changed a IEnumerable to an IEnumerable where TDynamic is a dynamic type - // built for the graph selection - - // We create a NewArrayInit as part of the bulk service loading - if (ifTrue.NodeType == ExpressionType.NewArrayInit) - { - var ifFalseType = ifFalse.Type.GetEnumerableOrArrayType()!; - ifTrue = Expression.NewArrayInit(ifFalseType); - type = typeof(IEnumerable<>).MakeGenericType(ifFalseType); - } - if (ifFalse.NodeType == ExpressionType.NewArrayInit) - { - var ifTrueType = ifTrue.Type.GetEnumerableOrArrayType()!; - ifFalse = Expression.NewArrayInit(ifTrueType); - type = typeof(IEnumerable<>).MakeGenericType(ifTrueType); - } + callBase = base.Visit(key); + cache[key] = callBase; } - var cond = Expression.Condition(test, ifTrue, ifFalse, type); - return cond; - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - if (toReplace != null && toReplace == node) - return newParam!; - bool baseCallIsEnumerable = node.Object == null && node.Arguments[0].Type.IsEnumerableOrArray(); - if (node.Object == null && node.Arguments.Count > 1 && node.Method.IsGenericMethod) + var callBaseType = callBase.Type.IsEnumerableOrArray() ? callBase.Type.GetEnumerableOrArrayType()! : callBase.Type; + var oldCallBaseType = baseCallIsEnumerable ? node.Arguments[0].Type.GetEnumerableOrArrayType()! : node.Arguments[0].Type; + if (callBaseType != oldCallBaseType) { - // Replace expression that are inside method calls that might need parameters updated (.Where() etc.) - var key = node.Arguments[0]; - // if we have already replaced this expression then use the cached version. - // e.g. Extension method where the same expression is passed to each method - cache.TryGetValue(key, out Expression? callBase); - if (callBase == null) - { - callBase = base.Visit(key); - cache[key] = callBase; - } - - var callBaseType = callBase.Type.IsEnumerableOrArray() ? callBase.Type.GetEnumerableOrArrayType()! : callBase.Type; - var oldCallBaseType = baseCallIsEnumerable ? node.Arguments[0].Type.GetEnumerableOrArrayType()! : node.Arguments[0].Type; - if (callBaseType != oldCallBaseType) + var replaceAgain = new ParameterReplacer(); + var newTypeArgs = new List(node.Arguments.Count) { callBaseType }; + var newArgs = new List(node.Arguments.Count) { callBase }; + var oldTypeArgs = node.Method.GetGenericArguments(); + foreach (var oldArg in node.Arguments.Skip(1)) { - var replaceAgain = new ParameterReplacer(); - var newTypeArgs = new List(node.Arguments.Count) { callBaseType }; - var newArgs = new List(node.Arguments.Count) { callBase }; - var oldTypeArgs = node.Method.GetGenericArguments(); - foreach (var oldArg in node.Arguments.Skip(1)) + if (oldArg is LambdaExpression oldLambda) { - if (oldArg is LambdaExpression oldLambda) - { - var newArg = (LambdaExpression)replaceAgain.Replace(oldArg, ((LambdaExpression)oldArg).Parameters[0], Expression.Parameter(callBaseType)); - newArgs.Add(newArg); - var argTypeNonList = newArg.ReturnType.IsEnumerableOrArray() ? newArg.ReturnType.GetEnumerableOrArrayType()! : null; - var oldArgTypeNonList = oldLambda.ReturnType.IsEnumerableOrArray() ? oldLambda.ReturnType.GetEnumerableOrArrayType()! : null; - if (oldTypeArgs.Contains(oldLambda.ReturnType)) - newTypeArgs.Add(newArg.ReturnType); - else if (argTypeNonList != null && oldTypeArgs.Contains(oldArgTypeNonList)) - newTypeArgs.Add(argTypeNonList); - } - else - { - newArgs.Add(oldArg); - if (oldTypeArgs.Contains(oldArg.Type)) - newTypeArgs.Add(oldArg.Type); - } + var newArg = (LambdaExpression)replaceAgain.Replace(oldArg, ((LambdaExpression)oldArg).Parameters[0], Expression.Parameter(callBaseType)); + newArgs.Add(newArg); + var argTypeNonList = newArg.ReturnType.IsEnumerableOrArray() ? newArg.ReturnType.GetEnumerableOrArrayType()! : null; + var oldArgTypeNonList = oldLambda.ReturnType.IsEnumerableOrArray() ? oldLambda.ReturnType.GetEnumerableOrArrayType()! : null; + if (oldTypeArgs.Contains(oldLambda.ReturnType)) + newTypeArgs.Add(newArg.ReturnType); + else if (argTypeNonList != null && oldTypeArgs.Contains(oldArgTypeNonList)) + newTypeArgs.Add(argTypeNonList); + } + else + { + newArgs.Add(oldArg); + if (oldTypeArgs.Contains(oldArg.Type)) + newTypeArgs.Add(oldArg.Type); } - if (oldTypeArgs.Length != newTypeArgs.Count) - throw new EntityGraphQLCompilerException($"Post service object selection contains a method call with mismatched generic type arguments."); - var newCall = Expression.Call(node.Method.DeclaringType!, node.Method.Name, newTypeArgs.ToArray(), newArgs.ToArray()); - return newCall; } + if (oldTypeArgs.Length != newTypeArgs.Count) + throw new EntityGraphQLCompilerException($"Post service object selection contains a method call with mismatched generic type arguments."); + var newCall = Expression.Call(node.Method.DeclaringType!, node.Method.Name, newTypeArgs.ToArray(), newArgs.ToArray()); + return newCall; } - else if (baseCallIsEnumerable && !node.Type.IsEnumerableOrArray() && node.Arguments.Count == 1 && hasNewFieldNameForType) - { - // field is going from collection to a single - if execution is split over non service fields and then with - // the next context doesn't have the collection to single. It only has the single - var newField = base.Visit(node.Arguments[0]); - if (newField != null) - return newField; - } - - var callOn = node.Object; - if (callOn != null) - { - if (callOn.Type == toReplaceType) - callOn = newParam!; - else - callOn = base.Visit(callOn); - } + } + else if (baseCallIsEnumerable && !node.Type.IsEnumerableOrArray() && node.Arguments.Count == 1 && hasNewFieldNameForType) + { + // field is going from collection to a single - if execution is split over non service fields and then with + // the next context doesn't have the collection to single. It only has the single + var newField = base.Visit(node.Arguments[0]); + if (newField != null) + return newField; + } - return Expression.Call(callOn, node.Method, node.Arguments.Select(base.Visit).ToArray()!); + var callOn = node.Object; + if (callOn != null) + { + if (callOn.Type == toReplaceType) + callOn = newParam!; + else + callOn = base.Visit(callOn); } + + return Expression.Call(callOn, node.Method, node.Arguments.Select(base.Visit).ToArray()!); } } diff --git a/src/EntityGraphQL/Directives/DirectiveProcessor.cs b/src/EntityGraphQL/Directives/DirectiveProcessor.cs index d095885e..33552acd 100644 --- a/src/EntityGraphQL/Directives/DirectiveProcessor.cs +++ b/src/EntityGraphQL/Directives/DirectiveProcessor.cs @@ -4,32 +4,31 @@ using EntityGraphQL.Compiler; using EntityGraphQL.Schema; -namespace EntityGraphQL.Directives +namespace EntityGraphQL.Directives; + +/// +/// Base directive processor. To implement custom directives inherit from this class and override either or both +/// ProcessQuery() - used to make changes to the query before execution (e.g. @include/skip) +/// ProcessResult() - used to make changes to the result of the item the directive is on +/// +public abstract class DirectiveProcessor : IDirectiveProcessor { - /// - /// Base directive processor. To implement custom directives inherit from this class and override either or both - /// ProcessQuery() - used to make changes to the query before execution (e.g. @include/skip) - /// ProcessResult() - used to make changes to the result of the item the directive is on - /// - public abstract class DirectiveProcessor : IDirectiveProcessor - { - public Type GetArgumentsType() => typeof(TArguments); + public Type GetArgumentsType() => typeof(TArguments); - public abstract string Name { get; } - public abstract string Description { get; } - public abstract List Location { get; } + public abstract string Name { get; } + public abstract string Description { get; } + public abstract List Location { get; } - private IDictionary? arguments; + private IDictionary? arguments; - public virtual IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) - { - return node; - } + public virtual IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) + { + return node; + } - public IDictionary GetArguments(ISchemaProvider schema) - { - arguments ??= typeof(TArguments).GetProperties().ToList().Select(prop => ArgType.FromProperty(schema, prop, null)).ToDictionary(i => i.Name, i => i); - return arguments; - } + public IDictionary GetArguments(ISchemaProvider schema) + { + arguments ??= typeof(TArguments).GetProperties().ToList().Select(prop => ArgType.FromProperty(schema, prop, null)).ToDictionary(i => i.Name, i => i); + return arguments; } } diff --git a/src/EntityGraphQL/Directives/IncludeDirectiveProcessor.cs b/src/EntityGraphQL/Directives/IncludeDirectiveProcessor.cs index 5292ec32..646792ce 100644 --- a/src/EntityGraphQL/Directives/IncludeDirectiveProcessor.cs +++ b/src/EntityGraphQL/Directives/IncludeDirectiveProcessor.cs @@ -1,35 +1,26 @@ -using System; using System.Collections.Generic; using EntityGraphQL.Compiler; using EntityGraphQL.Schema; -namespace EntityGraphQL.Directives -{ - public class IncludeDirectiveProcessor : DirectiveProcessor - { - public override string Name - { - get => "include"; - } - public override string Description - { - get => "Directs the executor to include this field or fragment only when the `if` argument is true."; - } +namespace EntityGraphQL.Directives; - public override List Location => - new() { ExecutableDirectiveLocation.FIELD, ExecutableDirectiveLocation.FRAGMENT_SPREAD, ExecutableDirectiveLocation.INLINE_FRAGMENT }; +public class IncludeDirectiveProcessor : DirectiveProcessor +{ + public override string Name => "include"; + public override string Description => "Directs the executor to include this field or fragment only when the `if` argument is true."; - public override IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) - { - if (arguments is null) - throw new EntityGraphQLException("Argument 'if' is required for @include directive"); - return ((IncludeArguments)arguments).If ? node : null; - } - } + public override List Location => [ExecutableDirectiveLocation.FIELD, ExecutableDirectiveLocation.FRAGMENT_SPREAD, ExecutableDirectiveLocation.INLINE_FRAGMENT]; - public class IncludeArguments + public override IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) { - [GraphQLField("if", "Included when true.")] - public bool If { get; set; } + if (arguments is null) + throw new EntityGraphQLException("Argument 'if' is required for @include directive"); + return ((IncludeArguments)arguments).If ? node : null; } } + +public class IncludeArguments +{ + [GraphQLField("if", "Included when true.")] + public bool If { get; set; } +} diff --git a/src/EntityGraphQL/Directives/SkipDirectiveProcessor.cs b/src/EntityGraphQL/Directives/SkipDirectiveProcessor.cs index a3619018..5e566cd4 100644 --- a/src/EntityGraphQL/Directives/SkipDirectiveProcessor.cs +++ b/src/EntityGraphQL/Directives/SkipDirectiveProcessor.cs @@ -1,34 +1,25 @@ -using System; using System.Collections.Generic; using EntityGraphQL.Compiler; using EntityGraphQL.Schema; -namespace EntityGraphQL.Directives -{ - public class SkipDirectiveProcessor : DirectiveProcessor - { - public override string Name - { - get => "skip"; - } - public override string Description - { - get => "Directs the executor to skip this field or fragment when the `if` argument is true."; - } - public override List Location => - new() { ExecutableDirectiveLocation.FIELD, ExecutableDirectiveLocation.FRAGMENT_SPREAD, ExecutableDirectiveLocation.INLINE_FRAGMENT }; +namespace EntityGraphQL.Directives; - public override IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) - { - if (arguments is null) - throw new EntityGraphQLException("Argument 'if' is required for @skip directive"); - return !((SkipArguments)arguments).If ? node : null; - } - } +public class SkipDirectiveProcessor : DirectiveProcessor +{ + public override string Name => "skip"; + public override string Description => "Directs the executor to skip this field or fragment when the `if` argument is true."; + public override List Location => [ExecutableDirectiveLocation.FIELD, ExecutableDirectiveLocation.FRAGMENT_SPREAD, ExecutableDirectiveLocation.INLINE_FRAGMENT]; - public class SkipArguments + public override IGraphQLNode? VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) { - [GraphQLField("if", "Excluded when true.")] - public bool If { get; set; } + if (arguments is null) + throw new EntityGraphQLException("Argument 'if' is required for @skip directive"); + return !((SkipArguments)arguments).If ? node : null; } } + +public class SkipArguments +{ + [GraphQLField("if", "Excluded when true.")] + public bool If { get; set; } +} diff --git a/src/EntityGraphQL/EntityGraphQL.csproj b/src/EntityGraphQL/EntityGraphQL.csproj index ae80c1a8..5755b2f4 100644 --- a/src/EntityGraphQL/EntityGraphQL.csproj +++ b/src/EntityGraphQL/EntityGraphQL.csproj @@ -1,10 +1,10 @@ - netstandard2.1;net6.0;net8.0 - 12.0 + netstandard2.1;net6.0;net8.0;net9.0 + 13.0 EntityGraphQL EntityGraphQL - 5.5.3 + 5.6.0 A GraphQL library for .NET Core. Compiles queries into .NET Expressions (LinqProvider) for runtime execution against object graphs. E.g. against an ORM data model (EntityFramework or others) or just an in-memory object. Luke Murray https://github.com/lukemurray/EntityGraphQL @@ -17,17 +17,19 @@ enable Recommended true + + true - + - - + + - + diff --git a/src/EntityGraphQL/EntityGraphQLArgumentException.cs b/src/EntityGraphQL/EntityGraphQLArgumentException.cs index c8668056..314ca5a6 100644 --- a/src/EntityGraphQL/EntityGraphQLArgumentException.cs +++ b/src/EntityGraphQL/EntityGraphQLArgumentException.cs @@ -1,13 +1,12 @@ using System; -namespace EntityGraphQL +namespace EntityGraphQL; + +public class EntityGraphQLArgumentException : Exception { - public class EntityGraphQLArgumentException : Exception - { - public EntityGraphQLArgumentException(string message) - : base(message) { } + public EntityGraphQLArgumentException(string message) + : base(message) { } - public EntityGraphQLArgumentException(string parameterName, string message) - : base($"{message} (Parameter '{parameterName}')") { } - } + public EntityGraphQLArgumentException(string parameterName, string message) + : base($"{message} (Parameter '{parameterName}')") { } } diff --git a/src/EntityGraphQL/EntityGraphQLFieldException.cs b/src/EntityGraphQL/EntityGraphQLFieldException.cs index 8eca193a..17ae789d 100644 --- a/src/EntityGraphQL/EntityGraphQLFieldException.cs +++ b/src/EntityGraphQL/EntityGraphQLFieldException.cs @@ -1,15 +1,14 @@ using System; -namespace EntityGraphQL +namespace EntityGraphQL; + +internal sealed class EntityGraphQLFieldException : Exception { - internal sealed class EntityGraphQLFieldException : Exception - { - public readonly string FieldName; + public readonly string FieldName; - public EntityGraphQLFieldException(string fieldName, Exception innerException) - : base(null, innerException) - { - FieldName = fieldName; - } + public EntityGraphQLFieldException(string fieldName, Exception innerException) + : base(null, innerException) + { + FieldName = fieldName; } } diff --git a/src/EntityGraphQL/Extensions/DictionaryExtensions.cs b/src/EntityGraphQL/Extensions/DictionaryExtensions.cs index 793a9d0d..59371f8d 100644 --- a/src/EntityGraphQL/Extensions/DictionaryExtensions.cs +++ b/src/EntityGraphQL/Extensions/DictionaryExtensions.cs @@ -8,7 +8,7 @@ public static class DictionaryExtensions public static Dictionary MergeNew(this IDictionary source, IReadOnlyDictionary? other) where TKey : notnull { - var result = source != null ? source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) : new Dictionary(); + var result = source != null ? source.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) : []; if (other != null) foreach (var kvp in other) result[kvp.Key] = kvp.Value; diff --git a/src/EntityGraphQL/Extensions/EnumerableExtensions.cs b/src/EntityGraphQL/Extensions/EnumerableExtensions.cs index 90bc0df7..03e8a819 100644 --- a/src/EntityGraphQL/Extensions/EnumerableExtensions.cs +++ b/src/EntityGraphQL/Extensions/EnumerableExtensions.cs @@ -4,71 +4,70 @@ using System.Linq.Expressions; using EntityGraphQL.Schema; -namespace EntityGraphQL.Extensions +namespace EntityGraphQL.Extensions; + +/// +/// Extension methods to allow to allow you to build queries and reuse expressions/filters +/// +public static class EnumerableExtensions { - /// - /// Extension methods to allow to allow you to build queries and reuse expressions/filters - /// - public static class EnumerableExtensions + public static IEnumerable Take(this IEnumerable source, int? count) { - public static IEnumerable Take(this IEnumerable source, int? count) - { - if (!count.HasValue) - return source; + if (!count.HasValue) + return source; - return Enumerable.Take(source, count.Value); - } + return Enumerable.Take(source, count.Value); + } - public static IEnumerable Skip(this IEnumerable source, int? count) - { - if (!count.HasValue) - return source; + public static IEnumerable Skip(this IEnumerable source, int? count) + { + if (!count.HasValue) + return source; - return Enumerable.Skip(source, count.Value); - } + return Enumerable.Skip(source, count.Value); + } - public static IEnumerable WhereWhen(this IEnumerable source, Expression> wherePredicate, bool applyPredicate) - { - if (applyPredicate) - return Queryable.Where(source.AsQueryable(), wherePredicate).AsEnumerable(); + public static IEnumerable WhereWhen(this IEnumerable source, Expression> wherePredicate, bool applyPredicate) + { + if (applyPredicate) + return Queryable.Where(source.AsQueryable(), wherePredicate).AsEnumerable(); - return source; - } + return source; + } - public static IEnumerable WhereWhen(this IEnumerable source, EntityQueryType filter, bool applyPredicate) - { - if (applyPredicate) - return Queryable.Where(source.AsQueryable(), filter.Query!); - return source; - } + public static IEnumerable WhereWhen(this IEnumerable source, EntityQueryType filter, bool applyPredicate) + { + if (applyPredicate) + return Queryable.Where(source.AsQueryable(), filter.Query!); + return source; + } - /// - /// Does a null check on source. Returns null if source is null, otherwise returns source.ToList() - /// If returnEmptyList is true, returns an empty list if source is null - /// - /// - /// - /// - /// - public static List? ToListWithNullCheck(this IEnumerable source, bool returnEmptyList) - { - if (source == null) - return returnEmptyList ? [] : null; - return source.ToList(); - } + /// + /// Does a null check on source. Returns null if source is null, otherwise returns source.ToList() + /// If returnEmptyList is true, returns an empty list if source is null + /// + /// + /// + /// + /// + public static List? ToListWithNullCheck(this IEnumerable source, bool returnEmptyList) + { + if (source == null) + return returnEmptyList ? [] : null; + return source.ToList(); + } - public static IEnumerable? SelectWithNullCheck(this IEnumerable source, Func selector) - { - if (source == null) - return null; - return source.Select(selector); - } + public static IEnumerable? SelectWithNullCheck(this IEnumerable source, Func selector) + { + if (source == null) + return null; + return source.Select(selector); + } - public static IEnumerable? SelectManyWithNullCheck(this IEnumerable source, Func> selector) - { - if (source == null) - return []; - return source.SelectMany(selector); - } + public static IEnumerable? SelectManyWithNullCheck(this IEnumerable source, Func> selector) + { + if (source == null) + return []; + return source.SelectMany(selector); } } diff --git a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfo.cs b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfo.cs index eaf223cf..53077258 100644 --- a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfo.cs +++ b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfo.cs @@ -3,68 +3,67 @@ #if NETSTANDARD2_1 using System; -namespace Nullability +namespace Nullability; + +/// +/// A class that represents nullability info +/// +public sealed class NullabilityInfo { - /// - /// A class that represents nullability info - /// - public sealed class NullabilityInfo + internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState, NullabilityInfo? elementType, NullabilityInfo[] typeArguments) { - internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState, NullabilityInfo? elementType, NullabilityInfo[] typeArguments) - { - Type = type; - ReadState = readState; - WriteState = writeState; - ElementType = elementType; - GenericTypeArguments = typeArguments; - } + Type = type; + ReadState = readState; + WriteState = writeState; + ElementType = elementType; + GenericTypeArguments = typeArguments; + } - /// - /// The of the member or generic parameter - /// to which this NullabilityInfo belongs - /// - public Type Type { get; } + /// + /// The of the member or generic parameter + /// to which this NullabilityInfo belongs + /// + public Type Type { get; } - /// - /// The nullability read state of the member - /// - public NullabilityState ReadState { get; internal set; } + /// + /// The nullability read state of the member + /// + public NullabilityState ReadState { get; internal set; } - /// - /// The nullability write state of the member - /// - public NullabilityState WriteState { get; internal set; } + /// + /// The nullability write state of the member + /// + public NullabilityState WriteState { get; internal set; } - /// - /// If the member type is an array, gives the of the elements of the array, null otherwise - /// - public NullabilityInfo? ElementType { get; } + /// + /// If the member type is an array, gives the of the elements of the array, null otherwise + /// + public NullabilityInfo? ElementType { get; } - /// - /// If the member type is a generic type, gives the array of for each type parameter - /// - public NullabilityInfo[] GenericTypeArguments { get; } - } + /// + /// If the member type is a generic type, gives the array of for each type parameter + /// + public NullabilityInfo[] GenericTypeArguments { get; } +} +/// +/// An enum that represents nullability state +/// +public enum NullabilityState +{ /// - /// An enum that represents nullability state + /// Nullability context not enabled (oblivious) /// - public enum NullabilityState - { - /// - /// Nullability context not enabled (oblivious) - /// - Unknown, + Unknown, - /// - /// Non nullable value or reference type - /// - NotNull, + /// + /// Non nullable value or reference type + /// + NotNull, - /// - /// Nullable value or reference type - /// - Nullable - } + /// + /// Nullable value or reference type + /// + Nullable, } #endif diff --git a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoContext.cs b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoContext.cs index c6b54fa9..69f8e066 100644 --- a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoContext.cs +++ b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoContext.cs @@ -8,631 +8,629 @@ using System.Reflection; using System.Linq; -namespace Nullability +namespace Nullability; + +/// +/// Provides APIs for populating nullability information/context from reflection members: +/// , , and . +/// +public sealed class NullabilityInfoContext { + private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices"; + private readonly Dictionary _publicOnlyModules = []; + private readonly Dictionary _context = []; + + internal static bool IsSupported { get; } = AppContext.TryGetSwitch("System.Reflection.NullabilityInfoContext.IsSupported", out bool isSupported) ? isSupported : true; + + [Flags] + private enum NotAnnotatedStatus + { + None = 0x0, // no restriction, all members annotated + Private = 0x1, // private members not annotated + Internal = + 0x2 // internal members not annotated + , + } + + private NullabilityState? GetNullableContext(MemberInfo? memberInfo) + { + while (memberInfo != null) + { + if (_context.TryGetValue(memberInfo, out NullabilityState state)) + { + return state; + } + + foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData()) + { + if (attribute.AttributeType.Name == "NullableContextAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) + { + state = TranslateByte(attribute.ConstructorArguments[0].Value); + _context.Add(memberInfo, state); + return state; + } + } + + memberInfo = memberInfo.DeclaringType; + } + + return null; + } + /// - /// Provides APIs for populating nullability information/context from reflection members: - /// , , and . + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. /// - public sealed class NullabilityInfoContext + /// The parameter which nullability info gets populated + /// If the parameterInfo parameter is null + /// + public NullabilityInfo Create(ParameterInfo parameterInfo) { - private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices"; - private readonly Dictionary _publicOnlyModules = new(); - private readonly Dictionary _context = new(); + EnsureIsSupported(); - internal static bool IsSupported { get; } = AppContext.TryGetSwitch("System.Reflection.NullabilityInfoContext.IsSupported", out bool isSupported) ? isSupported : true; + IList attributes = parameterInfo.GetCustomAttributesData(); + NullableAttributeStateParser parser = + parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method) ? NullableAttributeStateParser.Unknown : CreateParser(attributes); + NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser); - [Flags] - private enum NotAnnotatedStatus + if (nullability.ReadState != NullabilityState.Unknown) { - None = 0x0, // no restriction, all members annotated - Private = 0x1, // private members not annotated - Internal = 0x2 // internal members not annotated + CheckParameterMetadataType(parameterInfo, nullability); } - private NullabilityState? GetNullableContext(MemberInfo? memberInfo) + CheckNullabilityAttributes(nullability, attributes); + return nullability; + } + + private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability) + { + if (parameter.Member is MethodInfo method) { - while (memberInfo != null) + MethodInfo metaMethod = GetMethodMetadataDefinition(method); + ParameterInfo? metaParameter = null; + if (string.IsNullOrEmpty(parameter.Name)) { - if (_context.TryGetValue(memberInfo, out NullabilityState state)) - { - return state; - } - - foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData()) + metaParameter = metaMethod.ReturnParameter; + } + else + { + ParameterInfo[] parameters = metaMethod.GetParameters(); + for (int i = 0; i < parameters.Length; i++) { - if (attribute.AttributeType.Name == "NullableContextAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) + if (parameter.Position == i && parameter.Name == parameters[i].Name) { - state = TranslateByte(attribute.ConstructorArguments[0].Value); - _context.Add(memberInfo, state); - return state; + metaParameter = parameters[i]; + break; } } - - memberInfo = memberInfo.DeclaringType; } - return null; + if (metaParameter != null) + { + CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType, parameter.Member.ReflectedType); + } } + } - /// - /// Populates for the given . - /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's - /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. - /// - /// The parameter which nullability info gets populated - /// If the parameterInfo parameter is null - /// - public NullabilityInfo Create(ParameterInfo parameterInfo) + private static MethodInfo GetMethodMetadataDefinition(MethodInfo method) + { + if (method.IsGenericMethod && !method.IsGenericMethodDefinition) { - EnsureIsSupported(); - - IList attributes = parameterInfo.GetCustomAttributesData(); - NullableAttributeStateParser parser = - parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method) ? NullableAttributeStateParser.Unknown : CreateParser(attributes); - NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser); + method = method.GetGenericMethodDefinition(); + } - if (nullability.ReadState != NullabilityState.Unknown) - { - CheckParameterMetadataType(parameterInfo, nullability); - } + return (MethodInfo)GetMemberMetadataDefinition(method); + } - CheckNullabilityAttributes(nullability, attributes); - return nullability; - } + private static void CheckNullabilityAttributes(NullabilityInfo nullability, IList attributes) + { + var codeAnalysisReadState = NullabilityState.Unknown; + var codeAnalysisWriteState = NullabilityState.Unknown; - private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability) + foreach (CustomAttributeData attribute in attributes) { - if (parameter.Member is MethodInfo method) + if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis") { - MethodInfo metaMethod = GetMethodMetadataDefinition(method); - ParameterInfo? metaParameter = null; - if (string.IsNullOrEmpty(parameter.Name)) + if (attribute.AttributeType.Name == "NotNullAttribute") { - metaParameter = metaMethod.ReturnParameter; + codeAnalysisReadState = NullabilityState.NotNull; } - else + else if ( + (attribute.AttributeType.Name == "MaybeNullAttribute" || attribute.AttributeType.Name == "MaybeNullWhenAttribute") + && codeAnalysisReadState == NullabilityState.Unknown + && !IsValueTypeOrValueTypeByRef(nullability.Type) + ) { - ParameterInfo[] parameters = metaMethod.GetParameters(); - for (int i = 0; i < parameters.Length; i++) - { - if (parameter.Position == i && parameter.Name == parameters[i].Name) - { - metaParameter = parameters[i]; - break; - } - } + codeAnalysisReadState = NullabilityState.Nullable; } - - if (metaParameter != null) + else if (attribute.AttributeType.Name == "DisallowNullAttribute") + { + codeAnalysisWriteState = NullabilityState.NotNull; + } + else if (attribute.AttributeType.Name == "AllowNullAttribute" && codeAnalysisWriteState == NullabilityState.Unknown && !IsValueTypeOrValueTypeByRef(nullability.Type)) { - CheckGenericParameters(nullability, metaMethod, metaParameter.ParameterType, parameter.Member.ReflectedType); + codeAnalysisWriteState = NullabilityState.Nullable; } } } - private static MethodInfo GetMethodMetadataDefinition(MethodInfo method) + if (codeAnalysisReadState != NullabilityState.Unknown) { - if (method.IsGenericMethod && !method.IsGenericMethodDefinition) - { - method = method.GetGenericMethodDefinition(); - } - - return (MethodInfo)GetMemberMetadataDefinition(method); + nullability.ReadState = codeAnalysisReadState; } - - private static void CheckNullabilityAttributes(NullabilityInfo nullability, IList attributes) + if (codeAnalysisWriteState != NullabilityState.Unknown) { - var codeAnalysisReadState = NullabilityState.Unknown; - var codeAnalysisWriteState = NullabilityState.Unknown; + nullability.WriteState = codeAnalysisWriteState; + } + } - foreach (CustomAttributeData attribute in attributes) - { - if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis") - { - if (attribute.AttributeType.Name == "NotNullAttribute") - { - codeAnalysisReadState = NullabilityState.NotNull; - } - else if ( - (attribute.AttributeType.Name == "MaybeNullAttribute" || attribute.AttributeType.Name == "MaybeNullWhenAttribute") - && codeAnalysisReadState == NullabilityState.Unknown - && !IsValueTypeOrValueTypeByRef(nullability.Type) - ) - { - codeAnalysisReadState = NullabilityState.Nullable; - } - else if (attribute.AttributeType.Name == "DisallowNullAttribute") - { - codeAnalysisWriteState = NullabilityState.NotNull; - } - else if (attribute.AttributeType.Name == "AllowNullAttribute" && codeAnalysisWriteState == NullabilityState.Unknown && !IsValueTypeOrValueTypeByRef(nullability.Type)) - { - codeAnalysisWriteState = NullabilityState.Nullable; - } - } - } + /// + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the propertyInfo parameter is null + /// + public NullabilityInfo Create(PropertyInfo propertyInfo) + { + EnsureIsSupported(); - if (codeAnalysisReadState != NullabilityState.Unknown) - { - nullability.ReadState = codeAnalysisReadState; - } - if (codeAnalysisWriteState != NullabilityState.Unknown) - { - nullability.WriteState = codeAnalysisWriteState; - } + MethodInfo? getter = propertyInfo.GetGetMethod(true); + MethodInfo? setter = propertyInfo.GetSetMethod(true); + bool annotationsDisabled = (getter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(getter)) && (setter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(setter)); + NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(propertyInfo.GetCustomAttributesData()); + NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, parser); + + if (getter != null) + { + CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData()); + } + else + { + nullability.ReadState = NullabilityState.Unknown; } - /// - /// Populates for the given . - /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's - /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. - /// - /// The parameter which nullability info gets populated - /// If the propertyInfo parameter is null - /// - public NullabilityInfo Create(PropertyInfo propertyInfo) + if (setter != null) { - EnsureIsSupported(); + CheckNullabilityAttributes(nullability, setter.GetParameters().Last().GetCustomAttributesData()); + } + else + { + nullability.WriteState = NullabilityState.Unknown; + } - MethodInfo? getter = propertyInfo.GetGetMethod(true); - MethodInfo? setter = propertyInfo.GetSetMethod(true); - bool annotationsDisabled = (getter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(getter)) && (setter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(setter)); - NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(propertyInfo.GetCustomAttributesData()); - NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, parser); + return nullability; + } - if (getter != null) - { - CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData()); - } - else - { - nullability.ReadState = NullabilityState.Unknown; - } + private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodBase method) + { + if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) && IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module)) + { + return true; + } - if (setter != null) - { - CheckNullabilityAttributes(nullability, setter.GetParameters().Last().GetCustomAttributesData()); - } - else - { - nullability.WriteState = NullabilityState.Unknown; - } + return false; + } - return nullability; - } + /// + /// Populates for the given . + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the eventInfo parameter is null + /// + public NullabilityInfo Create(EventInfo eventInfo) + { + EnsureIsSupported(); - private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodBase method) - { - if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) && IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module)) - { - return true; - } + return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(eventInfo.GetCustomAttributesData())); + } - return false; - } + /// + /// Populates for the given + /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's + /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. + /// + /// The parameter which nullability info gets populated + /// If the fieldInfo parameter is null + /// + public NullabilityInfo Create(FieldInfo fieldInfo) + { + EnsureIsSupported(); - /// - /// Populates for the given . - /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's - /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. - /// - /// The parameter which nullability info gets populated - /// If the eventInfo parameter is null - /// - public NullabilityInfo Create(EventInfo eventInfo) - { - EnsureIsSupported(); + IList attributes = fieldInfo.GetCustomAttributesData(); + NullableAttributeStateParser parser = IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo) ? NullableAttributeStateParser.Unknown : CreateParser(attributes); + NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, parser); + CheckNullabilityAttributes(nullability, attributes); + return nullability; + } - return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(eventInfo.GetCustomAttributesData())); + private static void EnsureIsSupported() + { + if (!IsSupported) + { + throw new InvalidOperationException("NullabilityInfoContext is not supported"); } + } - /// - /// Populates for the given - /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's - /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state. - /// - /// The parameter which nullability info gets populated - /// If the fieldInfo parameter is null - /// - public NullabilityInfo Create(FieldInfo fieldInfo) + private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo) + { + if ((fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) && IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module)) { - EnsureIsSupported(); - - IList attributes = fieldInfo.GetCustomAttributesData(); - NullableAttributeStateParser parser = IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo) ? NullableAttributeStateParser.Unknown : CreateParser(attributes); - NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, parser); - CheckNullabilityAttributes(nullability, attributes); - return nullability; + return true; } - private static void EnsureIsSupported() + return false; + } + + private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module) + { + if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value)) { - if (!IsSupported) - { - throw new InvalidOperationException("NullabilityInfoContext is not supported"); - } + value = PopulateAnnotationInfo(module.GetCustomAttributesData()); + _publicOnlyModules.Add(module, value); } - private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo) + if (value == NotAnnotatedStatus.None) { - if ( - (fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) - && IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module) - ) - { - return true; - } - return false; } - private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module) + if ((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private) || isAssembly && value.HasFlag(NotAnnotatedStatus.Internal)) { - if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value)) - { - value = PopulateAnnotationInfo(module.GetCustomAttributesData()); - _publicOnlyModules.Add(module, value); - } - - if (value == NotAnnotatedStatus.None) - { - return false; - } - - if ((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private) || isAssembly && value.HasFlag(NotAnnotatedStatus.Internal)) - { - return true; - } - - return false; + return true; } - private static NotAnnotatedStatus PopulateAnnotationInfo(IList customAttributes) + return false; + } + + private static NotAnnotatedStatus PopulateAnnotationInfo(IList customAttributes) + { + foreach (CustomAttributeData attribute in customAttributes) { - foreach (CustomAttributeData attribute in customAttributes) + if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) { - if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) + if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue) { - if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue) - { - return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private; - } - else - { - return NotAnnotatedStatus.Private; - } + return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private; + } + else + { + return NotAnnotatedStatus.Private; } } - - return NotAnnotatedStatus.None; } - private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser) - { - int index = 0; - NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index); + return NotAnnotatedStatus.None; + } - if (nullability.ReadState != NullabilityState.Unknown) - { - TryLoadGenericMetaTypeNullability(memberInfo, nullability); - } + private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser) + { + int index = 0; + NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index); - return nullability; + if (nullability.ReadState != NullabilityState.Unknown) + { + TryLoadGenericMetaTypeNullability(memberInfo, nullability); } - private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser, ref int index) - { - NullabilityState state = NullabilityState.Unknown; - NullabilityInfo? elementState = null; - NullabilityInfo[] genericArgumentsState = Array.Empty(); - Type underlyingType = type; + return nullability; + } - if (underlyingType.IsByRef || underlyingType.IsPointer) - { - underlyingType = underlyingType.GetElementType()!; - } + private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser, ref int index) + { + NullabilityState state = NullabilityState.Unknown; + NullabilityInfo? elementState = null; + NullabilityInfo[] genericArgumentsState = Array.Empty(); + Type underlyingType = type; - if (underlyingType.IsValueType) - { - if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType) - { - underlyingType = nullableUnderlyingType; - state = NullabilityState.Nullable; - } - else - { - state = NullabilityState.NotNull; - } + if (underlyingType.IsByRef || underlyingType.IsPointer) + { + underlyingType = underlyingType.GetElementType()!; + } - if (underlyingType.IsGenericType) - { - ++index; - } + if (underlyingType.IsValueType) + { + if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType) + { + underlyingType = nullableUnderlyingType; + state = NullabilityState.Nullable; } else { - if (!parser.ParseNullableState(index++, ref state) && GetNullableContext(memberInfo) is { } contextState) - { - state = contextState; - } - - if (underlyingType.IsArray) - { - elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index); - } + state = NullabilityState.NotNull; } if (underlyingType.IsGenericType) { - Type[] genericArguments = underlyingType.GetGenericArguments(); - genericArgumentsState = new NullabilityInfo[genericArguments.Length]; - - for (int i = 0; i < genericArguments.Length; i++) - { - genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], parser, ref index); - } + ++index; } - - return new NullabilityInfo(type, state, state, elementState, genericArgumentsState); } - - private static NullableAttributeStateParser CreateParser(IList customAttributes) + else { - foreach (CustomAttributeData attribute in customAttributes) + if (!parser.ParseNullableState(index++, ref state) && GetNullableContext(memberInfo) is { } contextState) { - if (attribute.AttributeType.Name == "NullableAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) - { - return new NullableAttributeStateParser(attribute.ConstructorArguments[0].Value); - } + state = contextState; } - return new NullableAttributeStateParser(null); + if (underlyingType.IsArray) + { + elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index); + } } - private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability) + if (underlyingType.IsGenericType) { - MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo); - Type? metaType = null; - if (metaMember is FieldInfo field) - { - metaType = field.FieldType; - } - else if (metaMember is PropertyInfo property) - { - metaType = GetPropertyMetaType(property); - } + Type[] genericArguments = underlyingType.GetGenericArguments(); + genericArgumentsState = new NullabilityInfo[genericArguments.Length]; - if (metaType != null) + for (int i = 0; i < genericArguments.Length; i++) { - CheckGenericParameters(nullability, metaMember!, metaType, memberInfo.ReflectedType); + genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], parser, ref index); } } - private static MemberInfo GetMemberMetadataDefinition(MemberInfo member) + return new NullabilityInfo(type, state, state, elementState, genericArgumentsState); + } + + private static NullableAttributeStateParser CreateParser(IList customAttributes) + { + foreach (CustomAttributeData attribute in customAttributes) { - Type? type = member.DeclaringType; - if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition) + if (attribute.AttributeType.Name == "NullableAttribute" && attribute.AttributeType.Namespace == CompilerServicesNameSpace && attribute.ConstructorArguments.Count == 1) { - return NullabilityInfoExtensions.GetMemberWithSameMetadataDefinitionAs(type.GetGenericTypeDefinition(), member); + return new NullableAttributeStateParser(attribute.ConstructorArguments[0].Value); } + } + + return new NullableAttributeStateParser(null); + } - return member; + private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability) + { + MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo); + Type? metaType = null; + if (metaMember is FieldInfo field) + { + metaType = field.FieldType; + } + else if (metaMember is PropertyInfo property) + { + metaType = GetPropertyMetaType(property); } - private static Type GetPropertyMetaType(PropertyInfo property) + if (metaType != null) { - if (property.GetGetMethod(true) is MethodInfo method) - { - return method.ReturnType; - } + CheckGenericParameters(nullability, metaMember!, metaType, memberInfo.ReflectedType); + } + } + + private static MemberInfo GetMemberMetadataDefinition(MemberInfo member) + { + Type? type = member.DeclaringType; + if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition) + { + return NullabilityInfoExtensions.GetMemberWithSameMetadataDefinitionAs(type.GetGenericTypeDefinition(), member); + } - return property.GetSetMethod(true)!.GetParameters()[0].ParameterType; + return member; + } + + private static Type GetPropertyMetaType(PropertyInfo property) + { + if (property.GetGetMethod(true) is MethodInfo method) + { + return method.ReturnType; } - private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType, Type? reflectedType) + return property.GetSetMethod(true)!.GetParameters()[0].ParameterType; + } + + private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType, Type? reflectedType) + { + if (metaType.IsGenericParameter) { - if (metaType.IsGenericParameter) + if (nullability.ReadState == NullabilityState.NotNull) { - if (nullability.ReadState == NullabilityState.NotNull) - { - TryUpdateGenericParameterNullability(nullability, metaType, reflectedType); - } + TryUpdateGenericParameterNullability(nullability, metaType, reflectedType); } - else if (metaType.ContainsGenericParameters) + } + else if (metaType.ContainsGenericParameters) + { + if (nullability.GenericTypeArguments.Length > 0) { - if (nullability.GenericTypeArguments.Length > 0) - { - Type[] genericArguments = metaType.GetGenericArguments(); + Type[] genericArguments = metaType.GetGenericArguments(); - for (int i = 0; i < genericArguments.Length; i++) - { - CheckGenericParameters(nullability.GenericTypeArguments[i], metaMember, genericArguments[i], reflectedType); - } - } - else if (nullability.ElementType is { } elementNullability && metaType.IsArray) - { - CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType); - } - // We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this - // will be a no-op regardless - else if (metaType.IsByRef) + for (int i = 0; i < genericArguments.Length; i++) { - CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType); + CheckGenericParameters(nullability.GenericTypeArguments[i], metaMember, genericArguments[i], reflectedType); } } - } - - private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, Type genericParameter, Type? reflectedType) - { - Debug.Assert(genericParameter.IsGenericParameter); - - if ( - reflectedType is not null - && !genericParameter.IsGenericMethodParameter() - && TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, reflectedType, reflectedType) - ) + else if (nullability.ElementType is { } elementNullability && metaType.IsArray) { - return true; + CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType); } - - if (IsValueTypeOrValueTypeByRef(nullability.Type)) + // We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this + // will be a no-op regardless + else if (metaType.IsByRef) { - return true; + CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType); } + } + } - var state = NullabilityState.Unknown; - if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state)) - { - nullability.ReadState = state; - nullability.WriteState = state; - return true; - } + private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, Type genericParameter, Type? reflectedType) + { + Debug.Assert(genericParameter.IsGenericParameter); - if (GetNullableContext(genericParameter) is { } contextState) - { - nullability.ReadState = contextState; - nullability.WriteState = contextState; - return true; - } + if ( + reflectedType is not null + && !genericParameter.IsGenericMethodParameter() + && TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, reflectedType, reflectedType) + ) + { + return true; + } - return false; + if (IsValueTypeOrValueTypeByRef(nullability.Type)) + { + return true; } - private bool TryUpdateGenericTypeParameterNullabilityFromReflectedType(NullabilityInfo nullability, Type genericParameter, Type context, Type reflectedType) + var state = NullabilityState.Unknown; + if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state)) { - Debug.Assert(genericParameter.IsGenericParameter && !genericParameter.IsGenericMethodParameter()); + nullability.ReadState = state; + nullability.WriteState = state; + return true; + } - Type contextTypeDefinition = context.IsGenericType && !context.IsGenericTypeDefinition ? context.GetGenericTypeDefinition() : context; - if (genericParameter.DeclaringType == contextTypeDefinition) - { - return false; - } + if (GetNullableContext(genericParameter) is { } contextState) + { + nullability.ReadState = contextState; + nullability.WriteState = contextState; + return true; + } - Type? baseType = contextTypeDefinition.BaseType; - if (baseType is null) - { - return false; - } + return false; + } - if (!baseType.IsGenericType || (baseType.IsGenericTypeDefinition ? baseType : baseType.GetGenericTypeDefinition()) != genericParameter.DeclaringType) - { - return TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, baseType, reflectedType); - } + private bool TryUpdateGenericTypeParameterNullabilityFromReflectedType(NullabilityInfo nullability, Type genericParameter, Type context, Type reflectedType) + { + Debug.Assert(genericParameter.IsGenericParameter && !genericParameter.IsGenericMethodParameter()); - Type[] genericArguments = baseType.GetGenericArguments(); - Type genericArgument = genericArguments[genericParameter.GenericParameterPosition]; - if (genericArgument.IsGenericParameter) - { - return TryUpdateGenericParameterNullability(nullability, genericArgument, reflectedType); - } + Type contextTypeDefinition = context.IsGenericType && !context.IsGenericTypeDefinition ? context.GetGenericTypeDefinition() : context; + if (genericParameter.DeclaringType == contextTypeDefinition) + { + return false; + } - NullableAttributeStateParser parser = CreateParser(contextTypeDefinition.GetCustomAttributesData()); - int nullabilityStateIndex = 1; // start at 1 since index 0 is the type itself - for (int i = 0; i < genericParameter.GenericParameterPosition; i++) - { - nullabilityStateIndex += CountNullabilityStates(genericArguments[i]); - } - return TryPopulateNullabilityInfo(nullability, parser, ref nullabilityStateIndex); + Type? baseType = contextTypeDefinition.BaseType; + if (baseType is null) + { + return false; + } - static int CountNullabilityStates(Type type) - { - Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; - if (underlyingType.IsGenericType) - { - int count = 1; - foreach (Type genericArgument in underlyingType.GetGenericArguments()) - { - count += CountNullabilityStates(genericArgument); - } - return count; - } + if (!baseType.IsGenericType || (baseType.IsGenericTypeDefinition ? baseType : baseType.GetGenericTypeDefinition()) != genericParameter.DeclaringType) + { + return TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, baseType, reflectedType); + } - if (underlyingType.HasElementType) - { - return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!); - } + Type[] genericArguments = baseType.GetGenericArguments(); + Type genericArgument = genericArguments[genericParameter.GenericParameterPosition]; + if (genericArgument.IsGenericParameter) + { + return TryUpdateGenericParameterNullability(nullability, genericArgument, reflectedType); + } - return type.IsValueType ? 0 : 1; - } + NullableAttributeStateParser parser = CreateParser(contextTypeDefinition.GetCustomAttributesData()); + int nullabilityStateIndex = 1; // start at 1 since index 0 is the type itself + for (int i = 0; i < genericParameter.GenericParameterPosition; i++) + { + nullabilityStateIndex += CountNullabilityStates(genericArguments[i]); } + return TryPopulateNullabilityInfo(nullability, parser, ref nullabilityStateIndex); - private bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index) + static int CountNullabilityStates(Type type) { - bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type); - if (!isValueType) + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + if (underlyingType.IsGenericType) { - var state = NullabilityState.Unknown; - if (!parser.ParseNullableState(index, ref state)) + int count = 1; + foreach (Type genericArgument in underlyingType.GetGenericArguments()) { - return false; + count += CountNullabilityStates(genericArgument); } - - nullability.ReadState = state; - nullability.WriteState = state; + return count; } - if (!isValueType || (Nullable.GetUnderlyingType(nullability.Type) ?? nullability.Type).IsGenericType) + if (underlyingType.HasElementType) { - index++; + return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!); } - if (nullability.GenericTypeArguments.Length > 0) - { - foreach (NullabilityInfo genericTypeArgumentNullability in nullability.GenericTypeArguments) - { - TryPopulateNullabilityInfo(genericTypeArgumentNullability, parser, ref index); - } - } - else if (nullability.ElementType is { } elementTypeNullability) + return type.IsValueType ? 0 : 1; + } + } + + private bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index) + { + bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type); + if (!isValueType) + { + var state = NullabilityState.Unknown; + if (!parser.ParseNullableState(index, ref state)) { - TryPopulateNullabilityInfo(elementTypeNullability, parser, ref index); + return false; } - return true; + nullability.ReadState = state; + nullability.WriteState = state; } - private static NullabilityState TranslateByte(object? value) + if (!isValueType || (Nullable.GetUnderlyingType(nullability.Type) ?? nullability.Type).IsGenericType) { - return value is byte b ? TranslateByte(b) : NullabilityState.Unknown; + index++; } - private static NullabilityState TranslateByte(byte b) => - b switch + if (nullability.GenericTypeArguments.Length > 0) + { + foreach (NullabilityInfo genericTypeArgumentNullability in nullability.GenericTypeArguments) { - 1 => NullabilityState.NotNull, - 2 => NullabilityState.Nullable, - _ => NullabilityState.Unknown - }; + TryPopulateNullabilityInfo(genericTypeArgumentNullability, parser, ref index); + } + } + else if (nullability.ElementType is { } elementTypeNullability) + { + TryPopulateNullabilityInfo(elementTypeNullability, parser, ref index); + } + + return true; + } - private static bool IsValueTypeOrValueTypeByRef(Type type) => type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType); + private static NullabilityState TranslateByte(object? value) + { + return value is byte b ? TranslateByte(b) : NullabilityState.Unknown; + } - private readonly struct NullableAttributeStateParser + private static NullabilityState TranslateByte(byte b) => + b switch { - private static readonly object UnknownByte = (byte)0; + 1 => NullabilityState.NotNull, + 2 => NullabilityState.Nullable, + _ => NullabilityState.Unknown, + }; - private readonly object? _nullableAttributeArgument; + private static bool IsValueTypeOrValueTypeByRef(Type type) => type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType); - public NullableAttributeStateParser(object? nullableAttributeArgument) - { - this._nullableAttributeArgument = nullableAttributeArgument; - } + private readonly struct NullableAttributeStateParser + { + private static readonly object UnknownByte = (byte)0; - public static NullableAttributeStateParser Unknown => new(UnknownByte); + private readonly object? _nullableAttributeArgument; - public bool ParseNullableState(int index, ref NullabilityState state) - { - switch (this._nullableAttributeArgument) - { - case byte b: - state = TranslateByte(b); - return true; - case ReadOnlyCollection args when index < args.Count && args[index].Value is byte elementB: - state = TranslateByte(elementB); - return true; - default: - return false; - } + public NullableAttributeStateParser(object? nullableAttributeArgument) + { + this._nullableAttributeArgument = nullableAttributeArgument; + } + + public static NullableAttributeStateParser Unknown => new(UnknownByte); + + public bool ParseNullableState(int index, ref NullabilityState state) + { + switch (this._nullableAttributeArgument) + { + case byte b: + state = TranslateByte(b); + return true; + case ReadOnlyCollection args when index < args.Count && args[index].Value is byte elementB: + state = TranslateByte(elementB); + return true; + default: + return false; } } } diff --git a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoExtensions.cs b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoExtensions.cs index 75a0cded..5038a008 100644 --- a/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoExtensions.cs +++ b/src/EntityGraphQL/Extensions/Nullability/NullabilityInfoExtensions.cs @@ -4,222 +4,221 @@ using System.Reflection; using EntityGraphQL.Compiler; -namespace Nullability -{ - /// - /// Static and thread safe wrapper around . - /// - public static class NullabilityInfoExtensions - { - private static readonly ConcurrentDictionary parameterCache = new(); - private static readonly ConcurrentDictionary propertyCache = new(); - private static readonly ConcurrentDictionary eventCache = new(); - private static readonly ConcurrentDictionary fieldCache = new(); - - public static NullabilityInfo GetNullabilityInfo(this MemberInfo info) - { - if (info is PropertyInfo propertyInfo) - { - return propertyInfo.GetNullabilityInfo(); - } - - if (info is EventInfo eventInfo) - { - return eventInfo.GetNullabilityInfo(); - } - - if (info is FieldInfo fieldInfo) - { - return fieldInfo.GetNullabilityInfo(); - } - - if (info is MethodInfo methodInfo) - { - return methodInfo.GetNullabilityInfo(); - } +namespace Nullability; - throw new ArgumentException($"Unsupported type:{info.GetType().FullName}"); - } - - public static NullabilityInfo Unwrap(this NullabilityInfo info) - { - if (info.GenericTypeArguments.Length == 0) - { - return info; - } - - if (info.Type.GetGenericTypeDefinition() == typeof(Expression<>)) - { - return info.GenericTypeArguments[0].Unwrap(); - } - - if (info.Type.Name.StartsWith("Func`", StringComparison.InvariantCulture)) - { - return info.GenericTypeArguments[^1].Unwrap(); - } - - return info; - } +/// +/// Static and thread safe wrapper around . +/// +public static class NullabilityInfoExtensions +{ + private static readonly ConcurrentDictionary parameterCache = new(); + private static readonly ConcurrentDictionary propertyCache = new(); + private static readonly ConcurrentDictionary eventCache = new(); + private static readonly ConcurrentDictionary fieldCache = new(); - public static NullabilityInfo GetNullabilityInfo(this MethodInfo info) + public static NullabilityInfo GetNullabilityInfo(this MemberInfo info) + { + if (info is PropertyInfo propertyInfo) { - return info.ReturnParameter.GetNullabilityInfo(); + return propertyInfo.GetNullabilityInfo(); } - public static NullabilityState GetNullability(this MemberInfo info) + if (info is EventInfo eventInfo) { - return GetReadOrWriteState(info.Name, info.GetNullabilityInfo()); + return eventInfo.GetNullabilityInfo(); } - public static bool IsNullable(this MemberInfo info) + if (info is FieldInfo fieldInfo) { - var nullability = info.GetNullabilityInfo(); - return IsNullable(info.Name, nullability); + return fieldInfo.GetNullabilityInfo(); } - public static NullabilityInfo GetNullabilityInfo(this FieldInfo info) + if (info is MethodInfo methodInfo) { - return fieldCache.GetOrAdd( - info, - inner => - { - var nullabilityContext = new NullabilityInfoContext(); - return nullabilityContext.Create(inner); - } - ); + return methodInfo.GetNullabilityInfo(); } - public static NullabilityState GetNullability(this FieldInfo info) - { - return GetReadOrWriteState(info.Name, info.GetNullabilityInfo()); - } + throw new ArgumentException($"Unsupported type:{info.GetType().FullName}"); + } - public static bool IsNullable(this FieldInfo info) + public static NullabilityInfo Unwrap(this NullabilityInfo info) + { + if (info.GenericTypeArguments.Length == 0) { - var nullability = info.GetNullabilityInfo(); - return IsNullable(info.Name, nullability); + return info; } - public static NullabilityInfo GetNullabilityInfo(this EventInfo info) + if (info.Type.GetGenericTypeDefinition() == typeof(Expression<>)) { - return eventCache.GetOrAdd( - info, - inner => - { - var nullabilityContext = new NullabilityInfoContext(); - return nullabilityContext.Create(inner); - } - ); + return info.GenericTypeArguments[0].Unwrap(); } - public static NullabilityState GetNullability(this EventInfo info) + if (info.Type.Name.StartsWith("Func`", StringComparison.InvariantCulture)) { - return GetReadOrWriteState(info.Name, info.GetNullabilityInfo()); + return info.GenericTypeArguments[^1].Unwrap(); } - public static bool IsNullable(this EventInfo info) - { - var nullability = info.GetNullabilityInfo(); - return IsNullable(info.Name, nullability); - } + return info; + } - public static NullabilityInfo GetNullabilityInfo(this PropertyInfo info) - { - return propertyCache.GetOrAdd( - info, - inner => - { - var nullabilityContext = new NullabilityInfoContext(); - return nullabilityContext.Create(inner); - } - ); - } + public static NullabilityInfo GetNullabilityInfo(this MethodInfo info) + { + return info.ReturnParameter.GetNullabilityInfo(); + } - public static NullabilityState GetNullability(this PropertyInfo info) - { - return GetReadOrWriteState(info.Name, info.GetNullabilityInfo()); - } + public static NullabilityState GetNullability(this MemberInfo info) + { + return GetReadOrWriteState(info.GetNullabilityInfo()); + } - public static bool IsNullable(this PropertyInfo info) - { - var nullability = info.GetNullabilityInfo(); - return IsNullable(info.Name, nullability); - } + public static bool IsNullable(this MemberInfo info) + { + var nullability = info.GetNullabilityInfo(); + return IsNullable(info.Name, nullability); + } - public static NullabilityInfo GetNullabilityInfo(this ParameterInfo info) - { - return parameterCache.GetOrAdd( - info, - inner => - { - var nullabilityContext = new NullabilityInfoContext(); - return nullabilityContext.Create(inner); - } - ); - } + public static NullabilityInfo GetNullabilityInfo(this FieldInfo info) + { + return fieldCache.GetOrAdd( + info, + inner => + { + var nullabilityContext = new NullabilityInfoContext(); + return nullabilityContext.Create(inner); + } + ); + } - public static NullabilityState GetNullability(this ParameterInfo info) - { - return GetReadOrWriteState(info.Name!, info.GetNullabilityInfo()); - } + public static NullabilityState GetNullability(this FieldInfo info) + { + return GetReadOrWriteState(info.GetNullabilityInfo()); + } - public static bool IsNullable(this ParameterInfo info) - { - var nullability = info.GetNullabilityInfo(); - return IsNullable(info.Name!, nullability); - } + public static bool IsNullable(this FieldInfo info) + { + var nullability = info.GetNullabilityInfo(); + return IsNullable(info.Name, nullability); + } - static NullabilityState GetReadOrWriteState(string name, NullabilityInfo nullability) - { - if (nullability.ReadState != NullabilityState.Unknown) + public static NullabilityInfo GetNullabilityInfo(this EventInfo info) + { + return eventCache.GetOrAdd( + info, + inner => { - return nullability.ReadState; + var nullabilityContext = new NullabilityInfoContext(); + return nullabilityContext.Create(inner); } + ); + } - return nullability.WriteState; - } + public static NullabilityState GetNullability(this EventInfo info) + { + return GetReadOrWriteState(info.GetNullabilityInfo()); + } - static NullabilityState GetKnownState(string name, NullabilityInfo nullability) - { - var readState = nullability.ReadState; - if (readState != NullabilityState.Unknown) + public static bool IsNullable(this EventInfo info) + { + var nullability = info.GetNullabilityInfo(); + return IsNullable(info.Name, nullability); + } + + public static NullabilityInfo GetNullabilityInfo(this PropertyInfo info) + { + return propertyCache.GetOrAdd( + info, + inner => { - return readState; + var nullabilityContext = new NullabilityInfoContext(); + return nullabilityContext.Create(inner); } + ); + } - var writeState = nullability.WriteState; - if (writeState != NullabilityState.Unknown) + public static NullabilityState GetNullability(this PropertyInfo info) + { + return GetReadOrWriteState(info.GetNullabilityInfo()); + } + + public static bool IsNullable(this PropertyInfo info) + { + var nullability = info.GetNullabilityInfo(); + return IsNullable(info.Name, nullability); + } + + public static NullabilityInfo GetNullabilityInfo(this ParameterInfo info) + { + return parameterCache.GetOrAdd( + info, + inner => { - return writeState; + var nullabilityContext = new NullabilityInfoContext(); + return nullabilityContext.Create(inner); } + ); + } - throw new EntityGraphQLCompilerException($"The nullability of '{nullability.Type.FullName}.{name}' is unknown. Assembly: {nullability.Type.Assembly.FullName}."); - } + public static NullabilityState GetNullability(this ParameterInfo info) + { + return GetReadOrWriteState(info.GetNullabilityInfo()); + } + + public static bool IsNullable(this ParameterInfo info) + { + var nullability = info.GetNullabilityInfo(); + return IsNullable(info.Name!, nullability); + } - static bool IsNullable(string name, NullabilityInfo nullability) + private static NullabilityState GetReadOrWriteState(NullabilityInfo nullability) + { + if (nullability.ReadState != NullabilityState.Unknown) { - return GetKnownState(name, nullability) == NullabilityState.Nullable; + return nullability.ReadState; } - //Patching - public static MemberInfo GetMemberWithSameMetadataDefinitionAs(Type type, MemberInfo member) + return nullability.WriteState; + } + + private static NullabilityState GetKnownState(string name, NullabilityInfo nullability) + { + var readState = nullability.ReadState; + if (readState != NullabilityState.Unknown) { - const BindingFlags all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; - foreach (var info in type.GetMembers(all)) - { - if (info.HasSameMetadataDefinitionAs(member)) - { - return info; - } - } + return readState; + } - throw new MissingMemberException(type.FullName, member.Name); + var writeState = nullability.WriteState; + if (writeState != NullabilityState.Unknown) + { + return writeState; } - //https://github.com/dotnet/runtime/issues/23493 - public static bool IsGenericMethodParameter(this Type target) + throw new EntityGraphQLCompilerException($"The nullability of '{nullability.Type.FullName}.{name}' is unknown. Assembly: {nullability.Type.Assembly.FullName}."); + } + + private static bool IsNullable(string name, NullabilityInfo nullability) + { + return GetKnownState(name, nullability) == NullabilityState.Nullable; + } + + //Patching + public static MemberInfo GetMemberWithSameMetadataDefinitionAs(Type type, MemberInfo member) + { + const BindingFlags all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; + foreach (var info in type.GetMembers(all)) { - return target.IsGenericParameter && target.DeclaringMethod != null; + if (info.HasSameMetadataDefinitionAs(member)) + { + return info; + } } + + throw new MissingMemberException(type.FullName, member.Name); + } + + //https://github.com/dotnet/runtime/issues/23493 + public static bool IsGenericMethodParameter(this Type target) + { + return target.IsGenericParameter && target.DeclaringMethod != null; } } diff --git a/src/EntityGraphQL/Extensions/QueryableExtensions.cs b/src/EntityGraphQL/Extensions/QueryableExtensions.cs index d66ea9f4..24bcc97b 100644 --- a/src/EntityGraphQL/Extensions/QueryableExtensions.cs +++ b/src/EntityGraphQL/Extensions/QueryableExtensions.cs @@ -3,49 +3,48 @@ using System.Linq.Expressions; using EntityGraphQL.Schema; -namespace EntityGraphQL.Extensions +namespace EntityGraphQL.Extensions; + +/// +/// Extension methods to allow to allow you to build queries and reuse expressions/filters +/// +public static class QueryableExtensions { - /// - /// Extension methods to allow to allow you to build queries and reuse expressions/filters - /// - public static class QueryableExtensions + public static IQueryable Take(this IQueryable source, int? count) { - public static IQueryable Take(this IQueryable source, int? count) - { - if (!count.HasValue) - return source; + if (!count.HasValue) + return source; - return Queryable.Take(source, count.Value); - } + return Queryable.Take(source, count.Value); + } - public static IQueryable Skip(this IQueryable source, int? count) - { - if (!count.HasValue) - return source; + public static IQueryable Skip(this IQueryable source, int? count) + { + if (!count.HasValue) + return source; + + return Queryable.Skip(source, count.Value); + } - return Queryable.Skip(source, count.Value); - } + public static IQueryable WhereWhen(this IQueryable source, Expression> wherePredicate, bool applyPredicate) + { + if (applyPredicate) + return Queryable.Where(source, wherePredicate); - public static IQueryable WhereWhen(this IQueryable source, Expression> wherePredicate, bool applyPredicate) - { - if (applyPredicate) - return Queryable.Where(source, wherePredicate); + return source; + } - return source; - } + public static IQueryable WhereWhen(this IQueryable source, EntityQueryType filter, bool applyPredicate) + { + if (applyPredicate) + return Queryable.Where(source, filter.Query!); + return source; + } - public static IQueryable WhereWhen(this IQueryable source, EntityQueryType filter, bool applyPredicate) - { - if (applyPredicate) - return Queryable.Where(source, filter.Query!); - return source; - } - - public static IQueryable? SelectWithNullCheck(this IQueryable source, Expression> selector) - { - if (source == null) - return null; - return source.Select(selector); - } + public static IQueryable? SelectWithNullCheck(this IQueryable source, Expression> selector) + { + if (source == null) + return null; + return source.Select(selector); } } diff --git a/src/EntityGraphQL/Extensions/StringExtensions.cs b/src/EntityGraphQL/Extensions/StringExtensions.cs index 17816071..1d2f0ae6 100644 --- a/src/EntityGraphQL/Extensions/StringExtensions.cs +++ b/src/EntityGraphQL/Extensions/StringExtensions.cs @@ -2,28 +2,27 @@ using System.Globalization; using System.Linq; -namespace EntityGraphQL.Extensions +namespace EntityGraphQL.Extensions; + +public static class StringExtensions { - public static class StringExtensions + public static string FirstCharToUpper(this string input) { - public static string FirstCharToUpper(this string input) + return input switch { - return input switch - { - null => throw new ArgumentNullException(nameof(input)), - "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), - _ => input.First().ToString().ToUpper(CultureInfo.InvariantCulture) + input[1..] - }; - } + null => throw new ArgumentNullException(nameof(input)), + "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), + _ => input.First().ToString().ToUpper(CultureInfo.InvariantCulture) + input[1..], + }; + } - public static string FirstCharToLower(this string input) + public static string FirstCharToLower(this string input) + { + return input switch { - return input switch - { - null => throw new ArgumentNullException(nameof(input)), - "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), - _ => input.First().ToString().ToLower(CultureInfo.InvariantCulture) + input[1..] - }; - } + null => throw new ArgumentNullException(nameof(input)), + "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)), + _ => input.First().ToString().ToLower(CultureInfo.InvariantCulture) + input[1..], + }; } } diff --git a/src/EntityGraphQL/GraphQLVaildation.cs b/src/EntityGraphQL/GraphQLVaildation.cs index 8c7c93d7..37cb2065 100644 --- a/src/EntityGraphQL/GraphQLVaildation.cs +++ b/src/EntityGraphQL/GraphQLVaildation.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; -namespace EntityGraphQL +namespace EntityGraphQL; + +public class GraphQLValidator : IGraphQLValidator { - public class GraphQLValidator : IGraphQLValidator - { - public List Errors { get; set; } = new List(); - public bool HasErrors => Errors.Count > 0; + public List Errors { get; set; } = []; + public bool HasErrors => Errors.Count > 0; - public void AddError(string message) => Errors.Add(new GraphQLError(message, null)); + public void AddError(string message) => Errors.Add(new GraphQLError(message, null)); - public void AddError(string message, Dictionary extensions) => Errors.Add(new GraphQLError(message, extensions)); - } + public void AddError(string message, Dictionary extensions) => Errors.Add(new GraphQLError(message, extensions)); } diff --git a/src/EntityGraphQL/IGraphQLValidator.cs b/src/EntityGraphQL/IGraphQLValidator.cs index 8216f178..f97b89b1 100644 --- a/src/EntityGraphQL/IGraphQLValidator.cs +++ b/src/EntityGraphQL/IGraphQLValidator.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; -namespace EntityGraphQL +namespace EntityGraphQL; + +public interface IGraphQLValidator { - public interface IGraphQLValidator - { - List Errors { get; } - bool HasErrors { get; } + List Errors { get; } + bool HasErrors { get; } - void AddError(string message); - void AddError(string message, Dictionary extensions); - } + void AddError(string message); + void AddError(string message, Dictionary extensions); } diff --git a/src/EntityGraphQL/QueryRequest.cs b/src/EntityGraphQL/QueryRequest.cs index 25bba93d..e592f2d3 100644 --- a/src/EntityGraphQL/QueryRequest.cs +++ b/src/EntityGraphQL/QueryRequest.cs @@ -72,15 +72,9 @@ public class GraphQLError : Dictionary { private static readonly string MessageKey = "message"; - public string Message - { - get => (string)this[MessageKey]; - } + public string Message => (string)this[MessageKey]; - public Dictionary? Extensions - { - get => (Dictionary?)this.GetValueOrDefault(QueryResult.ExtensionsKey); - } + public Dictionary? Extensions => (Dictionary?)this.GetValueOrDefault(QueryResult.ExtensionsKey); public GraphQLError(string message, IDictionary? extensions) { diff --git a/src/EntityGraphQL/QueryResult.cs b/src/EntityGraphQL/QueryResult.cs index 60521593..9775f26e 100644 --- a/src/EntityGraphQL/QueryResult.cs +++ b/src/EntityGraphQL/QueryResult.cs @@ -1,68 +1,71 @@ using System.Collections.Generic; using System.Linq; -namespace EntityGraphQL +namespace EntityGraphQL; + +public class QueryResult : Dictionary { - public class QueryResult : Dictionary + private static readonly string DataKey = "data"; + private static readonly string ErrorsKey = "errors"; + internal static readonly string ExtensionsKey = "extensions"; + public List? Errors => (List?)this.GetValueOrDefault(ErrorsKey); + public Dictionary? Data => (Dictionary?)this.GetValueOrDefault(DataKey); + + /// + /// Use Extensions to add any custom data for the result + /// + public Dictionary? Extensions => (Dictionary?)this.GetValueOrDefault(ExtensionsKey); + + public QueryResult() { } + + public QueryResult(GraphQLError error) { - private static readonly string DataKey = "data"; - private static readonly string ErrorsKey = "errors"; - internal static readonly string ExtensionsKey = "extensions"; - public List? Errors => (List?)this.GetValueOrDefault(ErrorsKey); - public Dictionary? Data - { - get => (Dictionary?)this.GetValueOrDefault(DataKey); - } + this[ErrorsKey] = new List { error }; + } - /// - /// Use Extensions to add any custom data for the result - /// - public Dictionary? Extensions - { - get => (Dictionary?)this.GetValueOrDefault(ExtensionsKey); - } + public QueryResult(IEnumerable errors) + { + this[ErrorsKey] = errors.ToList(); + } - public QueryResult() { } + public bool HasErrors() => Errors?.Count > 0; - public QueryResult(GraphQLError error) - { - this[ErrorsKey] = new List { error }; - } + public void AddError(string error, IDictionary? extensions = null) + { + AddError(new GraphQLError(error, extensions)); + } - public QueryResult(IEnumerable errors) + public void AddError(GraphQLError error) + { + if (!this.ContainsKey(ErrorsKey)) { - this[ErrorsKey] = errors.ToList(); + this[ErrorsKey] = new List(); } - public bool HasErrors() => Errors?.Count > 0; + ((List)this[ErrorsKey]).Add(error); + } - public void AddError(string error, IDictionary? extensions = null) + public void AddErrors(IEnumerable errors) + { + if (!this.ContainsKey(ErrorsKey)) { - AddError(new GraphQLError(error, extensions)); + this[ErrorsKey] = new List(); } + ((List)this[ErrorsKey]).AddRange(errors); + } - public void AddError(GraphQLError error) - { - if (!this.ContainsKey(ErrorsKey)) - { - this[ErrorsKey] = new List(); - } - - ((List)this[ErrorsKey]).Add(error); - } + public void SetData(IDictionary data) + { + this[DataKey] = data.ToDictionary(d => d.Key, d => d.Value); + } - public void AddErrors(IEnumerable errors) - { - if (!this.ContainsKey(ErrorsKey)) - { - this[ErrorsKey] = new List(); - } - ((List)this[ErrorsKey]).AddRange(errors); - } + public void RemoveDataKey() + { + Remove(DataKey); + } - public void SetData(IDictionary data) - { - this[DataKey] = data.ToDictionary(d => d.Key, d => d.Value); - } + public bool HasErrorKey() + { + return ContainsKey(ErrorsKey); } } diff --git a/src/EntityGraphQL/Schema/ArgType.cs b/src/EntityGraphQL/Schema/ArgType.cs index 6d57090e..dd5a2056 100644 --- a/src/EntityGraphQL/Schema/ArgType.cs +++ b/src/EntityGraphQL/Schema/ArgType.cs @@ -7,136 +7,134 @@ using EntityGraphQL.Extensions; using Nullability; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Holds information about arguments for fields +/// +public class ArgType { - /// - /// Holds information about arguments for fields - /// - public class ArgType + public string Name { get; private set; } + public string DotnetName { get; private set; } + public string Description { get; set; } + public GqlTypeInfo Type { get; private set; } + public object? DefaultValue { get; set; } + public MemberInfo? MemberInfo { get; internal set; } + + private RequiredAttribute? requiredAttribute; + public bool IsRequired { get; set; } + public Type RawType { get; private set; } + + public ArgType(string name, string dotnetName, GqlTypeInfo type, MemberInfo? memberInfo, Type rawType) + { + Name = name; + DotnetName = dotnetName; + Description = string.Empty; + Type = type; + MemberInfo = memberInfo; + RawType = rawType; + DefaultValue = null; + IsRequired = false; + } + + public static ArgType FromProperty(ISchemaProvider schema, PropertyInfo prop, object? defaultValue) + { + var nullability = prop.GetNullabilityInfo(); + var arg = MakeArgType(schema, prop.Name, prop, prop.GetCustomAttributes(), prop.PropertyType, defaultValue, nullability); + + return arg; + } + + public static ArgType FromParameter(ISchemaProvider schema, ParameterInfo parameter, object? defaultValue) + { + var nullability = parameter.GetNullabilityInfo(); + var arg = MakeArgType(schema, parameter.Name!, null, parameter.GetCustomAttributes(), parameter.ParameterType, defaultValue, nullability); + return arg; + } + + public static ArgType FromField(ISchemaProvider schema, FieldInfo field, object? defaultValue) + { + var nullability = field.GetNullabilityInfo(); + var arg = MakeArgType(schema, field.Name, field, field.GetCustomAttributes(), field.FieldType, defaultValue, nullability); + return arg; + } + + private static ArgType MakeArgType(ISchemaProvider schema, string name, MemberInfo? memberInfo, IEnumerable attributes, Type type, object? defaultValue, NullabilityInfo nullability) { - public string Name { get; private set; } - public string DotnetName { get; private set; } - public string Description { get; set; } - public GqlTypeInfo Type { get; private set; } - public object? DefaultValue { get; set; } - public MemberInfo? MemberInfo { get; internal set; } - - private RequiredAttribute? requiredAttribute; - public bool IsRequired { get; set; } - public Type RawType { get; private set; } - - public ArgType(string name, string dotnetName, GqlTypeInfo type, MemberInfo? memberInfo, Type rawType) + var markedRequired = false; + var gqlLookupType = type; + var argType = type; + if (gqlLookupType.IsConstructedGenericType && gqlLookupType.GetGenericTypeDefinition() == typeof(RequiredField<>)) { - Name = name; - DotnetName = dotnetName; - Description = string.Empty; - Type = type; - MemberInfo = memberInfo; - RawType = rawType; - DefaultValue = null; - IsRequired = false; + markedRequired = true; + argType = gqlLookupType = gqlLookupType.GetGenericArguments()[0]; + // default value will often be the default value of the non-null type (e.g. 0 for int). + // We are saying here it must be provided by the query + defaultValue = null; } - - public static ArgType FromProperty(ISchemaProvider schema, PropertyInfo prop, object? defaultValue) + if (gqlLookupType.IsConstructedGenericType && gqlLookupType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) { - var nullability = prop.GetNullabilityInfo(); - var arg = MakeArgType(schema, prop.Name, prop, prop.GetCustomAttributes(), prop.PropertyType, defaultValue, nullability); - - return arg; + gqlLookupType = typeof(string); } - - public static ArgType FromParameter(ISchemaProvider schema, ParameterInfo parameter, object? defaultValue) + if (gqlLookupType.IsEnumerableOrArray()) { - var nullability = parameter.GetNullabilityInfo(); - var arg = MakeArgType(schema, parameter.Name!, null, parameter.GetCustomAttributes(), parameter.ParameterType, defaultValue, nullability); - return arg; + gqlLookupType = gqlLookupType.GetNonNullableOrEnumerableType(); } + if (gqlLookupType.IsNullableType()) + { + gqlLookupType = gqlLookupType.GetGenericArguments()[0]; + } + + var gqlTypeInfo = new GqlTypeInfo(() => schema.GetSchemaType(gqlLookupType, true, null), argType, nullability); + var arg = new ArgType(schema.SchemaFieldNamer(name), name, gqlTypeInfo, memberInfo, type) + { + DefaultValue = defaultValue, + IsRequired = markedRequired, + requiredAttribute = attributes.FirstOrDefault(a => a is RequiredAttribute) as RequiredAttribute, + }; - public static ArgType FromField(ISchemaProvider schema, FieldInfo field, object? defaultValue) + if (memberInfo?.GetCustomAttribute() is GraphQLFieldAttribute gqlFieldAttr) { - var nullability = field.GetNullabilityInfo(); - var arg = MakeArgType(schema, field.Name, field, field.GetCustomAttributes(), field.FieldType, defaultValue, nullability); - return arg; + if (!string.IsNullOrEmpty(gqlFieldAttr.Name)) + arg.Name = gqlFieldAttr.Name; + if (!string.IsNullOrEmpty(gqlFieldAttr.Description)) + arg.Description = gqlFieldAttr.Description; } - private static ArgType MakeArgType(ISchemaProvider schema, string name, MemberInfo? memberInfo, IEnumerable attributes, Type type, object? defaultValue, NullabilityInfo nullability) + if (arg.requiredAttribute != null || GraphQLNotNullAttribute.IsMemberMarkedNotNull(attributes) || nullability.WriteState == NullabilityState.NotNull) { - var markedRequired = false; - var gqlLookupType = type; - var argType = type; - if (gqlLookupType.IsConstructedGenericType && gqlLookupType.GetGenericTypeDefinition() == typeof(RequiredField<>)) - { - markedRequired = true; - argType = gqlLookupType = gqlLookupType.GetGenericArguments()[0]; - // default value will often be the default value of the non-null type (e.g. 0 for int). - // We are saying here it must be provided by the query - defaultValue = null; - } - if (gqlLookupType.IsConstructedGenericType && gqlLookupType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) - { - gqlLookupType = typeof(string); - } - if (gqlLookupType.IsEnumerableOrArray()) - { - gqlLookupType = gqlLookupType.GetNonNullableOrEnumerableType(); - } - if (gqlLookupType.IsNullableType()) - { - gqlLookupType = gqlLookupType.GetGenericArguments()[0]; - } - - var gqlTypeInfo = new GqlTypeInfo(() => schema.GetSchemaType(gqlLookupType, true, null), argType, nullability); - var arg = new ArgType(schema.SchemaFieldNamer(name), name, gqlTypeInfo, memberInfo, type) - { - DefaultValue = defaultValue, - IsRequired = markedRequired, - requiredAttribute = attributes.FirstOrDefault(a => a is RequiredAttribute) as RequiredAttribute - }; - - if (memberInfo?.GetCustomAttribute() is GraphQLFieldAttribute gqlFieldAttr) - { - if (!string.IsNullOrEmpty(gqlFieldAttr.Name)) - arg.Name = gqlFieldAttr.Name; - if (!string.IsNullOrEmpty(gqlFieldAttr.Description)) - arg.Description = gqlFieldAttr.Description; - } - - if (arg.requiredAttribute != null || GraphQLNotNullAttribute.IsMemberMarkedNotNull(attributes) || nullability.WriteState == NullabilityState.NotNull) - { - arg.IsRequired = true; - } - - if (arg.IsRequired) - arg.Type.TypeNotNullable = true; - else if (arg.Type.TypeNotNullable) - arg.IsRequired = true; - - if (attributes.FirstOrDefault(a => a is DescriptionAttribute) is DescriptionAttribute d) - { - arg.Description = d.Description; - } - - return arg; + arg.IsRequired = true; } - /// - /// Validate that the value for the argument meets the requirements of the argument - /// - /// - /// - /// - /// - public void Validate(object? val, string fieldName, IList validationErrors) + if (arg.IsRequired) + arg.Type.TypeNotNullable = true; + else if (arg.Type.TypeNotNullable) + arg.IsRequired = true; + + if (attributes.FirstOrDefault(a => a is DescriptionAttribute) is DescriptionAttribute d) { - var valType = val?.GetType(); - if (valType != null && valType.IsGenericType && valType.GetGenericTypeDefinition() == typeof(RequiredField<>)) - val = valType.GetProperty("Value")!.GetValue(val); - if (requiredAttribute != null && !requiredAttribute.IsValid(val)) - validationErrors.Add(requiredAttribute.ErrorMessage != null ? $"Field '{fieldName}' - {requiredAttribute.ErrorMessage}" : $"Field '{fieldName}' - missing required argument '{Name}'"); - else if (IsRequired && val == null && DefaultValue == null) - validationErrors.Add($"Field '{fieldName}' - missing required argument '{Name}'"); - - Type.SchemaType.Validate(val); + arg.Description = d.Description; } + + return arg; + } + + /// + /// Validate that the value for the argument meets the requirements of the argument + /// + /// + /// + /// + public void Validate(object? val, string fieldName, IList validationErrors) + { + var valType = val?.GetType(); + if (valType != null && valType.IsGenericType && valType.GetGenericTypeDefinition() == typeof(RequiredField<>)) + val = valType.GetProperty("Value")!.GetValue(val); + if (requiredAttribute != null && !requiredAttribute.IsValid(val)) + validationErrors.Add(requiredAttribute.ErrorMessage != null ? $"Field '{fieldName}' - {requiredAttribute.ErrorMessage}" : $"Field '{fieldName}' - missing required argument '{Name}'"); + else if (IsRequired && val == null && DefaultValue == null) + validationErrors.Add($"Field '{fieldName}' - missing required argument '{Name}'"); + + Type.SchemaType.Validate(val); } } diff --git a/src/EntityGraphQL/Schema/ArgumentHelper.cs b/src/EntityGraphQL/Schema/ArgumentHelper.cs index 659c119b..19e450a5 100644 --- a/src/EntityGraphQL/Schema/ArgumentHelper.cs +++ b/src/EntityGraphQL/Schema/ArgumentHelper.cs @@ -78,10 +78,7 @@ public class EntityQueryType : BaseEntityQueryType /// /// public Expression>? Query { get; set; } - public override bool HasValue - { - get => Query != null; - } + public override bool HasValue => Query != null; public EntityQueryType() : base(typeof(TType)) { } diff --git a/src/EntityGraphQL/Schema/Attributes/GraphQLAuthorizeAttribute.cs b/src/EntityGraphQL/Schema/Attributes/GraphQLAuthorizeAttribute.cs index 37721943..96b679f9 100644 --- a/src/EntityGraphQL/Schema/Attributes/GraphQLAuthorizeAttribute.cs +++ b/src/EntityGraphQL/Schema/Attributes/GraphQLAuthorizeAttribute.cs @@ -2,28 +2,27 @@ using System.Collections.Generic; using System.Linq; -namespace EntityGraphQL.Authorization -{ - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class GraphQLAuthorizeAttribute : Attribute - { - /// - /// Initializes a new instance of the GraphQLAuthorizeAttribute class - /// - public GraphQLAuthorizeAttribute() { } +namespace EntityGraphQL.Authorization; - /// - /// Initializes a new instance of the GraphQLAuthorizeAttribute class with the specified roles. - /// - /// - public GraphQLAuthorizeAttribute(params string[] roles) - { - Roles = roles.ToList(); - } +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public class GraphQLAuthorizeAttribute : Attribute +{ + /// + /// Initializes a new instance of the GraphQLAuthorizeAttribute class + /// + public GraphQLAuthorizeAttribute() { } - /// - /// Gets or sets the roles name that determines access to the resource. - /// - public List? Roles { get; set; } + /// + /// Initializes a new instance of the GraphQLAuthorizeAttribute class with the specified roles. + /// + /// + public GraphQLAuthorizeAttribute(params string[] roles) + { + Roles = roles.ToList(); } + + /// + /// Gets or sets the roles name that determines access to the resource. + /// + public List? Roles { get; set; } } diff --git a/src/EntityGraphQL/Schema/Attributes/GraphQLIgnoreAttribute.cs b/src/EntityGraphQL/Schema/Attributes/GraphQLIgnoreAttribute.cs index c0b5e92d..25375fe3 100644 --- a/src/EntityGraphQL/Schema/Attributes/GraphQLIgnoreAttribute.cs +++ b/src/EntityGraphQL/Schema/Attributes/GraphQLIgnoreAttribute.cs @@ -1,87 +1,86 @@ using System; using System.Reflection; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Tell the Schema Builder to ignore this field or property +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] +public class GraphQLIgnoreAttribute : Attribute { - /// - /// Tell the Schema Builder to ignore this field or property - /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class GraphQLIgnoreAttribute : Attribute + public GraphQLIgnoreAttribute(GraphQLIgnoreType from = GraphQLIgnoreType.All) { - public GraphQLIgnoreAttribute(GraphQLIgnoreType from = GraphQLIgnoreType.All) - { - IgnoreFrom = from; - } + IgnoreFrom = from; + } - public GraphQLIgnoreType IgnoreFrom { get; } + public GraphQLIgnoreType IgnoreFrom { get; } - /// - /// Property is marked as being ignored for inclusion in the Query schema - /// - /// - /// - public static bool ShouldIgnoreMemberFromQuery(MemberInfo prop) + /// + /// Property is marked as being ignored for inclusion in the Query schema + /// + /// + /// + public static bool ShouldIgnoreMemberFromQuery(MemberInfo prop) + { + if (prop.GetCustomAttribute() is GraphQLIgnoreAttribute attribute) { - if (prop.GetCustomAttribute(typeof(GraphQLIgnoreAttribute)) is GraphQLIgnoreAttribute attribute) + if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Query) { - if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Query) - { - return true; - } + return true; } - return false; } + return false; + } - /// - /// Property is marked as being ignored for inclusion in the Mutation Input types - /// - /// - /// - public static bool ShouldIgnoreMemberFromInput(MemberInfo prop) + /// + /// Property is marked as being ignored for inclusion in the Mutation Input types + /// + /// + /// + public static bool ShouldIgnoreMemberFromInput(MemberInfo prop) + { + if (prop.GetCustomAttribute() is GraphQLIgnoreAttribute attribute) { - if (prop.GetCustomAttribute(typeof(GraphQLIgnoreAttribute)) is GraphQLIgnoreAttribute attribute) + if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Input) { - if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Input) - { - return true; - } + return true; } - return false; } + return false; + } - /// - /// Parameter is marked as being ignored for inclusion in the Mutation Input types - /// - /// - /// - public static bool ShouldIgnoreMemberFromInput(ParameterInfo prop) + /// + /// Parameter is marked as being ignored for inclusion in the Mutation Input types + /// + /// + /// + public static bool ShouldIgnoreMemberFromInput(ParameterInfo prop) + { + if (prop.GetCustomAttribute() is GraphQLIgnoreAttribute attribute) { - if (prop.GetCustomAttribute(typeof(GraphQLIgnoreAttribute)) is GraphQLIgnoreAttribute attribute) + if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Input) { - if (attribute.IgnoreFrom == GraphQLIgnoreType.All || attribute.IgnoreFrom == GraphQLIgnoreType.Input) - { - return true; - } + return true; } - return false; } + return false; } +} - public enum GraphQLIgnoreType - { - /// - /// Ignored in generating the schema for Query - /// - Query, +public enum GraphQLIgnoreType +{ + /// + /// Ignored in generating the schema for Query + /// + Query, - /// - /// Ignored in generating/deserialising the input types - Input, + /// + /// Ignored in generating/deserialising the input types + Input, - /// - /// Ignored completely by EntityGraphQL - /// - All, - } + /// + /// Ignored completely by EntityGraphQL + /// + All, } diff --git a/src/EntityGraphQL/Schema/Attributes/GraphQLMutationAttribute.cs b/src/EntityGraphQL/Schema/Attributes/GraphQLMutationAttribute.cs index 457f6027..2e316689 100644 --- a/src/EntityGraphQL/Schema/Attributes/GraphQLMutationAttribute.cs +++ b/src/EntityGraphQL/Schema/Attributes/GraphQLMutationAttribute.cs @@ -1,28 +1,27 @@ using System; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Marks the method in the class as a Mutation for EntityGraphQL to include in the Mutation Type. +/// You need to add the mutation to the schema using schema.AddMutationFrom(); +/// +[AttributeUsage(AttributeTargets.Method)] +public class GraphQLMutationAttribute : GraphQLMethodAttribute { - /// - /// Marks the method in the class as a Mutation for EntityGraphQL to include in the Mutation Type. - /// You need to add the mutation to the schema using schema.AddMutationFrom(); - /// - [AttributeUsage(AttributeTargets.Method)] - public class GraphQLMutationAttribute : GraphQLMethodAttribute + public GraphQLMutationAttribute(string description = "") { - public GraphQLMutationAttribute(string description = "") - { - this.Description = description; - } + this.Description = description; } +} - [AttributeUsage(AttributeTargets.Method)] - public abstract class GraphQLMethodAttribute : Attribute +[AttributeUsage(AttributeTargets.Method)] +public abstract class GraphQLMethodAttribute : Attribute +{ + public GraphQLMethodAttribute(string description = "") { - public GraphQLMethodAttribute(string description = "") - { - this.Description = description; - } - - public string Description { get; set; } + this.Description = description; } + + public string Description { get; set; } } diff --git a/src/EntityGraphQL/Schema/Attributes/GraphQLNotNullAttribute.cs b/src/EntityGraphQL/Schema/Attributes/GraphQLNotNullAttribute.cs index 5b946c10..d75b0871 100644 --- a/src/EntityGraphQL/Schema/Attributes/GraphQLNotNullAttribute.cs +++ b/src/EntityGraphQL/Schema/Attributes/GraphQLNotNullAttribute.cs @@ -4,57 +4,56 @@ using System.Linq; using System.Reflection; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Tell the Schema Builder that when building this field, it is not nullable in the schema +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] +public class GraphQLNotNullAttribute : Attribute { + public GraphQLNotNullAttribute() { } + /// - /// Tell the Schema Builder that when building this field, it is not nullable in the schema + /// Check if property is marked as being not null /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class GraphQLNotNullAttribute : Attribute + /// + /// + public static bool IsMemberMarkedNotNull(ICustomAttributeProvider prop) { - public GraphQLNotNullAttribute() { } - - /// - /// Check if property is marked as being not null - /// - /// - /// - public static bool IsMemberMarkedNotNull(ICustomAttributeProvider prop) - { - return IsMemberMarkedNotNull(prop.GetCustomAttributes(false).Cast()); - } + return IsMemberMarkedNotNull(prop.GetCustomAttributes(false).Cast()); + } - public static bool IsMemberMarkedNotNull(IEnumerable attributes) + public static bool IsMemberMarkedNotNull(IEnumerable attributes) + { + if (attributes.Any(a => a is GraphQLNotNullAttribute) || attributes.Any(a => a is RequiredAttribute)) { - if (attributes.Any(a => a is GraphQLNotNullAttribute) || attributes.Any(a => a is RequiredAttribute)) - { - return true; - } - return false; + return true; } + return false; } +} + +/// +/// Tells the schema builder that this the element type in the List/array of this field is nullable in the schema. +/// By default a IEnumerable will have the T as non-nullable in the GraphQL schema +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] +public class GraphQLElementTypeNullableAttribute : Attribute +{ + public GraphQLElementTypeNullableAttribute() { } /// - /// Tells the schema builder that this the element type in the List/array of this field is nullable in the schema. - /// By default a IEnumerable will have the T as non-nullable in the GraphQL schema + /// Check if property is marked as being not null /// - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class GraphQLElementTypeNullableAttribute : Attribute + /// + /// + public static bool IsMemberElementMarkedNullable(ICustomAttributeProvider prop) { - public GraphQLElementTypeNullableAttribute() { } - - /// - /// Check if property is marked as being not null - /// - /// - /// - public static bool IsMemberElementMarkedNullable(ICustomAttributeProvider prop) + if (prop.GetCustomAttributes(false).Any(a => a is GraphQLElementTypeNullableAttribute)) { - if (prop.GetCustomAttributes(false).Any(a => a is GraphQLElementTypeNullableAttribute)) - { - return true; - } - return false; + return true; } + return false; } } diff --git a/src/EntityGraphQL/Schema/Attributes/GraphQLSubscriptionAttribute.cs b/src/EntityGraphQL/Schema/Attributes/GraphQLSubscriptionAttribute.cs index fe6b47d7..9c8ca089 100644 --- a/src/EntityGraphQL/Schema/Attributes/GraphQLSubscriptionAttribute.cs +++ b/src/EntityGraphQL/Schema/Attributes/GraphQLSubscriptionAttribute.cs @@ -1,17 +1,16 @@ using System; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Marks the method in the class as a Subscription for EntityGraphQL to include in the Subscription Type. +/// You need to add the subscription class (containing the method) to the schema using schema.AddSubscriptionFrom(); +/// +[AttributeUsage(AttributeTargets.Method)] +public class GraphQLSubscriptionAttribute : GraphQLMethodAttribute { - /// - /// Marks the method in the class as a Subscription for EntityGraphQL to include in the Subscription Type. - /// You need to add the subscription class (containing the method) to the schema using schema.AddSubscriptionFrom(); - /// - [AttributeUsage(AttributeTargets.Method)] - public class GraphQLSubscriptionAttribute : GraphQLMethodAttribute + public GraphQLSubscriptionAttribute(string description = "") { - public GraphQLSubscriptionAttribute(string description = "") - { - this.Description = description; - } + this.Description = description; } } diff --git a/src/EntityGraphQL/Schema/BaseField.cs b/src/EntityGraphQL/Schema/BaseField.cs index 2efafef3..20de8a3b 100644 --- a/src/EntityGraphQL/Schema/BaseField.cs +++ b/src/EntityGraphQL/Schema/BaseField.cs @@ -179,8 +179,7 @@ public void UseArgumentsFrom(IField field) /// public IField RequiresAllRoles(params string[] roles) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAllRoles(roles); return this; } @@ -191,8 +190,7 @@ public IField RequiresAllRoles(params string[] roles) /// public IField RequiresAnyRole(params string[] roles) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAnyRole(roles); return this; } @@ -203,8 +201,7 @@ public IField RequiresAnyRole(params string[] roles) /// public IField RequiresAllPolicies(params string[] policies) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAllPolicies(policies); return this; } @@ -215,8 +212,7 @@ public IField RequiresAllPolicies(params string[] policies) /// public IField RequiresAnyPolicy(params string[] policies) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAnyPolicy(policies); return this; } @@ -259,4 +255,16 @@ public IField AddDirective(ISchemaDirective directive) Directives.Add(directive); return this; } + + /// + /// Defines if the return type of this field is nullable or not. + /// + /// + /// + public IField IsNullable(bool nullable) + { + ReturnType.TypeNotNullable = !nullable; + + return this; + } } diff --git a/src/EntityGraphQL/Schema/BaseSchemaTypeWithFields.cs b/src/EntityGraphQL/Schema/BaseSchemaTypeWithFields.cs index f19bcebd..487cca16 100644 --- a/src/EntityGraphQL/Schema/BaseSchemaTypeWithFields.cs +++ b/src/EntityGraphQL/Schema/BaseSchemaTypeWithFields.cs @@ -5,197 +5,184 @@ using EntityGraphQL.Compiler; using EntityGraphQL.Schema.Directives; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public abstract class BaseSchemaTypeWithFields : ISchemaType + where TFieldType : IField { - public abstract class BaseSchemaTypeWithFields : ISchemaType - where TFieldType : IField + public ISchemaProvider Schema { get; } + internal Dictionary FieldsByName { get; } = []; + public abstract Type TypeDotnet { get; } + public string Name { get; } + public string? Description { get; set; } + public GqlTypes GqlType { get; protected set; } + + protected List BaseTypes { get; set; } = []; + protected List PossibleTypes { get; set; } = []; + public IList BaseTypesReadOnly => BaseTypes.AsReadOnly(); + public IList PossibleTypesReadOnly => PossibleTypes.AsReadOnly(); + + private readonly List directives = []; + public IList Directives => directives.AsReadOnly(); + public bool IsInput => GqlType == GqlTypes.InputObject; + public bool IsInterface => GqlType == GqlTypes.Interface; + public bool IsEnum => GqlType == GqlTypes.Enum; + public bool IsScalar => GqlType == GqlTypes.Scalar; + + public bool RequiresSelection => GqlType != GqlTypes.Scalar && GqlType != GqlTypes.Enum; + public RequiredAuthorization? RequiredAuthorization { get; set; } + private readonly Regex nameRegex = new("^[_a-zA-Z0-9]+$"); + + public event Action OnAddField = delegate { }; + public event Action OnValidate = delegate { }; + + protected BaseSchemaTypeWithFields(ISchemaProvider schema, string name, string? description, RequiredAuthorization? requiredAuthorization) { - public ISchemaProvider Schema { get; } - internal Dictionary FieldsByName { get; } = new(); - public abstract Type TypeDotnet { get; } - public string Name { get; } - public string? Description { get; set; } - public GqlTypes GqlType { get; protected set; } - - protected List BaseTypes { get; set; } = []; - protected List PossibleTypes { get; set; } = []; - public IList BaseTypesReadOnly => BaseTypes.AsReadOnly(); - public IList PossibleTypesReadOnly => PossibleTypes.AsReadOnly(); - - private readonly List directives = []; - public IList Directives => directives.AsReadOnly(); - public bool IsInput - { - get { return GqlType == GqlTypes.InputObject; } - } - public bool IsInterface - { - get { return GqlType == GqlTypes.Interface; } - } - public bool IsEnum - { - get { return GqlType == GqlTypes.Enum; } - } - public bool IsScalar - { - get { return GqlType == GqlTypes.Scalar; } - } - - public bool RequiresSelection => GqlType != GqlTypes.Scalar && GqlType != GqlTypes.Enum; - public RequiredAuthorization? RequiredAuthorization { get; set; } - private readonly Regex nameRegex = new("^[_a-zA-Z0-9]+$"); - - public event Action OnAddField = delegate { }; - public event Action OnValidate = delegate { }; - - protected BaseSchemaTypeWithFields(ISchemaProvider schema, string name, string? description, RequiredAuthorization? requiredAuthorization) - { - if (!nameRegex.IsMatch(name)) - throw new EntityGraphQLCompilerException($"Names must only contain [_a-zA-Z0-9] but '{name}' does not."); - this.Schema = schema; - Name = name; - Description = description; - RequiredAuthorization = requiredAuthorization; - } + if (!nameRegex.IsMatch(name)) + throw new EntityGraphQLCompilerException($"Names must only contain [_a-zA-Z0-9] but '{name}' does not."); + this.Schema = schema; + Name = name; + Description = description; + RequiredAuthorization = requiredAuthorization; + } - public void ApplyAttributes(IEnumerable attributes) + public void ApplyAttributes(IEnumerable attributes) + { + if (attributes.Any()) { - if (attributes.Any()) + foreach (var attribute in attributes) { - foreach (var attribute in attributes) + if (attribute is ExtensionAttribute extension) { - if (attribute is ExtensionAttribute extension) - { - extension.ApplyExtension(this); - } - else - { - var handler = Schema.GetAttributeHandlerFor(attribute.GetType()); - handler?.ApplyExtension(this, attribute); - } + extension.ApplyExtension(this); + } + else + { + var handler = Schema.GetAttributeHandlerFor(attribute.GetType()); + handler?.ApplyExtension(this, attribute); } } } + } - /// - /// Search for a field by name. Use HasField() to check if field exists. - /// - /// Field name. Case sensitive - /// Current request context. Used by EntityGraphQL when compiling queries. If are calling this during schema configure, you can pass null - /// The field object for further configuration - /// - /// If field if not found - public IField GetField(string identifier, QueryRequestContext? requestContext) + /// + /// Search for a field by name. Use HasField() to check if field exists. + /// + /// Field name. Case sensitive + /// Current request context. Used by EntityGraphQL when compiling queries. If are calling this during schema configure, you can pass null + /// The field object for further configuration + /// + /// If field if not found + public IField GetField(string identifier, QueryRequestContext? requestContext) + { + if (FieldsByName.TryGetValue(identifier, out var field)) { - if (FieldsByName.TryGetValue(identifier, out var field)) - { - if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.RequiredAuthorization)) - throw new EntityGraphQLAccessException($"You are not authorized to access the '{identifier}' field on type '{Name}'."); - if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.ReturnType.SchemaType.RequiredAuthorization)) - throw new EntityGraphQLAccessException($"You are not authorized to access the '{field.ReturnType.SchemaType.Name}' type returned by field '{identifier}'."); - - return FieldsByName[identifier]; - } + if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.RequiredAuthorization)) + throw new EntityGraphQLAccessException($"You are not authorized to access the '{identifier}' field on type '{Name}'."); + if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.ReturnType.SchemaType.RequiredAuthorization)) + throw new EntityGraphQLAccessException($"You are not authorized to access the '{field.ReturnType.SchemaType.Name}' type returned by field '{identifier}'."); - throw new EntityGraphQLCompilerException($"Field '{identifier}' not found on type '{Name}'"); + return FieldsByName[identifier]; } - public bool GetField(string identifier, QueryRequestContext? requestContext, out IField? field) - { - if (HasField(identifier, requestContext)) - { - field = (Field)GetField(identifier, requestContext); - return true; - } - field = null; - return false; - } + throw new EntityGraphQLCompilerException($"Field '{identifier}' not found on type '{Name}'"); + } - /// - /// Return all the fields defined on this type - /// - /// List of Field objects - public IEnumerable GetFields() + public bool GetField(string identifier, QueryRequestContext? requestContext, out IField? field) + { + if (HasField(identifier, requestContext)) { - return FieldsByName.Values.Cast(); + field = (Field)GetField(identifier, requestContext); + return true; } + field = null; + return false; + } - /// - /// Checks if this type has a field with the given name - /// - /// Field name. Case sensitive - /// - public bool HasField(string identifier, QueryRequestContext? requestContext) - { - if (FieldsByName.TryGetValue(identifier, out var field)) - { - if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.RequiredAuthorization)) - return false; + /// + /// Return all the fields defined on this type + /// + /// List of Field objects + public IEnumerable GetFields() + { + return FieldsByName.Values.Cast(); + } - return true; - } + /// + /// Checks if this type has a field with the given name + /// + /// Field name. Case sensitive + /// + public bool HasField(string identifier, QueryRequestContext? requestContext) + { + if (FieldsByName.TryGetValue(identifier, out var field)) + { + if (requestContext != null && !requestContext.AuthorizationService.IsAuthorized(requestContext.User, field.RequiredAuthorization)) + return false; - return false; + return true; } - public abstract ISchemaType AddAllFields(SchemaBuilderOptions? options = null); + return false; + } + + public abstract ISchemaType AddAllFields(SchemaBuilderOptions? options = null); - public void AddFields(IEnumerable fields) + public void AddFields(IEnumerable fields) + { + foreach (var f in fields) { - foreach (var f in fields) - { - AddField(f); - } + AddField(f); } + } - public IField AddField(IField field) - { - if (FieldsByName.ContainsKey(field.Name)) - throw new EntityQuerySchemaException($"Field '{field.Name}' already exists on type '{this.Name}'. Use ReplaceField() if this is intended."); + public IField AddField(IField field) + { + if (FieldsByName.ContainsKey(field.Name)) + throw new EntityQuerySchemaException($"Field '{field.Name}' already exists on type '{this.Name}'. Use ReplaceField() if this is intended."); - OnAddField(field); + OnAddField(field); - FieldsByName.Add(field.Name, (TFieldType)field); - return field; - } + FieldsByName.Add(field.Name, (TFieldType)field); + return field; + } - /// - /// Remove a field by the given name. Case sensitive. If the field does not exist, nothing happens. - /// - /// - public void RemoveField(string name) - { - FieldsByName.Remove(name); - } + /// + /// Remove a field by the given name. Case sensitive. If the field does not exist, nothing happens. + /// + /// + public void RemoveField(string name) + { + FieldsByName.Remove(name); + } - public ISchemaType AddDirective(ISchemaDirective directive) + public ISchemaType AddDirective(ISchemaDirective directive) + { + if ( + (GqlType == GqlTypes.Scalar && !directive.Location.Contains(TypeSystemDirectiveLocation.Scalar)) + || (GqlType == GqlTypes.QueryObject && !directive.Location.Contains(TypeSystemDirectiveLocation.QueryObject)) + || (GqlType == GqlTypes.Interface && !directive.Location.Contains(TypeSystemDirectiveLocation.Interface)) + || (GqlType == GqlTypes.Enum && !directive.Location.Contains(TypeSystemDirectiveLocation.Enum)) + || (GqlType == GqlTypes.InputObject && !directive.Location.Contains(TypeSystemDirectiveLocation.InputObject)) + || (GqlType == GqlTypes.Union && !directive.Location.Contains(TypeSystemDirectiveLocation.Union)) + ) { - if ( - (GqlType == GqlTypes.Scalar && !directive.Location.Contains(TypeSystemDirectiveLocation.Scalar)) - || (GqlType == GqlTypes.QueryObject && !directive.Location.Contains(TypeSystemDirectiveLocation.QueryObject)) - || (GqlType == GqlTypes.Interface && !directive.Location.Contains(TypeSystemDirectiveLocation.Interface)) - || (GqlType == GqlTypes.Enum && !directive.Location.Contains(TypeSystemDirectiveLocation.Enum)) - || (GqlType == GqlTypes.InputObject && !directive.Location.Contains(TypeSystemDirectiveLocation.InputObject)) - || (GqlType == GqlTypes.Union && !directive.Location.Contains(TypeSystemDirectiveLocation.Union)) - ) - { - throw new EntityQuerySchemaException($"{TypeDotnet.Name} marked with {directive.GetType().Name} directive which is not valid on a {GqlType}"); - } + throw new EntityQuerySchemaException($"{TypeDotnet.Name} marked with {directive.GetType().Name} directive which is not valid on a {GqlType}"); + } - directives.Add(directive); + directives.Add(directive); - return this; - } + return this; + } - public void Validate(object? value) - { - OnValidate(value); - } + public void Validate(object? value) + { + OnValidate(value); + } - public abstract ISchemaType ImplementAllBaseTypes(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); + public abstract ISchemaType ImplementAllBaseTypes(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); #pragma warning disable CA1716 - public abstract ISchemaType Implements(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); - public abstract ISchemaType Implements(string typeName); + public abstract ISchemaType Implements(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); + public abstract ISchemaType Implements(string typeName); #pragma warning restore CA1716 - } } diff --git a/src/EntityGraphQL/Schema/ControllerType.cs b/src/EntityGraphQL/Schema/ControllerType.cs index 6a78a333..0ce541bc 100644 --- a/src/EntityGraphQL/Schema/ControllerType.cs +++ b/src/EntityGraphQL/Schema/ControllerType.cs @@ -35,9 +35,9 @@ public ControllerType AddFrom(SchemaBuilderOptions? options = null) foreach (Type type in types) { var classLevelRequiredAuth = SchemaType.Schema.AuthorizationService.GetRequiredAuthFromType(type); - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { - var attribute = method.GetCustomAttribute(typeof(GraphQLMethodAttribute)) as GraphQLMethodAttribute; + var attribute = method.GetCustomAttribute(); if (attribute != null || options.AddNonAttributedMethodsInControllers) { string name = SchemaType.Schema.SchemaFieldNamer(method.Name); @@ -67,7 +67,7 @@ public BaseField Add(Delegate @delegate, SchemaBuilderOptions? options = null) /// Options for the schema builder public BaseField Add(string fieldName, Delegate @delegate, SchemaBuilderOptions? options = null) { - var description = (@delegate.Method.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute)?.Description ?? string.Empty; + var description = @delegate.Method.GetCustomAttribute()?.Description ?? string.Empty; return Add(fieldName, description, @delegate, options); } @@ -86,7 +86,7 @@ public BaseField Add(string fieldName, string description, Delegate @delegate, S private BaseField AddMethodAsField(string name, RequiredAuthorization? classLevelRequiredAuth, MethodInfo method, string? description, SchemaBuilderOptions? options) { options ??= new SchemaBuilderOptions(); - var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); + var isAsync = method.GetCustomAttribute() != null || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); var methodAuth = SchemaType.Schema.AuthorizationService.GetRequiredAuthFromMember(method); var requiredClaims = methodAuth; if (classLevelRequiredAuth != null) diff --git a/src/EntityGraphQL/Schema/Directives/DeprecatedDirective.cs b/src/EntityGraphQL/Schema/Directives/DeprecatedDirective.cs index 1763e6e0..ce80d76b 100644 --- a/src/EntityGraphQL/Schema/Directives/DeprecatedDirective.cs +++ b/src/EntityGraphQL/Schema/Directives/DeprecatedDirective.cs @@ -43,7 +43,7 @@ public DeprecatedDirective(string? reason = null) TypeSystemDirectiveLocation.FieldDefinition, TypeSystemDirectiveLocation.ArgumentDefinition, TypeSystemDirectiveLocation.InputFieldDefinition, - TypeSystemDirectiveLocation.EnumValue + TypeSystemDirectiveLocation.EnumValue, }; public void ProcessField(Models.Field field) diff --git a/src/EntityGraphQL/Schema/Directives/ISchemaDirective.cs b/src/EntityGraphQL/Schema/Directives/ISchemaDirective.cs index dbb497b7..b8734ae3 100644 --- a/src/EntityGraphQL/Schema/Directives/ISchemaDirective.cs +++ b/src/EntityGraphQL/Schema/Directives/ISchemaDirective.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; -namespace EntityGraphQL.Schema.Directives +namespace EntityGraphQL.Schema.Directives; + +public interface ISchemaDirective { - public interface ISchemaDirective - { - IEnumerable Location { get; } + IEnumerable Location { get; } - void ProcessField(Models.Field field) { } - void ProcessType(Models.TypeElement type) { } - void ProcessEnumValue(Models.EnumValue enumValue) { } + void ProcessField(Models.Field field) { } + void ProcessType(Models.TypeElement type) { } + void ProcessEnumValue(Models.EnumValue enumValue) { } - string ToGraphQLSchemaString(); - } + string ToGraphQLSchemaString(); } diff --git a/src/EntityGraphQL/Schema/Directives/SchemaDirectiveExtensions.cs b/src/EntityGraphQL/Schema/Directives/SchemaDirectiveExtensions.cs index 156f117b..90cb5b13 100644 --- a/src/EntityGraphQL/Schema/Directives/SchemaDirectiveExtensions.cs +++ b/src/EntityGraphQL/Schema/Directives/SchemaDirectiveExtensions.cs @@ -1,32 +1,31 @@ using System.Collections.Generic; using EntityGraphQL.Schema.Directives; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public static class SchemaDirectiveExtensions { - public static class SchemaDirectiveExtensions + public static void ProcessField(this IEnumerable directives, Models.Field field) { - public static void ProcessField(this IEnumerable directives, Models.Field field) + foreach (var directive in directives) { - foreach (var directive in directives) - { - directive.ProcessField(field); - } + directive.ProcessField(field); } + } - public static void ProcessType(this IEnumerable directives, Models.TypeElement type) + public static void ProcessType(this IEnumerable directives, Models.TypeElement type) + { + foreach (var directive in directives) { - foreach (var directive in directives) - { - directive.ProcessType(type); - } + directive.ProcessType(type); } + } - public static void ProcessEnumValue(this IEnumerable directives, Models.EnumValue enumValue) + public static void ProcessEnumValue(this IEnumerable directives, Models.EnumValue enumValue) + { + foreach (var directive in directives) { - foreach (var directive in directives) - { - directive.ProcessEnumValue(enumValue); - } + directive.ProcessEnumValue(enumValue); } } } diff --git a/src/EntityGraphQL/Schema/Directives/TypeSystemDirectiveLocation.cs b/src/EntityGraphQL/Schema/Directives/TypeSystemDirectiveLocation.cs index 5bcfa460..50629602 100644 --- a/src/EntityGraphQL/Schema/Directives/TypeSystemDirectiveLocation.cs +++ b/src/EntityGraphQL/Schema/Directives/TypeSystemDirectiveLocation.cs @@ -1,17 +1,16 @@ -namespace EntityGraphQL.Schema.Directives +namespace EntityGraphQL.Schema.Directives; + +public enum TypeSystemDirectiveLocation { - public enum TypeSystemDirectiveLocation - { - Schema, - Scalar, - QueryObject, - FieldDefinition, - ArgumentDefinition, - Interface, - Union, - Enum, - EnumValue, - InputObject, - InputFieldDefinition - } + Schema, + Scalar, + QueryObject, + FieldDefinition, + ArgumentDefinition, + Interface, + Union, + Enum, + EnumValue, + InputObject, + InputFieldDefinition, } diff --git a/src/EntityGraphQL/Schema/EntityQuerySchemaException.cs b/src/EntityGraphQL/Schema/EntityQuerySchemaException.cs index 0bf72b4a..0f9c58d6 100644 --- a/src/EntityGraphQL/Schema/EntityQuerySchemaException.cs +++ b/src/EntityGraphQL/Schema/EntityQuerySchemaException.cs @@ -1,15 +1,12 @@ using System; -namespace EntityGraphQL.Schema -{ - public class EntityQuerySchemaException : Exception - { - public EntityQuerySchemaException() { } +namespace EntityGraphQL.Schema; - public EntityQuerySchemaException(string message) - : base(message) { } +public class EntityQuerySchemaException : Exception +{ + public EntityQuerySchemaException(string message) + : base(message) { } - public EntityQuerySchemaException(string message, Exception innerException) - : base(message, innerException) { } - } + public EntityQuerySchemaException(string message, Exception innerException) + : base(message, innerException) { } } diff --git a/src/EntityGraphQL/Schema/ExtensionAttribute.cs b/src/EntityGraphQL/Schema/ExtensionAttribute.cs index 1dea793c..91b27cc7 100644 --- a/src/EntityGraphQL/Schema/ExtensionAttribute.cs +++ b/src/EntityGraphQL/Schema/ExtensionAttribute.cs @@ -1,56 +1,52 @@ using System; using System.Collections.Generic; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public abstract class ExtensionAttribute : Attribute { - public abstract class ExtensionAttribute : Attribute - { - public virtual void ApplyExtension(IField field) { } + public virtual void ApplyExtension(IField field) { } - public virtual void ApplyExtension(ISchemaType type) { } - } + public virtual void ApplyExtension(ISchemaType type) { } +} - /// - /// Used to handle other Attributes found in the schema where you can not extend from ExtensionAttribute - /// - /// - public abstract class AbstractExtensionAttributeHandler : IExtensionAttributeHandler - where TAttribute : Attribute +/// +/// Used to handle other Attributes found in the schema where you can not extend from ExtensionAttribute +/// +/// +public abstract class AbstractExtensionAttributeHandler : IExtensionAttributeHandler + where TAttribute : Attribute +{ + public IEnumerable AttributeTypes => new List { typeof(TAttribute) }; + + public virtual void ApplyExtension(IField field, Attribute attribute) { - public IEnumerable AttributeTypes - { - get => new List { typeof(TAttribute) }; - } - - public virtual void ApplyExtension(IField field, Attribute attribute) - { - if (attribute is TAttribute tAttribute) - ApplyExtension(field, tAttribute); - else - throw new ArgumentException($"Attribute must be of type {typeof(TAttribute).Name}"); - } - - public virtual void ApplyExtension(ISchemaType type, Attribute attribute) - { - if (attribute is TAttribute tAttribute) - ApplyExtension(type, tAttribute); - else - throw new ArgumentException($"Attribute must be of type {typeof(TAttribute).Name}"); - } - - public virtual void ApplyExtension(IField field, TAttribute attribute) { } - - public virtual void ApplyExtension(ISchemaType type, TAttribute attribute) { } + if (attribute is TAttribute tAttribute) + ApplyExtension(field, tAttribute); + else + throw new ArgumentException($"Attribute must be of type {typeof(TAttribute).Name}"); } - public interface IExtensionAttributeHandler + public virtual void ApplyExtension(ISchemaType type, Attribute attribute) { - /// - /// List of Attribute types this handler can handle - /// - IEnumerable AttributeTypes { get; } - - public void ApplyExtension(IField field, Attribute attribute); - public void ApplyExtension(ISchemaType type, Attribute attribute); + if (attribute is TAttribute tAttribute) + ApplyExtension(type, tAttribute); + else + throw new ArgumentException($"Attribute must be of type {typeof(TAttribute).Name}"); } + + public virtual void ApplyExtension(IField field, TAttribute attribute) { } + + public virtual void ApplyExtension(ISchemaType type, TAttribute attribute) { } +} + +public interface IExtensionAttributeHandler +{ + /// + /// List of Attribute types this handler can handle + /// + IEnumerable AttributeTypes { get; } + + public void ApplyExtension(IField field, Attribute attribute); + public void ApplyExtension(ISchemaType type, Attribute attribute); } diff --git a/src/EntityGraphQL/Schema/Field.cs b/src/EntityGraphQL/Schema/Field.cs index bc0d337b..659680cb 100644 --- a/src/EntityGraphQL/Schema/Field.cs +++ b/src/EntityGraphQL/Schema/Field.cs @@ -5,7 +5,6 @@ using EntityGraphQL.Compiler; using EntityGraphQL.Compiler.Util; using EntityGraphQL.Extensions; -using EntityGraphQL.Schema.FieldExtensions; namespace EntityGraphQL.Schema; @@ -76,7 +75,7 @@ public Field( : base(schema, fromType, name, description, returnType) { RequiredAuthorization = requiredAuth; - Extensions = new List(); + Extensions = []; if (resolve != null) { @@ -114,7 +113,7 @@ public Field( : base(schema, fromType, name, description, returnType) { RequiredAuthorization = requiredAuth; - Extensions = new List(); + Extensions = []; if (resolve != null) { @@ -133,7 +132,7 @@ public Field( /// /// /// - public Field IsNullable(bool nullable) + public new Field IsNullable(bool nullable) { ReturnType.TypeNotNullable = !nullable; diff --git a/src/EntityGraphQL/Schema/FieldExtensions/BaseFieldExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/BaseFieldExtension.cs index 247ab584..f0a99ca0 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/BaseFieldExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/BaseFieldExtension.cs @@ -43,7 +43,6 @@ public virtual Expression GetListExpressionForBulkResolve(Expression listExpress /// Called when the field is being finalized for execution but we have not yet created a new {} expression for the select. /// Not called for GraphQLFieldType.Scalar /// - /// Type of field being built. ListSelection or ObjectProjection /// Scalar: the expression. ListSelection: The expression used to add .Select() to. ObjectProjection: the base expression which fields are selected from /// Scalar: null. ListSelection: The selection fields used in .Select(). ObjectProjection: The fields used in the new { field1 = ..., field2 = ... } /// diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/Connection.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/Connection.cs index bbcc28ed..8785c6c1 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/Connection.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/Connection.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; using System.ComponentModel; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class Connection { - public class Connection + public Connection(int totalCount, dynamic arguments) { - public Connection(int totalCount, dynamic arguments) - { - TotalCount = totalCount; - PageInfo = new ConnectionPageInfo(totalCount, arguments); - arguments.TotalCount = totalCount; - } + TotalCount = totalCount; + PageInfo = new ConnectionPageInfo(totalCount, arguments); + arguments.TotalCount = totalCount; + } - [GraphQLNotNull] - [Description("Edge information about each node in the collection")] - public IEnumerable> Edges { get; set; } = new List>(); + [GraphQLNotNull] + [Description("Edge information about each node in the collection")] + public IEnumerable> Edges { get; set; } = new List>(); - [GraphQLNotNull] - [Description("Total count of items in the collection")] - public int TotalCount { get; set; } + [GraphQLNotNull] + [Description("Total count of items in the collection")] + public int TotalCount { get; set; } - [GraphQLNotNull] - [Description("Information about this page of data")] - public ConnectionPageInfo PageInfo { get; set; } - } + [GraphQLNotNull] + [Description("Information about this page of data")] + public ConnectionPageInfo PageInfo { get; set; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionArgs.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionArgs.cs index 90b9a9b7..18587215 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionArgs.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionArgs.cs @@ -1,23 +1,22 @@ -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class ConnectionArgs { - public class ConnectionArgs - { - // forward pagination - public int? First { get; set; } - public string? After { get; set; } + // forward pagination + public int? First { get; set; } + public string? After { get; set; } - [GraphQLIgnore] - public int? AfterNum { get; set; } = null; + [GraphQLIgnore] + public int? AfterNum { get; set; } = null; - // backward pagination - public int? Last { get; set; } - public string? Before { get; set; } + // backward pagination + public int? Last { get; set; } + public string? Before { get; set; } - [GraphQLIgnore] - public int? BeforeNum { get; set; } = null; + [GraphQLIgnore] + public int? BeforeNum { get; set; } = null; - // On creation of Connection<> we store the total count here to avoid having to execute it multiple times - [GraphQLIgnore] - public int TotalCount { get; set; } - } + // On creation of Connection<> we store the total count here to avoid having to execute it multiple times + [GraphQLIgnore] + public int TotalCount { get; set; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdge.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdge.cs index 4fef2d83..9f982c63 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdge.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdge.cs @@ -1,15 +1,14 @@ using System.ComponentModel; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class ConnectionEdge { - public class ConnectionEdge - { - [GraphQLNotNull] - [Description("The item of the collection")] - public TEntity? Node { get; set; } + [GraphQLNotNull] + [Description("The item of the collection")] + public TEntity? Node { get; set; } - [GraphQLNotNull] - [Description("The cursor for this items position within the collection")] - public string? Cursor { get; set; } - } + [GraphQLNotNull] + [Description("The cursor for this items position within the collection")] + public string? Cursor { get; set; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdgeExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdgeExtension.cs index b625bdb6..6b8026b3 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdgeExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionEdgeExtension.cs @@ -102,8 +102,13 @@ CompileContext compileContext expression, Expression.Call(typeof(ConnectionHelper), nameof(ConnectionHelper.GetSkipNumber), null, argumentParam, Expression.Constant(true)) ), - Expression.Call(typeof(ConnectionHelper), nameof(ConnectionHelper.GetTakeNumber), null, argumentParam, - Expression.Call(typeof(ConnectionHelper), nameof(ConnectionHelper.GetSkipNumber), null, argumentParam, Expression.Constant(false))) + Expression.Call( + typeof(ConnectionHelper), + nameof(ConnectionHelper.GetTakeNumber), + null, + argumentParam, + Expression.Call(typeof(ConnectionHelper), nameof(ConnectionHelper.GetSkipNumber), null, argumentParam, Expression.Constant(false)) + ) ); // we have moved the expression from the parent node to here. We need to call the before callback @@ -189,7 +194,7 @@ ParameterReplacer parameterReplacer new List { Expression.Bind(edgeType.GetProperty("Node")!, Expression.PropertyOrField(edgeParam, "Node")), - Expression.Bind(edgeType.GetProperty("Cursor")!, Expression.Call(typeof(ConnectionHelper), "GetCursor", null, argumentParam, idxParam, offsetParam)) + Expression.Bind(edgeType.GetProperty("Cursor")!, Expression.Call(typeof(ConnectionHelper), "GetCursor", null, argumentParam, idxParam, offsetParam)), } ), edgeParam, diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionHelper.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionHelper.cs index 49035429..2419edc1 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionHelper.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionHelper.cs @@ -3,116 +3,115 @@ using System.Buffers.Text; using System.Text; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public static class ConnectionHelper { - public static class ConnectionHelper + /// + /// Serialize an index/row number into base64 + /// + /// + public static unsafe string SerializeCursor(int index) { - /// - /// Serialize an index/row number into base64 - /// - /// - /// - public static unsafe string SerializeCursor(int index) - { - // resuts in less allocations - const int totalUtf8Bytes = 4 * (20 / 3); - Span resultSpan = stackalloc byte[totalUtf8Bytes]; - if (!Utf8Formatter.TryFormat(index, resultSpan, out int writtenBytes)) - throw new ArithmeticException(); + // resuts in less allocations + const int totalUtf8Bytes = 4 * (20 / 3); + Span resultSpan = stackalloc byte[totalUtf8Bytes]; + if (!Utf8Formatter.TryFormat(index, resultSpan, out int writtenBytes)) + throw new ArithmeticException(); - if (OperationStatus.Done != Base64.EncodeToUtf8InPlace(resultSpan, writtenBytes, out writtenBytes)) - throw new ArithmeticException(); + if (OperationStatus.Done != Base64.EncodeToUtf8InPlace(resultSpan, writtenBytes, out writtenBytes)) + throw new ArithmeticException(); - fixed (byte* bytePtr = resultSpan) - { - var base64String = Encoding.UTF8.GetString(bytePtr, writtenBytes); - return base64String; - } + fixed (byte* bytePtr = resultSpan) + { + var base64String = Encoding.UTF8.GetString(bytePtr, writtenBytes); + return base64String; } + } - /// - /// Deserialize a base64 string index/row number into a an int - /// - /// - /// - public static unsafe int? DeserializeCursor(ReadOnlySpan after) - { - if (after == null || after.IsEmpty) - return null; + /// + /// Deserialize a base64 string index/row number into a an int + /// + /// + /// + public static unsafe int? DeserializeCursor(ReadOnlySpan after) + { + if (after.IsEmpty) + return null; - fixed (char* charPtr = after) - { - var count = Encoding.UTF8.GetByteCount(charPtr, after.Length); + fixed (char* charPtr = after) + { + var count = Encoding.UTF8.GetByteCount(charPtr, after.Length); - Span buffer = stackalloc byte[count]; + Span buffer = stackalloc byte[count]; - fixed (byte* bytePtr = buffer) - { - Encoding.UTF8.GetBytes(charPtr, after.Length, bytePtr, buffer.Length); - } + fixed (byte* bytePtr = buffer) + { + Encoding.UTF8.GetBytes(charPtr, after.Length, bytePtr, buffer.Length); + } - if (OperationStatus.Done != Base64.DecodeFromUtf8InPlace(buffer, out int writtenBytes)) - throw new ArithmeticException(); + if (OperationStatus.Done != Base64.DecodeFromUtf8InPlace(buffer, out int writtenBytes)) + throw new ArithmeticException(); - if (!Utf8Parser.TryParse(buffer[..writtenBytes], out int index, out _)) - throw new ArithmeticException(); + if (!Utf8Parser.TryParse(buffer[..writtenBytes], out int index, out _)) + throw new ArithmeticException(); - return index; - } + return index; } + } - /// - /// Used at runtime in the expression built above - /// - public static string GetCursor(dynamic arguments, int idx, int? offset = null) + /// + /// Used at runtime in the expression built above + /// + public static string GetCursor(dynamic arguments, int idx, int? offset = null) + { + var index = idx + 1; + if (arguments.AfterNum != null) + index += arguments.AfterNum; + if (arguments.Last != null) { - var index = idx + 1; - if (arguments.AfterNum != null) - index += arguments.AfterNum; - if (arguments.Last != null) - { - if (arguments.BeforeNum != null) - index = arguments.BeforeNum - arguments.Last + idx; - else - index += arguments.TotalCount - (arguments.Last ?? 0); - } + if (arguments.BeforeNum != null) + index = arguments.BeforeNum - arguments.Last + idx; + else + index += arguments.TotalCount - (arguments.Last ?? 0); + } - if (offset < 0) index = idx + 1; + if (offset < 0) + index = idx + 1; - return SerializeCursor(index); - } + return SerializeCursor(index); + } - /// - /// Used at runtime in the expression built above - /// - public static int? GetSkipNumber(dynamic arguments, bool fixNegativeOffset = true) + /// + /// Used at runtime in the expression built above + /// + public static int? GetSkipNumber(dynamic arguments, bool fixNegativeOffset = true) + { + if (arguments.AfterNum != null) + return arguments.AfterNum; + if (arguments.Last != null) { - if (arguments.AfterNum != null) - return arguments.AfterNum; - if (arguments.Last != null) - { - var c = ((arguments.BeforeNum-1) ?? arguments.TotalCount) - arguments.Last; - - // Enumerable.Skip does not accept negative numbers. - if (fixNegativeOffset) - c = c > 0 ? c : 0; - return c; - } - return 0; + var c = ((arguments.BeforeNum - 1) ?? arguments.TotalCount) - arguments.Last; + + // Enumerable.Skip does not accept negative numbers. + if (fixNegativeOffset) + c = c > 0 ? c : 0; + return c; } + return 0; + } - /// - /// Used at runtime in the expression built above - /// - public static int? GetTakeNumber(dynamic arguments, int? offset = 0) - { - if (arguments.First == null && arguments.Last == null && arguments.BeforeNum == null || offset == null) - return null; + /// + /// Used at runtime in the expression built above + /// + public static int? GetTakeNumber(dynamic arguments, int? offset = 0) + { + if (arguments.First == null && arguments.Last == null && arguments.BeforeNum == null || offset == null) + return null; - // In cases where we have Last > BeforeNum, we need to take fewer results than Last says to - // See SkipTakeTests.TestLastAndBefore_WhenLastGreaterThanBeforeNum - var offsetAdjustedLast = offset >= 0 ? arguments.Last : arguments.Last + offset; - return arguments.First ?? offsetAdjustedLast ?? (arguments.BeforeNum - 1); - } + // In cases where we have Last > BeforeNum, we need to take fewer results than Last says to + // See SkipTakeTests.TestLastAndBefore_WhenLastGreaterThanBeforeNum + var offsetAdjustedLast = offset >= 0 ? arguments.Last : arguments.Last + offset; + return arguments.First ?? offsetAdjustedLast ?? (arguments.BeforeNum - 1); } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPageInfo.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPageInfo.cs index 1e9572b2..7e9c46d0 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPageInfo.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPageInfo.cs @@ -1,59 +1,55 @@ using System; using System.ComponentModel; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class ConnectionPageInfo { - public class ConnectionPageInfo + private readonly int totalCount; + private readonly dynamic arguments; + + public ConnectionPageInfo(int totalCount, dynamic arguments) { - private readonly int totalCount; - private readonly dynamic arguments; + this.totalCount = totalCount; + this.arguments = arguments; + } - public ConnectionPageInfo(int totalCount, dynamic arguments) + [GraphQLNotNull] + [Description("Last cursor in the page. Use this as the next from argument")] + public string EndCursor + { + get { - this.totalCount = totalCount; - this.arguments = arguments; - } + var idx = totalCount; + if (arguments.AfterNum != null && arguments.First != null) + idx = Math.Min(totalCount, arguments.AfterNum + arguments.First); + else if (arguments.First != null) + idx = arguments.First; + else if (arguments.BeforeNum != null) + idx = arguments.BeforeNum - 1; - [GraphQLNotNull] - [Description("Last cursor in the page. Use this as the next from argument")] - public string EndCursor - { - get - { - var idx = totalCount; - if (arguments.AfterNum != null && arguments.First != null) - idx = Math.Min(totalCount, arguments.AfterNum + arguments.First); - else if (arguments.First != null) - idx = arguments.First; - else if (arguments.BeforeNum != null) - idx = arguments.BeforeNum - 1; - - return ConnectionHelper.SerializeCursor(idx); - } + return ConnectionHelper.SerializeCursor(idx); } + } - [GraphQLNotNull] - [Description("Start cursor in the page. Use this to go backwards with the before argument")] - public string StartCursor + [GraphQLNotNull] + [Description("Start cursor in the page. Use this to go backwards with the before argument")] + public string StartCursor + { + get { - get - { - var idx = 1; - if (arguments.AfterNum != null) - idx = arguments.AfterNum + 1; - else if (arguments.Last != null) - idx = Math.Max((arguments.BeforeNum ?? (totalCount + 1)) - arguments.Last, 1); - return ConnectionHelper.SerializeCursor(idx); - } + var idx = 1; + if (arguments.AfterNum != null) + idx = arguments.AfterNum + 1; + else if (arguments.Last != null) + idx = Math.Max((arguments.BeforeNum ?? (totalCount + 1)) - arguments.Last, 1); + return ConnectionHelper.SerializeCursor(idx); } + } - [Description("If there is more data after this page")] - public bool HasNextPage => arguments.First != null ? ((arguments.AfterNum ?? 0) + arguments.First) < totalCount : arguments.BeforeNum < totalCount; + [Description("If there is more data after this page")] + public bool HasNextPage => arguments.First != null ? ((arguments.AfterNum ?? 0) + arguments.First) < totalCount : arguments.BeforeNum < totalCount; - [Description("If there is data previous to this page")] - public bool HasPreviousPage - { - get { return (arguments.AfterNum ?? 0) > 0 || (arguments.BeforeNum ?? totalCount) - (arguments.Last ?? totalCount) > 1; } - } - } + [Description("If there is data previous to this page")] + public bool HasPreviousPage => (arguments.AfterNum ?? 0) > 0 || (arguments.BeforeNum ?? totalCount) - (arguments.Last ?? totalCount) > 1; } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPagingExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPagingExtension.cs index d2b14f12..46f169a4 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPagingExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/ConnectionPagingExtension.cs @@ -6,176 +6,175 @@ using EntityGraphQL.Compiler.Util; using EntityGraphQL.Extensions; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +/// +/// Sets up a few extensions to modify a simple collection expression - db.Movies.OrderBy() into a connection paging graph +/// +public class ConnectionPagingExtension : BaseFieldExtension { + private Type? listType; + private bool isQueryable; + private Type? returnType; + + public Expression? OriginalFieldExpression { get; private set; } + public int? DefaultPageSize { get; } + public int? MaxPageSize { get; } + public List ExtensionsBeforePaging { get; private set; } = []; + + public ConnectionPagingExtension(int? defaultPageSize, int? maxPageSize) + { + DefaultPageSize = defaultPageSize; + MaxPageSize = maxPageSize; + } + /// - /// Sets up a few extensions to modify a simple collection expression - db.Movies.OrderBy() into a connection paging graph + /// Configure the field for a connection style paging field. Do as much as we can here as it is only executed once. + /// + /// There are a few fun things happening. + /// + /// 1. In this extension we set up the field with the Connection object graph using the constructor to implement most + /// of the fields + /// 2. We set up an extension on this field.edges.node to capture the selection from the compiled query as node is the + /// they are selecting fields from + /// 3. We set up an extension of field.edges which using data from this extension (we get the context and the args) and the + /// field.edges.node Select() to build a EF compatible expression that only returns the fields asked for in edges.node /// - public class ConnectionPagingExtension : BaseFieldExtension + /// + /// + public override void Configure(ISchemaProvider schema, IField field) { - private Type? listType; - private bool isQueryable; - private Type? returnType; + if (field.ResolveExpression == null) + throw new EntityGraphQLCompilerException($"ConnectionPagingExtension requires a Resolve function set on the field"); + + if (!field.ResolveExpression.Type.IsEnumerableOrArray()) + throw new ArgumentException($"Expression for field {field.Name} must be a collection to use ConnectionPagingExtension. Found type {field.ReturnType.TypeDotnet}"); - public Expression? OriginalFieldExpression { get; private set; } - public int? DefaultPageSize { get; } - public int? MaxPageSize { get; } - public List ExtensionsBeforePaging { get; private set; } = []; + // Make sure required types are in the schema + if (!schema.HasType(typeof(ConnectionPageInfo))) + schema.AddType("PageInfo", "Metadata about a page of data").AddAllFields(); + listType = field.ReturnType.TypeDotnet.GetEnumerableOrArrayType()!; + isQueryable = typeof(IQueryable).IsAssignableFrom(field.ReturnType.TypeDotnet); - public ConnectionPagingExtension(int? defaultPageSize, int? maxPageSize) + var edgeType = typeof(ConnectionEdge<>).MakeGenericType(listType); + if (!schema.HasType(edgeType)) { - DefaultPageSize = defaultPageSize; - MaxPageSize = maxPageSize; + var edgeName = $"{field.ReturnType.SchemaType.Name}Edge"; + schema.AddType(edgeType, edgeName, "Metadata about an edge of page result").AddAllFields(); } - /// - /// Configure the field for a connection style paging field. Do as much as we can here as it is only executed once. - /// - /// There are a few fun things happening. - /// - /// 1. In this extension we set up the field with the Connection object graph using the constructor to implement most - /// of the fields - /// 2. We set up an extension on this field.edges.node to capture the selection from the compiled query as node is the - /// they are selecting fields from - /// 3. We set up an extension of field.edges which using data from this extension (we get the context and the args) and the - /// field.edges.node Select() to build a EF compatible expression that only returns the fields asked for in edges.node - /// - /// - /// - public override void Configure(ISchemaProvider schema, IField field) + ISchemaType returnSchemaType; + var connectionType = typeof(Connection<>).MakeGenericType(listType); + var connectionName = $"{field.ReturnType.SchemaType.Name}Connection"; + if (!schema.HasType(connectionType)) { - if (field.ResolveExpression == null) - throw new EntityGraphQLCompilerException($"ConnectionPagingExtension requires a Resolve function set on the field"); - - if (!field.ResolveExpression.Type.IsEnumerableOrArray()) - throw new ArgumentException($"Expression for field {field.Name} must be a collection to use ConnectionPagingExtension. Found type {field.ReturnType.TypeDotnet}"); - - // Make sure required types are in the schema - if (!schema.HasType(typeof(ConnectionPageInfo))) - schema.AddType(typeof(ConnectionPageInfo), "PageInfo", "Metadata about a page of data").AddAllFields(); - listType = field.ReturnType.TypeDotnet.GetEnumerableOrArrayType()!; - isQueryable = typeof(IQueryable).IsAssignableFrom(field.ReturnType.TypeDotnet); - - var edgeType = typeof(ConnectionEdge<>).MakeGenericType(listType); - if (!schema.HasType(edgeType)) - { - var edgeName = $"{field.ReturnType.SchemaType.Name}Edge"; - schema.AddType(edgeType, edgeName, "Metadata about an edge of page result").AddAllFields(); - } - - ISchemaType returnSchemaType; - var connectionType = typeof(Connection<>).MakeGenericType(listType); - var connectionName = $"{field.ReturnType.SchemaType.Name}Connection"; - if (!schema.HasType(connectionType)) - { - returnSchemaType = schema.AddType(connectionType, connectionName, $"Metadata about a {field.ReturnType.SchemaType.Name} connection (paging over people)").AddAllFields(); - } - else - { - returnSchemaType = schema.Type(connectionName); - } - returnType = returnSchemaType.TypeDotnet; - - field.Returns(SchemaBuilder.MakeGraphQlType(schema, false, returnType, connectionName, field.Name, field.FromType)); - - // Update field arguments - field.AddArguments(new ConnectionArgs()); - - // set up Extension on Edges.Node field to handle the Select() insertion - var edgesField = returnSchemaType.GetField(schema.SchemaFieldNamer("Edges"), null); - - // We steal any previous extensions as they were expected to work on the original Resolve which we moved to Edges - ExtensionsBeforePaging = field.Extensions.Take(field.Extensions.FindIndex(e => e is ConnectionPagingExtension)).ToList(); - // the remaining extensions expect to be built from the ConnectionPaging shape - field.Extensions = field.Extensions.Skip(ExtensionsBeforePaging.Count).ToList(); - // We use this extension to update the Edges context by inserting the Select() which we get from the above extension - // if they have 2 fields with the type and paging we don't want to add extension multiple times - // See OffsetPagingTests.TestMultiUseWithArgs - if (!edgesField.Extensions.Any(e => e is ConnectionEdgeExtension)) - edgesField.AddExtension(new ConnectionEdgeExtension(listType, isQueryable)); - - OriginalFieldExpression = field.ResolveExpression; - - // Rebuild expression so all the fields and types are known - // and get it ready for completion at runtime (we need to know the selection fields to complete) - // it is built to reduce redundant repeated expressions. The whole thing ends up in a null check wrap - // conceptually it does similar to below (using Demo context) - // See Connection for implementation details of TotalCount and PageInfo - // (ctx, arguments) => { - // var connection = new Connection(ctx.Actors.Select(a => a.Person) - // -- other extensions might do things here (e.g. filter / sort) - // .Count(), arguments) - // { - // Edges = ctx.Actors.Select(a => a.Person) - // -- other extensions might do things here (e.g. filter / sort) - // .Skip(GetSkipNumber(arguments)) - // .Take(GetTakeNumber(arguments)) - // // we insert Select() here so that we do not fetch the whole table if using EF - // .Select(a => new ConnectionEdge - // { - // Node = new { - // field1 = a.field1, - // ... - // }, - // Cursor = null // built below - // }) - // // this is the select in memory that lets us build the cursors - // .Select((a, idx) => new ConnectionEdge // this is from Enumerable and EF will run the above - // { - // Node = a, - // Cursor = ConnectionHelper.GetCursor(arguments, idx) - // }), - // }; - // if (connection == null) - // return null; - // return .... // does the select of only the Connection fields asked for - // need to set this up here as the types are needed as we visiting the query tree - // we build the real one below in GetExpression() - var totalCountExp = Expression.Call(isQueryable ? typeof(Queryable) : typeof(Enumerable), "Count", [listType], OriginalFieldExpression!); - var argTypes = new List { totalCountExp.Type, field.ArgumentsParameter!.Type }; - var paramsArgs = new List { totalCountExp, field.ArgumentsParameter }; - var fieldExpression = Expression.MemberInit(Expression.New(returnType.GetConstructor(argTypes.ToArray())!, paramsArgs)); - - field.UpdateExpression(fieldExpression); + returnSchemaType = schema.AddType(connectionType, connectionName, $"Metadata about a {field.ReturnType.SchemaType.Name} connection (paging over people)").AddAllFields(); } - - public override Expression? GetExpression( - IField field, - Expression expression, - ParameterExpression? argumentParam, - dynamic? arguments, - Expression context, - IGraphQLNode? parentNode, - bool servicesPass, - ParameterReplacer parameterReplacer - ) + else { - // second pass with services we have the new edges shape. We need to handle things on the EdgeExtension - if (servicesPass) - return expression; + returnSchemaType = schema.Type(connectionName); + } + returnType = returnSchemaType.TypeDotnet; + + field.Returns(SchemaBuilder.MakeGraphQlType(schema, false, returnType, connectionName, field.Name, field.FromType)); + + // Update field arguments + field.AddArguments(new ConnectionArgs()); + + // set up Extension on Edges.Node field to handle the Select() insertion + var edgesField = returnSchemaType.GetField(schema.SchemaFieldNamer("Edges"), null); + + // We steal any previous extensions as they were expected to work on the original Resolve which we moved to Edges + ExtensionsBeforePaging = field.Extensions.Take(field.Extensions.FindIndex(e => e is ConnectionPagingExtension)).ToList(); + // the remaining extensions expect to be built from the ConnectionPaging shape + field.Extensions = field.Extensions.Skip(ExtensionsBeforePaging.Count).ToList(); + // We use this extension to update the Edges context by inserting the Select() which we get from the above extension + // if they have 2 fields with the type and paging we don't want to add extension multiple times + // See OffsetPagingTests.TestMultiUseWithArgs + if (!edgesField.Extensions.Any(e => e is ConnectionEdgeExtension)) + edgesField.AddExtension(new ConnectionEdgeExtension(listType, isQueryable)); + + OriginalFieldExpression = field.ResolveExpression; + + // Rebuild expression so all the fields and types are known + // and get it ready for completion at runtime (we need to know the selection fields to complete) + // it is built to reduce redundant repeated expressions. The whole thing ends up in a null check wrap + // conceptually it does similar to below (using Demo context) + // See Connection for implementation details of TotalCount and PageInfo + // (ctx, arguments) => { + // var connection = new Connection(ctx.Actors.Select(a => a.Person) + // -- other extensions might do things here (e.g. filter / sort) + // .Count(), arguments) + // { + // Edges = ctx.Actors.Select(a => a.Person) + // -- other extensions might do things here (e.g. filter / sort) + // .Skip(GetSkipNumber(arguments)) + // .Take(GetTakeNumber(arguments)) + // // we insert Select() here so that we do not fetch the whole table if using EF + // .Select(a => new ConnectionEdge + // { + // Node = new { + // field1 = a.field1, + // ... + // }, + // Cursor = null // built below + // }) + // // this is the select in memory that lets us build the cursors + // .Select((a, idx) => new ConnectionEdge // this is from Enumerable and EF will run the above + // { + // Node = a, + // Cursor = ConnectionHelper.GetCursor(arguments, idx) + // }), + // }; + // if (connection == null) + // return null; + // return .... // does the select of only the Connection fields asked for + // need to set this up here as the types are needed as we visiting the query tree + // we build the real one below in GetExpression() + var totalCountExp = Expression.Call(isQueryable ? typeof(Queryable) : typeof(Enumerable), "Count", [listType], OriginalFieldExpression!); + var argTypes = new List { totalCountExp.Type, field.ArgumentsParameter!.Type }; + var paramsArgs = new List { totalCountExp, field.ArgumentsParameter }; + var fieldExpression = Expression.MemberInit(Expression.New(returnType.GetConstructor(argTypes.ToArray())!, paramsArgs)); + + field.UpdateExpression(fieldExpression); + } + + public override Expression? GetExpression( + IField field, + Expression expression, + ParameterExpression? argumentParam, + dynamic? arguments, + Expression context, + IGraphQLNode? parentNode, + bool servicesPass, + ParameterReplacer parameterReplacer + ) + { + // second pass with services we have the new edges shape. We need to handle things on the EdgeExtension + if (servicesPass) + return expression; #if NET8_0_OR_GREATER - ArgumentNullException.ThrowIfNull(argumentParam, nameof(argumentParam)); + ArgumentNullException.ThrowIfNull(argumentParam, nameof(argumentParam)); #else - if (argumentParam == null) - throw new ArgumentNullException(nameof(argumentParam)); + if (argumentParam == null) + throw new ArgumentNullException(nameof(argumentParam)); #endif - // totalCountExp gets executed once in the new Connection() {} and we can reuse it - var edgeExpression = OriginalFieldExpression!; + // totalCountExp gets executed once in the new Connection() {} and we can reuse it + var edgeExpression = OriginalFieldExpression!; - if (ExtensionsBeforePaging.Count > 0) + if (ExtensionsBeforePaging.Count > 0) + { + // if we have other extensions (filter etc) we need to apply them to the totalCount + foreach (var extension in ExtensionsBeforePaging) { - // if we have other extensions (filter etc) we need to apply them to the totalCount - foreach (var extension in ExtensionsBeforePaging) - { - edgeExpression = extension.GetExpression(field, edgeExpression, argumentParam, arguments, context, parentNode, servicesPass, parameterReplacer)!; - } + edgeExpression = extension.GetExpression(field, edgeExpression, argumentParam, arguments, context, parentNode, servicesPass, parameterReplacer)!; } - var totalCountExp = Expression.Call(isQueryable ? typeof(Queryable) : typeof(Enumerable), nameof(Enumerable.Count), [listType!], edgeExpression!); - expression = Expression.MemberInit(Expression.New(returnType!.GetConstructor([totalCountExp.Type, argumentParam.Type])!, totalCountExp, argumentParam)); - - return expression; } + var totalCountExp = Expression.Call(isQueryable ? typeof(Queryable) : typeof(Enumerable), nameof(Enumerable.Count), [listType!], edgeExpression!); + expression = Expression.MemberInit(Expression.New(returnType!.GetConstructor([totalCountExp.Type, argumentParam.Type])!, totalCountExp, argumentParam)); + + return expression; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/UseConnectionPagingExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/UseConnectionPagingExtension.cs index 0bf3bd83..fd2a47ae 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/UseConnectionPagingExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/ConnectionPaging/UseConnectionPagingExtension.cs @@ -1,43 +1,42 @@ -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public static class UseConnectionPagingExtension { - public static class UseConnectionPagingExtension + /// + /// Update field to implement paging with the Connection<> classes and metadata. + /// Only call on a field that returns an IEnumerable + /// + /// + /// If no values are passed for first or last arguments. This value will be applied to the first argument + /// If either argument first or last is greater than this value an error is raised + /// + public static IField UseConnectionPaging(this IField field, int? defaultPageSize = null, int? maxPageSize = null) { - /// - /// Update field to implement paging with the Connection<> classes and metadata. - /// Only call on a field that returns an IEnumerable - /// - /// - /// If no values are passed for first or last arguments. This value will be applied to the first argument - /// If either argument first or last is greater than this value an error is raised - /// - public static IField UseConnectionPaging(this IField field, int? defaultPageSize = null, int? maxPageSize = null) - { - field.AddExtension(new ConnectionPagingExtension(defaultPageSize, maxPageSize)); - return field; - } + field.AddExtension(new ConnectionPagingExtension(defaultPageSize, maxPageSize)); + return field; } +} - public class UseConnectionPagingAttribute : ExtensionAttribute - { - public UseConnectionPagingAttribute() { } +public class UseConnectionPagingAttribute : ExtensionAttribute +{ + public UseConnectionPagingAttribute() { } - public UseConnectionPagingAttribute(int defaultPageSize) - { - DefaultPageSize = defaultPageSize; - } + public UseConnectionPagingAttribute(int defaultPageSize) + { + DefaultPageSize = defaultPageSize; + } - public UseConnectionPagingAttribute(int defaultPageSize, int maxPageSize) - : this(defaultPageSize) - { - MaxPageSize = maxPageSize; - } + public UseConnectionPagingAttribute(int defaultPageSize, int maxPageSize) + : this(defaultPageSize) + { + MaxPageSize = maxPageSize; + } - public int? DefaultPageSize { get; set; } - public int? MaxPageSize { get; set; } + public int? DefaultPageSize { get; set; } + public int? MaxPageSize { get; set; } - public override void ApplyExtension(IField field) - { - field.UseConnectionPaging(DefaultPageSize, MaxPageSize); - } + public override void ApplyExtension(IField field) + { + field.UseConnectionPaging(DefaultPageSize, MaxPageSize); } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/IFieldExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/IFieldExtension.cs index abe57bfc..2380267e 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/IFieldExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/IFieldExtension.cs @@ -118,7 +118,6 @@ ParameterReplacer parameterReplacer /// /// This should be thread safe /// - /// /// The final expression for the field /// Expression ProcessScalarExpression(Expression expression, ParameterReplacer parameterReplacer); diff --git a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetArgs.cs b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetArgs.cs index 860fc703..677077de 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetArgs.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetArgs.cs @@ -1,8 +1,7 @@ -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class OffsetArgs { - public class OffsetArgs - { - public int? Skip { get; set; } - public int? Take { get; set; } - } + public int? Skip { get; set; } + public int? Take { get; set; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPage.cs b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPage.cs index c3d16e42..1399e296 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPage.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPage.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; using System.ComponentModel; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class OffsetPage(int totalItems, int? skip, int? take) { - public class OffsetPage(int totalItems, int? skip, int? take) - { - private readonly int? skip = skip; - private readonly int? take = take; + private readonly int? skip = skip; + private readonly int? take = take; - [Description("Items in the page")] - public IEnumerable Items { get; set; } = new List(); + [Description("Items in the page")] + public IEnumerable Items { get; set; } = new List(); - [Description("True if there is more data before this page")] - public bool HasPreviousPage => (skip ?? 0) > 0; + [Description("True if there is more data before this page")] + public bool HasPreviousPage => (skip ?? 0) > 0; - [Description("True if there is more data after this page")] - public bool HasNextPage => take != null && ((skip ?? 0) + (take ?? 0)) < TotalItems; + [Description("True if there is more data after this page")] + public bool HasNextPage => take != null && ((skip ?? 0) + (take ?? 0)) < TotalItems; - [Description("Count of the total items in the collection")] - public int TotalItems { get; set; } = totalItems; - } + [Description("Count of the total items in the collection")] + public int TotalItems { get; set; } = totalItems; } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPaging.cs b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPaging.cs index 390e7bca..825aae5e 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPaging.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/OffsetPaging/OffsetPaging.cs @@ -1,43 +1,42 @@ -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public static class UseOffsetPagingExtension { - public static class UseOffsetPagingExtension + /// + /// Update field to implement paging with the Connection<> classes and metadata. + /// Only call on a field that returns an IEnumerable + /// + /// + /// If argument take is null this value will be used + /// If argument take is greater than this value an error will be raised + /// + public static IField UseOffsetPaging(this IField field, int? defaultPageSize = null, int? maxPageSize = null) { - /// - /// Update field to implement paging with the Connection<> classes and metadata. - /// Only call on a field that returns an IEnumerable - /// - /// - /// If argument take is null this value will be used - /// If argument take is greater than this value an error will be raised - /// - public static IField UseOffsetPaging(this IField field, int? defaultPageSize = null, int? maxPageSize = null) - { - field.AddExtension(new OffsetPagingExtension(defaultPageSize, maxPageSize)); - return field; - } + field.AddExtension(new OffsetPagingExtension(defaultPageSize, maxPageSize)); + return field; } +} - public class UseOffsetPagingAttribute : ExtensionAttribute - { - public int? DefaultPageSize { get; set; } - public int? MaxPageSize { get; set; } +public class UseOffsetPagingAttribute : ExtensionAttribute +{ + public int? DefaultPageSize { get; set; } + public int? MaxPageSize { get; set; } - public UseOffsetPagingAttribute() { } + public UseOffsetPagingAttribute() { } - public UseOffsetPagingAttribute(int defaultPageSize) - { - DefaultPageSize = defaultPageSize; - } + public UseOffsetPagingAttribute(int defaultPageSize) + { + DefaultPageSize = defaultPageSize; + } - public UseOffsetPagingAttribute(int defaultPageSize, int maxPageSize) - : this(defaultPageSize) - { - MaxPageSize = maxPageSize; - } + public UseOffsetPagingAttribute(int defaultPageSize, int maxPageSize) + : this(defaultPageSize) + { + MaxPageSize = maxPageSize; + } - public override void ApplyExtension(IField field) - { - field.UseOffsetPaging(DefaultPageSize, MaxPageSize); - } + public override void ApplyExtension(IField field) + { + field.UseOffsetPaging(DefaultPageSize, MaxPageSize); } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortArgs.cs b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortArgs.cs index e5a92181..28da9583 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortArgs.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortArgs.cs @@ -11,5 +11,5 @@ public class SortInput public enum SortDirection { ASC, - DESC + DESC, } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortExtension.cs index e8507207..c24dfa7b 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/SortExtension.cs @@ -7,196 +7,195 @@ using EntityGraphQL.Compiler.Util; using EntityGraphQL.Extensions; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public class SortExtension : BaseFieldExtension { - public class SortExtension : BaseFieldExtension + private ISchemaType? schemaReturnType; + private Type? listType; + private Type? methodType; + private Func? fieldNamer; + private readonly Type? fieldSelectionType; + private readonly List defaultSorts; + private readonly ParameterExpression? fieldSelectionParam; + private readonly Dictionary? fieldSelectionExpressions; + private readonly bool useSchemaFields; + + public SortExtension(LambdaExpression? fieldSelection, bool useSchemaFields, params ISort[] defaultSorts) + { + this.fieldSelectionType = fieldSelection?.ReturnType; + this.defaultSorts = defaultSorts?.ToList() ?? []; + this.fieldSelectionParam = fieldSelection?.Parameters.First(); + if (fieldSelection?.Body is NewExpression newExp) + this.fieldSelectionExpressions = newExp.Members?.Select((m, i) => new { m.Name, Expression = newExp.Arguments[i] }).ToDictionary(x => x.Name, x => x.Expression); + this.useSchemaFields = useSchemaFields; + } + + public override void Configure(ISchemaProvider schema, IField field) { - private ISchemaType? schemaReturnType; - private Type? listType; - private Type? methodType; - private Func? fieldNamer; - private readonly Type? fieldSelectionType; - private readonly List defaultSorts; - private readonly ParameterExpression? fieldSelectionParam; - private readonly Dictionary? fieldSelectionExpressions; - private readonly bool useSchemaFields; - - public SortExtension(LambdaExpression? fieldSelection, bool useSchemaFields, params ISort[] defaultSorts) + if (field.ResolveExpression == null) + throw new EntityGraphQLCompilerException($"SortExtension requires a Resolve function set on the field"); + + if (!field.ResolveExpression.Type.IsEnumerableOrArray()) + throw new ArgumentException($"Expression for field {field.Name} must be a collection to use SortExtension. Found type {field.ReturnType.TypeDotnet}"); + + if (!schema.HasType(typeof(SortDirection))) + schema.AddEnum("SortDirectionEnum", "Sort direction enum"); + schemaReturnType = field.ReturnType.SchemaType; + listType = field.ReturnType.TypeDotnet.GetEnumerableOrArrayType()!; + methodType = typeof(IQueryable).IsAssignableFrom(field.ReturnType.TypeDotnet) ? typeof(Queryable) : typeof(Enumerable); + + fieldNamer = schema.SchemaFieldNamer; + var sortInputName = $"{field.FromType.Name}{field.Name.FirstCharToUpper()}SortInput".FirstCharToUpper(); + ISchemaType schemaSortType; + var argSortType = MakeSortType(field); + // look type reuse type. Type is not recreated if it uses the same fields + if (schema.HasType(argSortType)) + schemaSortType = schema.GetSchemaType(argSortType, false, null); + else { - this.fieldSelectionType = fieldSelection?.ReturnType; - this.defaultSorts = defaultSorts?.ToList() ?? []; - this.fieldSelectionParam = fieldSelection?.Parameters.First(); - if (fieldSelection?.Body is NewExpression newExp) - this.fieldSelectionExpressions = newExp.Members?.Select((m, i) => new { m.Name, Expression = newExp.Arguments[i] }).ToDictionary(x => x.Name, x => x.Expression); - this.useSchemaFields = useSchemaFields; + schemaSortType = schema.AddInputType(argSortType, sortInputName, $"Sort arguments for {field.Name}").AddAllFields(); } - public override void Configure(ISchemaProvider schema, IField field) + var argType = typeof(SortInput<>).MakeGenericType(schemaSortType.TypeDotnet); + var argInstance = Activator.CreateInstance(argType)!; + if (defaultSorts.Count > 0) { - if (field.ResolveExpression == null) - throw new EntityGraphQLCompilerException($"SortExtension requires a Resolve function set on the field"); - - if (!field.ResolveExpression.Type.IsEnumerableOrArray()) - throw new ArgumentException($"Expression for field {field.Name} must be a collection to use SortExtension. Found type {field.ReturnType.TypeDotnet}"); - - if (!schema.HasType(typeof(SortDirection))) - schema.AddEnum("SortDirectionEnum", typeof(SortDirection), "Sort direction enum"); - schemaReturnType = field.ReturnType.SchemaType; - listType = field.ReturnType.TypeDotnet.GetEnumerableOrArrayType()!; - methodType = typeof(IQueryable).IsAssignableFrom(field.ReturnType.TypeDotnet) ? typeof(Queryable) : typeof(Enumerable); - - fieldNamer = schema.SchemaFieldNamer; - var sortInputName = $"{field.FromType.Name}{field.Name.FirstCharToUpper()}SortInput".FirstCharToUpper(); - ISchemaType schemaSortType; - var argSortType = MakeSortType(field); - // look type reuse type. Type is not recreated if it uses the same fields - if (schema.HasType(argSortType)) - schemaSortType = schema.GetSchemaType(argSortType, false, null); - else - { - schemaSortType = schema.AddInputType(argSortType, sortInputName, $"Sort arguments for {field.Name}").AddAllFields(); - } + var defaultSortValues = Activator.CreateInstance(typeof(List<>).MakeGenericType(schemaSortType.TypeDotnet))!; - var argType = typeof(SortInput<>).MakeGenericType(schemaSortType.TypeDotnet); - var argInstance = Activator.CreateInstance(argType)!; - if (defaultSorts.Count > 0) + foreach (var defaultSort in defaultSorts) { - var defaultSortValues = Activator.CreateInstance(typeof(List<>).MakeGenericType(schemaSortType.TypeDotnet))!; - - foreach (var defaultSort in defaultSorts) + var defaultSortValue = Activator.CreateInstance(schemaSortType.TypeDotnet)!; + // if the field is not there the default sort is not exposed in the API we the schema does not need to know about the default + var fieldExp = defaultSort.SortExpression.Body; + if (fieldExp.NodeType == ExpressionType.Convert) + fieldExp = ((UnaryExpression)fieldExp).Operand; + var sortValueField = schemaSortType.TypeDotnet.GetField(((MemberExpression)fieldExp).Member.Name); + if (sortValueField != null) { - var defaultSortValue = Activator.CreateInstance(schemaSortType.TypeDotnet)!; - // if the field is not there the default sort is not exposed in the API we the schema does not need to know about the default - var fieldExp = defaultSort.SortExpression.Body; - if (fieldExp.NodeType == ExpressionType.Convert) - fieldExp = ((UnaryExpression)fieldExp).Operand; - var sortValueField = schemaSortType.TypeDotnet.GetField(((MemberExpression)fieldExp).Member.Name); - if (sortValueField != null) - { - sortValueField.SetValue(defaultSortValue, defaultSort.Direction); - ((IList)defaultSortValues).Add(defaultSortValue); - argType.GetProperty("Sort")!.SetValue(argInstance, defaultSortValues); - } + sortValueField.SetValue(defaultSortValue, defaultSort.Direction); + ((IList)defaultSortValues).Add(defaultSortValue); + argType.GetProperty("Sort")!.SetValue(argInstance, defaultSortValues); } } - field.AddArguments(argInstance); } + field.AddArguments(argInstance); + } - private Type MakeSortType(IField field) - { - // Build the field args - Dictionary fields = []; - var directionType = typeof(SortDirection?); + private Type MakeSortType(IField field) + { + // Build the field args + Dictionary fields = []; + var directionType = typeof(SortDirection?); - if (useSchemaFields) + if (useSchemaFields) + { + foreach (var schemaField in schemaReturnType!.GetFields()) { - foreach (var schemaField in schemaReturnType!.GetFields()) - { - if (schemaField.Name.StartsWith("__", StringComparison.CurrentCulture)) - continue; - if (IsNotInputType(schemaField.ReturnType.TypeDotnet)) - continue; - fields.Add(schemaField.Name, directionType); - } + if (schemaField.Name.StartsWith("__", StringComparison.CurrentCulture)) + continue; + if (IsNotInputType(schemaField.ReturnType.TypeDotnet)) + continue; + fields.Add(schemaField.Name, directionType); } - else + } + else + { + var typeWithSortFields = fieldSelectionType ?? listType!; + foreach (var prop in typeWithSortFields.GetProperties()) { - var typeWithSortFields = fieldSelectionType ?? listType!; - foreach (var prop in typeWithSortFields.GetProperties()) - { - if (IsNotInputType(prop.PropertyType) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) - continue; - fields.Add(prop.Name, directionType); - } - foreach (var prop in typeWithSortFields.GetFields()) - { - if (IsNotInputType(prop.FieldType) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) - continue; - fields.Add(prop.Name, directionType); - } + if (IsNotInputType(prop.PropertyType) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) + continue; + fields.Add(prop.Name, directionType); + } + foreach (var prop in typeWithSortFields.GetFields()) + { + if (IsNotInputType(prop.FieldType) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) + continue; + fields.Add(prop.Name, directionType); } - // build SortInput - need a unique name if they use sort on another field with the same name - var argSortType = LinqRuntimeTypeBuilder.GetDynamicType(fields, field.Name); - return argSortType; } + // build SortInput - need a unique name if they use sort on another field with the same name + var argSortType = LinqRuntimeTypeBuilder.GetDynamicType(fields, field.Name); + return argSortType; + } - private static bool IsNotInputType(Type type) - { - return type.IsEnumerableOrArray() || (type.IsClass && type != typeof(string)); - } + private static bool IsNotInputType(Type type) + { + return type.IsEnumerableOrArray() || (type.IsClass && type != typeof(string)); + } - public override Expression? GetExpression( - IField field, - Expression expression, - ParameterExpression? argumentParam, - dynamic? arguments, - Expression context, - IGraphQLNode? parentNode, - bool servicesPass, - ParameterReplacer parameterReplacer - ) - { - // things are sorted already and the field shape has changed - if (servicesPass) - return expression; + public override Expression? GetExpression( + IField field, + Expression expression, + ParameterExpression? argumentParam, + dynamic? arguments, + Expression context, + IGraphQLNode? parentNode, + bool servicesPass, + ParameterReplacer parameterReplacer + ) + { + // things are sorted already and the field shape has changed + if (servicesPass) + return expression; - // default sort gets put in arguments - if (arguments != null && arguments!.Sort != null && arguments!.Sort.Count > 0) + // default sort gets put in arguments + if (arguments != null && arguments!.Sort != null && arguments!.Sort.Count > 0) + { + var sortMethod = "OrderBy"; + foreach (var sort in arguments!.Sort) { - var sortMethod = "OrderBy"; - foreach (var sort in arguments!.Sort) + // find the field that tells us the order field + foreach (var fieldInfo in ((Type)sort.GetType()).GetFields()) { - // find the field that tells us the order field - foreach (var fieldInfo in ((Type)sort.GetType()).GetFields()) + var direction = (SortDirection?)fieldInfo.GetValue(sort); + if (!direction.HasValue) + continue; + + string method = sortMethod; + + if (direction.Value == SortDirection.DESC) + method += "Descending"; + + var listParam = Expression.Parameter(listType!); + + Type sortReturnType; + if (fieldSelectionExpressions != null && fieldSelectionExpressions.TryGetValue(fieldInfo.Name, out var sortExpression)) { - var direction = (SortDirection?)fieldInfo.GetValue(sort); - if (!direction.HasValue) - continue; - - string method = sortMethod; - - if (direction.Value == SortDirection.DESC) - method += "Descending"; - - var listParam = Expression.Parameter(listType!); - - Type sortReturnType; - if (fieldSelectionExpressions != null && fieldSelectionExpressions.TryGetValue(fieldInfo.Name, out var sortExpression)) - { - sortReturnType = sortExpression.Type; - sortExpression = parameterReplacer.Replace(sortExpression, fieldSelectionParam!, listParam); - } - else - { - var schemaField = schemaReturnType!.GetField(fieldNamer!(fieldInfo.Name), null); - sortReturnType = schemaField.ReturnType.TypeDotnet; - sortExpression = schemaField.ResolveExpression ?? Expression.PropertyOrField(listParam, fieldInfo.Name); - listParam = schemaField.FieldParam!; - } - - expression = Expression.Call(methodType!, method, [listType!, sortReturnType], expression, Expression.Lambda(sortExpression, listParam)); - break; + sortReturnType = sortExpression.Type; + sortExpression = parameterReplacer.Replace(sortExpression, fieldSelectionParam!, listParam); } - sortMethod = "ThenBy"; + else + { + var schemaField = schemaReturnType!.GetField(fieldNamer!(fieldInfo.Name), null); + sortReturnType = schemaField.ReturnType.TypeDotnet; + sortExpression = schemaField.ResolveExpression ?? Expression.PropertyOrField(listParam, fieldInfo.Name); + listParam = schemaField.FieldParam!; + } + + expression = Expression.Call(methodType!, method, [listType!, sortReturnType], expression, Expression.Lambda(sortExpression, listParam)); + break; } + sortMethod = "ThenBy"; } - else if (defaultSorts.Count > 0) + } + else if (defaultSorts.Count > 0) + { + var thenBy = false; + foreach (var defaultSort in defaultSorts) { - var thenBy = false; - foreach (var defaultSort in defaultSorts) - { - var listParam = Expression.Parameter(listType!); - expression = Expression.Call( - methodType!, - defaultSort.Direction == SortDirection.ASC ? (thenBy ? "ThenBy" : "OrderBy") : (thenBy ? "ThenByDescending" : "OrderByDescending"), - new Type[] { listType!, defaultSort.SortExpression.Body.Type }, - expression, - parameterReplacer.Replace(defaultSort.SortExpression, defaultSort.SortExpression.Parameters.First(), listParam) - ); - thenBy = true; - } + var listParam = Expression.Parameter(listType!); + expression = Expression.Call( + methodType!, + defaultSort.Direction == SortDirection.ASC ? (thenBy ? "ThenBy" : "OrderBy") : (thenBy ? "ThenByDescending" : "OrderByDescending"), + new Type[] { listType!, defaultSort.SortExpression.Body.Type }, + expression, + parameterReplacer.Replace(defaultSort.SortExpression, defaultSort.SortExpression.Parameters.First(), listParam) + ); + thenBy = true; } - return expression; } + return expression; } } diff --git a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/UseSortExtension.cs b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/UseSortExtension.cs index 2610a66d..e2fddc74 100644 --- a/src/EntityGraphQL/Schema/FieldExtensions/Sorting/UseSortExtension.cs +++ b/src/EntityGraphQL/Schema/FieldExtensions/Sorting/UseSortExtension.cs @@ -1,122 +1,121 @@ using System; using System.Linq.Expressions; -namespace EntityGraphQL.Schema.FieldExtensions +namespace EntityGraphQL.Schema.FieldExtensions; + +public static class UseSortExtension { - public static class UseSortExtension + /// + /// Update field to implement a sort argument that takes options to sort a collection + /// Only call on a field that returns an IEnumerable + /// + /// + /// Select the fields you want available for sorting. + /// T must be the context of the collection you are applying the sort to + /// + public static IField UseSort(this IField field, Expression> fieldSelection) { - /// - /// Update field to implement a sort argument that takes options to sort a collection - /// Only call on a field that returns an IEnumerable - /// - /// - /// Select the fields you want available for sorting. - /// T must be the context of the collection you are applying the sort to - /// - public static IField UseSort(this IField field, Expression> fieldSelection) - { - field.AddExtension(new SortExtension(fieldSelection, false)); - return field; - } - - /// - /// Update field to implement a sort argument that takes options to sort a collection - /// Only call on a field that returns an IEnumerable - /// - /// - /// Select the fields you want available for sorting. - /// T must be the context of the collection you are applying the sort to - /// Sort to use if no sort argument supplied in query - /// Direction of the default sort - /// - public static IField UseSort( - this IField field, - Expression> fieldSelection, - Expression> defaultSort, - SortDirection direction - ) - { - field.AddExtension(new SortExtension(fieldSelection, false, new Sort(defaultSort, direction))); - return field; - } - - /// - /// Update field to implement a sort argument that takes options to sort a collection - /// Only call on a field that returns an IEnumerable - /// - /// - /// Sort to use if no sort argument supplied in query - /// Direction of the default sort - /// - public static IField UseSort(this IField field, Expression> defaultSort, SortDirection direction) - { - field.AddExtension(new SortExtension(null, false, new Sort(defaultSort, direction))); - return field; - } + field.AddExtension(new SortExtension(fieldSelection, false)); + return field; + } - public static IField UseSort(this IField field, Expression> fieldSelection, params Sort[] defaultSorts) - { - field.AddExtension(new SortExtension(fieldSelection, false, defaultSorts)); - return field; - } + /// + /// Update field to implement a sort argument that takes options to sort a collection + /// Only call on a field that returns an IEnumerable + /// + /// + /// Select the fields you want available for sorting. + /// T must be the context of the collection you are applying the sort to + /// Sort to use if no sort argument supplied in query + /// Direction of the default sort + /// + public static IField UseSort( + this IField field, + Expression> fieldSelection, + Expression> defaultSort, + SortDirection direction + ) + { + field.AddExtension(new SortExtension(fieldSelection, false, new Sort(defaultSort, direction))); + return field; + } - public static IField UseSort(this IField field, params Sort[] defaultSorts) - { - field.AddExtension(new SortExtension(null, false, defaultSorts)); - return field; - } + /// + /// Update field to implement a sort argument that takes options to sort a collection + /// Only call on a field that returns an IEnumerable + /// + /// + /// Sort to use if no sort argument supplied in query + /// Direction of the default sort + /// + public static IField UseSort(this IField field, Expression> defaultSort, SortDirection direction) + { + field.AddExtension(new SortExtension(null, false, new Sort(defaultSort, direction))); + return field; + } - public static IField UseSort(this IField field, bool useSchemaFields, params Sort[] defaultSorts) - { - field.AddExtension(new SortExtension(null, useSchemaFields, defaultSorts)); - return field; - } + public static IField UseSort(this IField field, Expression> fieldSelection, params Sort[] defaultSorts) + { + field.AddExtension(new SortExtension(fieldSelection, false, defaultSorts)); + return field; + } - /// - /// Update field to implement a sort argument that takes options to sort a collection - /// Only call on a field that returns an IEnumerable - /// - /// - /// Use the schema fields for sorting. Will use the fields current in the schema only - /// - public static IField UseSort(this IField field, bool useSchemaFields = false) - { - field.AddExtension(new SortExtension(null, useSchemaFields)); - return field; - } + public static IField UseSort(this IField field, params Sort[] defaultSorts) + { + field.AddExtension(new SortExtension(null, false, defaultSorts)); + return field; } - public class UseSortAttribute : ExtensionAttribute + public static IField UseSort(this IField field, bool useSchemaFields, params Sort[] defaultSorts) { - public UseSortAttribute() { } + field.AddExtension(new SortExtension(null, useSchemaFields, defaultSorts)); + return field; + } - public override void ApplyExtension(IField field) - { - field.UseSort(); - } + /// + /// Update field to implement a sort argument that takes options to sort a collection + /// Only call on a field that returns an IEnumerable + /// + /// + /// Use the schema fields for sorting. Will use the fields current in the schema only + /// + public static IField UseSort(this IField field, bool useSchemaFields = false) + { + field.AddExtension(new SortExtension(null, useSchemaFields)); + return field; } +} + +public class UseSortAttribute : ExtensionAttribute +{ + public UseSortAttribute() { } - public class Sort : ISort + public override void ApplyExtension(IField field) { - public LambdaExpression SortExpression { get; set; } - public SortDirection Direction { get; set; } + field.UseSort(); + } +} - public Sort(Expression> sortExpression) - { - SortExpression = sortExpression; - Direction = SortDirection.ASC; - } +public class Sort : ISort +{ + public LambdaExpression SortExpression { get; set; } + public SortDirection Direction { get; set; } - public Sort(Expression> sortExpression, SortDirection direction) - { - SortExpression = sortExpression; - Direction = direction; - } + public Sort(Expression> sortExpression) + { + SortExpression = sortExpression; + Direction = SortDirection.ASC; } - public interface ISort + public Sort(Expression> sortExpression, SortDirection direction) { - LambdaExpression SortExpression { get; set; } - SortDirection Direction { get; set; } + SortExpression = sortExpression; + Direction = direction; } } + +public interface ISort +{ + LambdaExpression SortExpression { get; set; } + SortDirection Direction { get; set; } +} diff --git a/src/EntityGraphQL/Schema/FieldToResolve.cs b/src/EntityGraphQL/Schema/FieldToResolve.cs index 8e2972e5..e663475e 100644 --- a/src/EntityGraphQL/Schema/FieldToResolve.cs +++ b/src/EntityGraphQL/Schema/FieldToResolve.cs @@ -66,67 +66,66 @@ public Field Resolve(Expression> fieldExpression return this; } - public FieldWithContextAndArgs Resolve(Expression> fieldExpression) => ResolveWithService(fieldExpression); - - public FieldWithContextAndArgs ResolveWithService(Expression> fieldExpression) + public FieldWithContextAndArgs Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, true); - Services = new List { fieldExpression.Parameters[2] }; + Services = [fieldExpression.Parameters[2]]; return this; } - public FieldWithContextAndArgs Resolve(Expression> fieldExpression) => - ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContextAndArgs ResolveWithService(Expression> fieldExpression) => Resolve(fieldExpression); - public FieldWithContextAndArgs ResolveWithServices(Expression> fieldExpression) + public FieldWithContextAndArgs Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, true); - Services = new List { fieldExpression.Parameters[2], fieldExpression.Parameters[3] }; + Services = [fieldExpression.Parameters[2], fieldExpression.Parameters[3]]; return this; } - public FieldWithContextAndArgs Resolve(Expression> fieldExpression) => - ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContextAndArgs ResolveWithServices(Expression> fieldExpression) => + Resolve(fieldExpression); - public FieldWithContextAndArgs ResolveWithServices(Expression> fieldExpression) + public FieldWithContextAndArgs Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, true); - Services = new List { fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4] }; + Services = [fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4]]; return this; } - public FieldWithContextAndArgs Resolve( - Expression> fieldExpression - ) => ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContextAndArgs ResolveWithServices( + Expression> fieldExpression + ) => Resolve(fieldExpression); - public FieldWithContextAndArgs ResolveWithServices( + public FieldWithContextAndArgs Resolve( Expression> fieldExpression ) { SetUpField(fieldExpression, true, true); - Services = new List { fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4], fieldExpression.Parameters[5] }; + Services = [fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4], fieldExpression.Parameters[5]]; return this; } - public FieldWithContextAndArgs Resolve( - Expression> fieldExpression - ) => ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContextAndArgs ResolveWithServices( + Expression> fieldExpression + ) => Resolve(fieldExpression); - public FieldWithContextAndArgs ResolveWithServices( + public FieldWithContextAndArgs Resolve( Expression> fieldExpression ) { SetUpField(fieldExpression, true, true); - Services = new List - { - fieldExpression.Parameters[2], - fieldExpression.Parameters[3], - fieldExpression.Parameters[4], - fieldExpression.Parameters[5], - fieldExpression.Parameters[6] - }; + Services = [fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4], fieldExpression.Parameters[5], fieldExpression.Parameters[6]]; return this; } + + [Obsolete("Use Resolve")] + public FieldWithContextAndArgs ResolveWithServices( + Expression> fieldExpression + ) => Resolve(fieldExpression); } /// @@ -144,61 +143,60 @@ public Field Resolve(Expression> fieldExpression) return this; } - public FieldWithContext Resolve(Expression> fieldExpression) => ResolveWithService(fieldExpression); - - public FieldWithContext ResolveWithService(Expression> fieldExpression) + public FieldWithContext Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, false); - Services = new List { fieldExpression.Parameters[1] }; + Services = [fieldExpression.Parameters[1]]; return this; } - public FieldWithContext Resolve(Expression> fieldExpression) => ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContext ResolveWithService(Expression> fieldExpression) => Resolve(fieldExpression); - public FieldWithContext ResolveWithServices(Expression> fieldExpression) + public FieldWithContext Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, false); - Services = new List { fieldExpression.Parameters[1], fieldExpression.Parameters[2] }; + Services = [fieldExpression.Parameters[1], fieldExpression.Parameters[2]]; return this; } - public FieldWithContext Resolve(Expression> fieldExpression) => - ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContext ResolveWithServices(Expression> fieldExpression) => Resolve(fieldExpression); - public FieldWithContext ResolveWithServices(Expression> fieldExpression) + public FieldWithContext Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, false); - Services = new List { fieldExpression.Parameters[1], fieldExpression.Parameters[2], fieldExpression.Parameters[3] }; + Services = [fieldExpression.Parameters[1], fieldExpression.Parameters[2], fieldExpression.Parameters[3]]; return this; } - public FieldWithContext Resolve(Expression> fieldExpression) => - ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContext ResolveWithServices(Expression> fieldExpression) => + Resolve(fieldExpression); - public FieldWithContext ResolveWithServices(Expression> fieldExpression) + public FieldWithContext Resolve(Expression> fieldExpression) { SetUpField(fieldExpression, true, false); - Services = new List { fieldExpression.Parameters[1], fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4] }; + Services = [fieldExpression.Parameters[1], fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4]]; return this; } - public FieldWithContext Resolve( - Expression> fieldExpression - ) => ResolveWithServices(fieldExpression); + [Obsolete("Use Resolve")] + public FieldWithContext ResolveWithServices( + Expression> fieldExpression + ) => Resolve(fieldExpression); - public FieldWithContext ResolveWithServices( + public FieldWithContext Resolve( Expression> fieldExpression ) { SetUpField(fieldExpression, true, false); - Services = new List - { - fieldExpression.Parameters[1], - fieldExpression.Parameters[2], - fieldExpression.Parameters[3], - fieldExpression.Parameters[4], - fieldExpression.Parameters[5] - }; + Services = [fieldExpression.Parameters[1], fieldExpression.Parameters[2], fieldExpression.Parameters[3], fieldExpression.Parameters[4], fieldExpression.Parameters[5]]; return this; } + + [Obsolete("Use Resolve")] + public FieldWithContext ResolveWithServices( + Expression> fieldExpression + ) => Resolve(fieldExpression); } diff --git a/src/EntityGraphQL/Schema/GqlTypeEnum.cs b/src/EntityGraphQL/Schema/GqlTypeEnum.cs index e5fe4fb5..15bd4922 100644 --- a/src/EntityGraphQL/Schema/GqlTypeEnum.cs +++ b/src/EntityGraphQL/Schema/GqlTypeEnum.cs @@ -1,21 +1,20 @@ -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public enum GqlTypes { - public enum GqlTypes - { - Scalar, - Enum, - QueryObject, - Interface, - InputObject, - Mutation, - Union - } + Scalar, + Enum, + QueryObject, + Interface, + InputObject, + Mutation, + Union, +} - public static class GqlTypesExtensions +public static class GqlTypesExtensions +{ + public static bool IsNotValidForInput(this GqlTypes type) { - public static bool IsNotValidForInput(this GqlTypes type) - { - return type == GqlTypes.Interface || type == GqlTypes.Mutation || type == GqlTypes.QueryObject || type == GqlTypes.Union; - } + return type == GqlTypes.Interface || type == GqlTypes.Mutation || type == GqlTypes.QueryObject || type == GqlTypes.Union; } } diff --git a/src/EntityGraphQL/Schema/GqlTypeInfo.cs b/src/EntityGraphQL/Schema/GqlTypeInfo.cs index 32a255c2..52718f9f 100644 --- a/src/EntityGraphQL/Schema/GqlTypeInfo.cs +++ b/src/EntityGraphQL/Schema/GqlTypeInfo.cs @@ -5,104 +5,103 @@ using Nullability; #endif -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Holds information about a type result in the schema (e.g. a field return type) +/// +public class GqlTypeInfo { + private ISchemaType? schemaType; + /// - /// Holds information about a type result in the schema (e.g. a field return type) + /// New GqlTypeInfo object that represents information about the return/argument type /// - public class GqlTypeInfo + /// Func to get the ISchemaType. Lookup is func as the type might be added later. It is cached after first look up + /// The dotnet type as it is. E.g. the List etc. + public GqlTypeInfo(Func schemaTypeGetter, Type typeDotnet) { - private ISchemaType? schemaType; - - /// - /// New GqlTypeInfo object that represents information about the return/argument type - /// - /// Func to get the ISchemaType. Lookup is func as the type might be added later. It is cached after first look up - /// The dotnet type as it is. E.g. the List etc. - public GqlTypeInfo(Func schemaTypeGetter, Type typeDotnet) - { - SchemaTypeGetter = schemaTypeGetter; + SchemaTypeGetter = schemaTypeGetter; - TypeDotnet = typeDotnet; - IsList = TypeDotnet.IsEnumerableOrArray(); - TypeNotNullable = TypeDotnet.IsValueType && !TypeDotnet.IsNullableType(); - ElementTypeNullable = false; - } + TypeDotnet = typeDotnet; + IsList = TypeDotnet.IsEnumerableOrArray(); + TypeNotNullable = TypeDotnet.IsValueType && !TypeDotnet.IsNullableType(); + ElementTypeNullable = false; + } - /// - /// New GqlTypeInfo object that represents information about the return/argument type - /// - /// Func to get the ISchemaType. Lookup is func as the type might be added later. It is cached after first look up - /// The dotnet type as it is. E.g. the List etc. - /// Nullability information about the property - public GqlTypeInfo(Func schemaTypeGetter, Type typeDotnet, NullabilityInfo nullability) - { - SchemaTypeGetter = schemaTypeGetter; + /// + /// New GqlTypeInfo object that represents information about the return/argument type + /// + /// Func to get the ISchemaType. Lookup is func as the type might be added later. It is cached after first look up + /// The dotnet type as it is. E.g. the List etc. + /// Nullability information about the property + public GqlTypeInfo(Func schemaTypeGetter, Type typeDotnet, NullabilityInfo nullability) + { + SchemaTypeGetter = schemaTypeGetter; - TypeDotnet = typeDotnet; - IsList = TypeDotnet.IsEnumerableOrArray(); - TypeNotNullable = nullability.ReadState == NullabilityState.NotNull; - ElementTypeNullable = nullability.GenericTypeArguments.Length > 0 && nullability.GenericTypeArguments[0].ReadState == NullabilityState.Nullable; - } + TypeDotnet = typeDotnet; + IsList = TypeDotnet.IsEnumerableOrArray(); + TypeNotNullable = nullability.ReadState == NullabilityState.NotNull; + ElementTypeNullable = nullability.GenericTypeArguments.Length > 0 && nullability.GenericTypeArguments[0].ReadState == NullabilityState.Nullable; + } - /// - /// The schema type - /// - /// - public ISchemaType SchemaType + /// + /// The schema type + /// + /// + public ISchemaType SchemaType + { + get { - get - { - schemaType ??= SchemaTypeGetter(); - return schemaType; - } + schemaType ??= SchemaTypeGetter(); + return schemaType; } + } - /// - /// Type described as type as a full GraphQL type. e.g. [Int!]! - /// - /// - public string GqlTypeForReturnOrArgument => - $"{(IsList ? "[" : "")}{SchemaType.Name}{((!IsList && TypeNotNullable) || (IsList && !ElementTypeNullable) ? "!" : "")}{(IsList ? "]" : "")}{(IsList && TypeNotNullable ? "!" : "")}"; - - /// - /// Typw is not nullable (! in GQL) - /// - /// - public bool TypeNotNullable { get; set; } - - /// - /// If IsList the element type if nullable or not ([Type!] in gql) - /// - /// - public bool ElementTypeNullable { get; set; } - - /// - /// The Type is a list/array ([] in gql) - /// - /// - public bool IsList { get; set; } - - public Func SchemaTypeGetter { get; } - - /// - /// Mapped type in dotnet - /// - /// - public Type TypeDotnet { get; } - - public override string ToString() - { - return GqlTypeForReturnOrArgument; - } + /// + /// Type described as type as a full GraphQL type. e.g. [Int!]! + /// + /// + public string GqlTypeForReturnOrArgument => + $"{(IsList ? "[" : "")}{SchemaType.Name}{((!IsList && TypeNotNullable) || (IsList && !ElementTypeNullable) ? "!" : "")}{(IsList ? "]" : "")}{(IsList && TypeNotNullable ? "!" : "")}"; - public static GqlTypeInfo FromGqlType(ISchemaProvider schema, Type dotnetType, string gqlType) - { - var strippedType = gqlType.Trim('!').Trim('[').Trim(']').Trim('!'); - var typeInfo = new GqlTypeInfo(() => schema.Type(strippedType), dotnetType) { TypeNotNullable = gqlType.EndsWith('!'), IsList = gqlType.Contains('[', StringComparison.InvariantCulture), }; - typeInfo.ElementTypeNullable = !(typeInfo.IsList && gqlType.Trim('!').Trim('[').Trim(']').EndsWith('!')); + /// + /// Typw is not nullable (! in GQL) + /// + /// + public bool TypeNotNullable { get; set; } - return typeInfo; - } + /// + /// If IsList the element type if nullable or not ([Type!] in gql) + /// + /// + public bool ElementTypeNullable { get; set; } + + /// + /// The Type is a list/array ([] in gql) + /// + /// + public bool IsList { get; set; } + + public Func SchemaTypeGetter { get; } + + /// + /// Mapped type in dotnet + /// + /// + public Type TypeDotnet { get; } + + public override string ToString() + { + return GqlTypeForReturnOrArgument; + } + + public static GqlTypeInfo FromGqlType(ISchemaProvider schema, Type dotnetType, string gqlType) + { + var strippedType = gqlType.Trim('!').Trim('[').Trim(']').Trim('!'); + var typeInfo = new GqlTypeInfo(() => schema.Type(strippedType), dotnetType) { TypeNotNullable = gqlType.EndsWith('!'), IsList = gqlType.Contains('[', StringComparison.InvariantCulture) }; + typeInfo.ElementTypeNullable = !(typeInfo.IsList && gqlType.Trim('!').Trim('[').Trim(']').EndsWith('!')); + + return typeInfo; } } diff --git a/src/EntityGraphQL/Schema/ICustomTypeConverter.cs b/src/EntityGraphQL/Schema/ICustomTypeConverter.cs index 76266d51..09f483f9 100644 --- a/src/EntityGraphQL/Schema/ICustomTypeConverter.cs +++ b/src/EntityGraphQL/Schema/ICustomTypeConverter.cs @@ -7,12 +7,8 @@ public interface ICustomTypeConverter Type Type { get; } /// - /// Change a non-null value from fromType to toType using you're custom method + /// Change a non-null value of fromType to toType using you're custom method /// - /// - /// - /// - /// - /// + /// The new object as toType object? ChangeType(object value, Type toType, ISchemaProvider schema); } diff --git a/src/EntityGraphQL/Schema/IField.cs b/src/EntityGraphQL/Schema/IField.cs index f87cc01f..862e1787 100644 --- a/src/EntityGraphQL/Schema/IField.cs +++ b/src/EntityGraphQL/Schema/IField.cs @@ -6,146 +6,143 @@ using EntityGraphQL.Schema.Directives; using EntityGraphQL.Schema.FieldExtensions; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public enum GraphQLQueryFieldType +{ + Query, + Mutation, + Subscription, +} + +/// +/// Represents a field in a GraphQL type. This can be a mutation field in the Mutation type or a field on a query type +/// +public interface IField { - public enum GraphQLQueryFieldType - { - Query, - Mutation, - Subscription, - } + IBulkFieldResolver? BulkResolver { get; } + GraphQLQueryFieldType FieldType { get; } + ISchemaProvider Schema { get; } + ParameterExpression? FieldParam { get; set; } + List? ExtractedFieldsFromServices { get; } + string? Description { get; } + + /// + /// Information about each field argument as represented in the GraphQL schema. + /// This is used to map the schema arguments to the dotnet expression arguments + /// + IDictionary Arguments { get; } + + /// + /// This is a ParameterExpression that is used to access all the field's arguments in the field expression. + /// The type is a type that has all the field's GraphQL Schema arguments as properties. + /// E.g. if the field has arguments (a, b, c) then expressions access them them like (args) => args.a + args.b + args.c + /// Note that these instances are replaced within the expression at execution time. + /// You should not store these at configuration time in field extensions + /// + ParameterExpression? ArgumentsParameter { get; } + + /// + /// This is the Type used in the field's expression. It maps to the arguments of the field. + /// + Type? ExpressionArgumentType { get; } + string Name { get; } + + /// + /// GraphQL type this field belongs to + /// + ISchemaType FromType { get; } /// - /// Represents a field in a GraphQL type. This can be a mutation field in the Mutation type or a field on a query type + /// Information about the GraphQL type returned by this field /// - public interface IField - { - IBulkFieldResolver? BulkResolver { get; } - GraphQLQueryFieldType FieldType { get; } - ISchemaProvider Schema { get; } - ParameterExpression? FieldParam { get; set; } - List? ExtractedFieldsFromServices { get; } - string? Description { get; } - - /// - /// Information about each field argument as represented in the GraphQL schema. - /// This is used to map the schema arguments to the dotnet expression arguments - /// - IDictionary Arguments { get; } - - /// - /// This is a ParameterExpression that is used to access all the field's arguments in the field expression. - /// The type is a type that has all the field's GraphQL Schema arguments as properties. - /// E.g. if the field has arguments (a, b, c) then expressions access them them like (args) => args.a + args.b + args.c - /// Note that these instances are replaced within the expression at execution time. - /// You should not store these at configuration time in field extensions - /// - ParameterExpression? ArgumentsParameter { get; } - - /// - /// This is the Type used in the field's expression. It maps to the arguments of the field. - /// - Type? ExpressionArgumentType { get; } - string Name { get; } - - /// - /// GraphQL type this field belongs to - /// - ISchemaType FromType { get; } - - /// - /// Information about the GraphQL type returned by this field - /// - GqlTypeInfo ReturnType { get; } - List Extensions { get; set; } - RequiredAuthorization? RequiredAuthorization { get; } - - IList DirectivesReadOnly { get; } - IField AddDirective(ISchemaDirective directive); - ArgType GetArgumentType(string argName); - bool HasArgumentByName(string argName); - - /// - /// If true the arguments on the field are used internally for processing (usually in extensions that change the - /// shape of the schema and need arguments from the original field) - /// Arguments will not be in introspection - /// - bool ArgumentsAreInternal { get; } - - /// - /// Services required to be injected for this fields selection - /// - List Services { get; } - IReadOnlyCollection> Validators { get; } - - [Obsolete( - "Avoid using this method, it creates issues if the field's type is used on multiple fields with different arguments. It will be removed in future versions. See updated OffsetPagingExtension for a better way using GetExpressionAndArguments" - )] - IField? UseArgumentsFromField { get; } - - /// - /// Given the current context, a type and a field name, it returns the expression for that field. Allows the provider to have a complex expression for a simple field. - /// Note this will change fieldExpression if the expression references arguments - /// - /// - /// - /// - /// - (Expression? expression, ParameterExpression? argumentParam) GetExpression( - Expression fieldExpression, - Expression? fieldContext, - IGraphQLNode? parentNode, - ParameterExpression? schemaContext, - CompileContext compileContext, - IReadOnlyDictionary args, - ParameterExpression? docParam, - object? docVariables, - IEnumerable directives, - bool contextChanged, - ParameterReplacer replacer - ); - Expression? ResolveExpression { get; } - IField UpdateExpression(Expression expression); - - IField AddExtension(IFieldExtension extension); - - /// - /// Add new arguments to the field. Properties on the args object will be merged with any existing arguments on the field. - /// - /// - void AddArguments(object args); - IField Returns(GqlTypeInfo gqlTypeInfo); - - [Obsolete( - "Avoid using this method, it creates issues if the field's type is used on multiple fields with different arguments. It will be removed in future versions. See updated OffsetPagingExtension for a better way using GetExpressionAndArguments" - )] - void UseArgumentsFrom(IField field); - IField AddValidator() - where TValidator : IArgumentValidator; - IField AddValidator(Action callback); - - /// - /// To access this field all roles listed here are required - /// - /// - IField RequiresAllRoles(params string[] roles); - - /// - /// To access this field any role listed is required - /// - /// - IField RequiresAnyRole(params string[] roles); - - /// - /// To access this field all policies listed here are required - /// - /// - IField RequiresAllPolicies(params string[] policies); - - /// - /// To access this field any policy listed is required - /// - /// - IField RequiresAnyPolicy(params string[] policies); - } + GqlTypeInfo ReturnType { get; } + List Extensions { get; set; } + RequiredAuthorization? RequiredAuthorization { get; } + + IList DirectivesReadOnly { get; } + IField AddDirective(ISchemaDirective directive); + ArgType GetArgumentType(string argName); + bool HasArgumentByName(string argName); + + /// + /// If true the arguments on the field are used internally for processing (usually in extensions that change the + /// shape of the schema and need arguments from the original field) + /// Arguments will not be in introspection + /// + bool ArgumentsAreInternal { get; } + + /// + /// Services required to be injected for this fields selection + /// + List Services { get; } + IReadOnlyCollection> Validators { get; } + + [Obsolete( + "Avoid using this method, it creates issues if the field's type is used on multiple fields with different arguments. It will be removed in future versions. See updated OffsetPagingExtension for a better way using GetExpressionAndArguments" + )] + IField? UseArgumentsFromField { get; } + + /// + /// Given the current context, a type and a field name, it returns the expression for that field. Allows the provider to have a complex expression for a simple field. + /// Note this will change fieldExpression if the expression references arguments + /// + (Expression? expression, ParameterExpression? argumentParam) GetExpression( + Expression fieldExpression, + Expression? fieldContext, + IGraphQLNode? parentNode, + ParameterExpression? schemaContext, + CompileContext compileContext, + IReadOnlyDictionary args, + ParameterExpression? docParam, + object? docVariables, + IEnumerable directives, + bool contextChanged, + ParameterReplacer replacer + ); + Expression? ResolveExpression { get; } + IField UpdateExpression(Expression expression); + + IField AddExtension(IFieldExtension extension); + + /// + /// Add new arguments to the field. Properties on the args object will be merged with any existing arguments on the field. + /// + /// + void AddArguments(object args); + IField Returns(GqlTypeInfo gqlTypeInfo); + + [Obsolete( + "Avoid using this method, it creates issues if the field's type is used on multiple fields with different arguments. It will be removed in future versions. See updated OffsetPagingExtension for a better way using GetExpressionAndArguments" + )] + void UseArgumentsFrom(IField field); + IField AddValidator() + where TValidator : IArgumentValidator; + IField AddValidator(Action callback); + + /// + /// To access this field all roles listed here are required + /// + /// + IField RequiresAllRoles(params string[] roles); + + /// + /// To access this field any role listed is required + /// + /// + IField RequiresAnyRole(params string[] roles); + + /// + /// To access this field all policies listed here are required + /// + /// + IField RequiresAllPolicies(params string[] policies); + + /// + /// To access this field any policy listed is required + /// + /// + IField RequiresAnyPolicy(params string[] policies); + + IField IsNullable(bool nullable); } diff --git a/src/EntityGraphQL/Schema/IGqlAuthorizationService.cs b/src/EntityGraphQL/Schema/IGqlAuthorizationService.cs index c52bdc08..038a49ed 100644 --- a/src/EntityGraphQL/Schema/IGqlAuthorizationService.cs +++ b/src/EntityGraphQL/Schema/IGqlAuthorizationService.cs @@ -3,16 +3,15 @@ using System.Reflection; using System.Security.Claims; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Provides a way to authenticate a user against a GraphQL request +/// +public interface IGqlAuthorizationService { - /// - /// Provides a way to authenticate a user against a GraphQL request - /// - public interface IGqlAuthorizationService - { - RequiredAuthorization? GetRequiredAuthFromExpression(LambdaExpression fieldSelection); - RequiredAuthorization GetRequiredAuthFromMember(MemberInfo field); - RequiredAuthorization GetRequiredAuthFromType(Type type); - bool IsAuthorized(ClaimsPrincipal? user, RequiredAuthorization? requiredAuthorization); - } + RequiredAuthorization? GetRequiredAuthFromExpression(LambdaExpression fieldSelection); + RequiredAuthorization GetRequiredAuthFromMember(MemberInfo field); + RequiredAuthorization GetRequiredAuthFromType(Type type); + bool IsAuthorized(ClaimsPrincipal? user, RequiredAuthorization? requiredAuthorization); } diff --git a/src/EntityGraphQL/Schema/ISchemaProvider.cs b/src/EntityGraphQL/Schema/ISchemaProvider.cs index 0b204325..6db72bd2 100644 --- a/src/EntityGraphQL/Schema/ISchemaProvider.cs +++ b/src/EntityGraphQL/Schema/ISchemaProvider.cs @@ -2,83 +2,82 @@ using System.Collections.Generic; using EntityGraphQL.Directives; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// An interface that the Compiler uses to help understand the types it is building against. This abstraction lets us +/// have a simple provider that maps directly to an object as well as other complex providers that read a schema from else where +/// and that can map them back to complex expressions. See SchemaProvider for implementation. +/// +/// It works with type name's as strings because although we ultimately build expressions against actual C# types the provider +/// might expose custom names for the underlying type. +/// +public interface ISchemaProvider { - /// - /// An interface that the Compiler uses to help understand the types it is building against. This abstraction lets us - /// have a simple provider that maps directly to an object as well as other complex providers that read a schema from else where - /// and that can map them back to complex expressions. See SchemaProvider for implementation. - /// - /// It works with type name's as strings because although we ultimately build expressions against actual C# types the provider - /// might expose custom names for the underlying type. - /// - public interface ISchemaProvider - { - Type QueryContextType { get; } - Type MutationType { get; } - Type SubscriptionType { get; } - Func SchemaFieldNamer { get; } - IGqlAuthorizationService AuthorizationService { get; set; } - string QueryContextName { get; } - IDictionary TypeConverters { get; } + Type QueryContextType { get; } + Type MutationType { get; } + Type SubscriptionType { get; } + Func SchemaFieldNamer { get; } + IGqlAuthorizationService AuthorizationService { get; set; } + string QueryContextName { get; } + IDictionary TypeConverters { get; } - void AddDirective(IDirectiveProcessor directive); - ISchemaType AddEnum(string name, Type type, string description); - SchemaType AddEnum(string name, string description); - ISchemaType AddInterface(string name, string? description); - ISchemaType AddInterface(Type type, string name, string? description); - ISchemaType AddUnion(Type type, string name, string? description); - SchemaType AddInputType(string name, string? description); - ISchemaType AddInputType(Type type, string name, string? description); - void AddMutationsFrom(SchemaBuilderOptions? options = null) - where TType : class; - ISchemaType AddScalarType(Type clrType, string gqlTypeName, string? description); - SchemaType AddScalarType(string gqlTypeName, string? description); - SchemaType AddType(string name, string? description); - ISchemaType AddType(Type contextType, string name, string? description); - SchemaType AddType(string name, string description, Action> updateFunc); - SchemaType AddType(string description); - ISchemaType AddType(ISchemaType schemaType); - void AddTypeMapping(string gqlType); - GqlTypeInfo? GetCustomTypeMapping(Type dotnetType); - IDirectiveProcessor GetDirective(string name); - IEnumerable GetDirectives(); - List GetEnumTypes(); - IEnumerable GetNonContextTypes(); - IEnumerable GetScalarTypes(); - IExtensionAttributeHandler? GetAttributeHandlerFor(Type attributeType); - ISchemaProvider AddAttributeHandler(IExtensionAttributeHandler handler); - ISchemaType GetSchemaType(string typeName, QueryRequestContext? requestContext); + void AddDirective(IDirectiveProcessor directive); + ISchemaType AddEnum(string name, Type type, string description); + SchemaType AddEnum(string name, string description); + ISchemaType AddInterface(string name, string? description); + ISchemaType AddInterface(Type type, string name, string? description); + ISchemaType AddUnion(Type type, string name, string? description); + SchemaType AddInputType(string name, string? description); + ISchemaType AddInputType(Type type, string name, string? description); + void AddMutationsFrom(SchemaBuilderOptions? options = null) + where TType : class; + ISchemaType AddScalarType(Type clrType, string gqlTypeName, string? description); + SchemaType AddScalarType(string gqlTypeName, string? description); + SchemaType AddType(string name, string? description); + ISchemaType AddType(Type contextType, string name, string? description); + SchemaType AddType(string name, string description, Action> updateFunc); + SchemaType AddType(string description); + ISchemaType AddType(ISchemaType schemaType); + void AddTypeMapping(string gqlType); + GqlTypeInfo? GetCustomTypeMapping(Type dotnetType); + IDirectiveProcessor GetDirective(string name); + IEnumerable GetDirectives(); + List GetEnumTypes(); + IEnumerable GetNonContextTypes(); + IEnumerable GetScalarTypes(); + IExtensionAttributeHandler? GetAttributeHandlerFor(Type attributeType); + ISchemaProvider AddAttributeHandler(IExtensionAttributeHandler handler); + ISchemaType GetSchemaType(string typeName, QueryRequestContext? requestContext); - [Obsolete("Use GetSchemaType(Type dotnetType, bool inputTypeScope, QueryRequestContext? requestContext) instead")] - ISchemaType GetSchemaType(Type dotnetType, QueryRequestContext? requestContext); - ISchemaType GetSchemaType(Type dotnetType, bool inputTypeScope, QueryRequestContext? requestContext); - bool TryGetSchemaType(Type dotnetType, bool inputTypeScope, out ISchemaType? schemaType, QueryRequestContext? requestContext); - bool HasType(string typeName); - bool HasType(Type type); - void PopulateFromContext(SchemaBuilderOptions? options = null); - ISchemaProvider RemoveType(); - ISchemaProvider RemoveType(string schemaType); - void RemoveTypeAndAllFields(); - void RemoveTypeAndAllFields(string typeName); - string ToGraphQLSchemaString(); - SchemaType Type(); - SchemaType Type(string typeName); - ISchemaType Type(string typeName); - ISchemaType Type(Type type); - SchemaType Type(Type type); - void UpdateType(Action> configure); - MutationType Mutation(); - SubscriptionType Subscription(); + [Obsolete("Use GetSchemaType(Type dotnetType, bool inputTypeScope, QueryRequestContext? requestContext) instead")] + ISchemaType GetSchemaType(Type dotnetType, QueryRequestContext? requestContext); + ISchemaType GetSchemaType(Type dotnetType, bool inputTypeScope, QueryRequestContext? requestContext); + bool TryGetSchemaType(Type dotnetType, bool inputTypeScope, out ISchemaType? schemaType, QueryRequestContext? requestContext); + bool HasType(string typeName); + bool HasType(Type type); + void PopulateFromContext(SchemaBuilderOptions? options = null); + ISchemaProvider RemoveType(); + ISchemaProvider RemoveType(string schemaType); + void RemoveTypeAndAllFields(); + void RemoveTypeAndAllFields(string typeName); + string ToGraphQLSchemaString(); + SchemaType Type(); + SchemaType Type(string typeName); + ISchemaType Type(string typeName); + ISchemaType Type(Type type); + SchemaType Type(Type type); + void UpdateType(Action> configure); + MutationType Mutation(); + SubscriptionType Subscription(); - /// - /// Call this to validate that the schema contains all the information it needs. As fields and Types can be added out of - /// order to the schema, this lets you validate that the schema is complete preventing you from getting the errors at - /// runtime during a query. - /// - /// Throws an EntityGraphQLCompilerException if the schema is not valid - /// - void Validate(); - ISchemaType CheckTypeAccess(ISchemaType schemaType, QueryRequestContext? requestContext); - } + /// + /// Call this to validate that the schema contains all the information it needs. As fields and Types can be added out of + /// order to the schema, this lets you validate that the schema is complete preventing you from getting the errors at + /// runtime during a query. + /// + /// Throws an EntityGraphQLCompilerException if the schema is not valid + /// + void Validate(); + ISchemaType CheckTypeAccess(ISchemaType schemaType, QueryRequestContext? requestContext); } diff --git a/src/EntityGraphQL/Schema/ISchemaType.cs b/src/EntityGraphQL/Schema/ISchemaType.cs index 0fcfc7e2..58886869 100644 --- a/src/EntityGraphQL/Schema/ISchemaType.cs +++ b/src/EntityGraphQL/Schema/ISchemaType.cs @@ -2,96 +2,95 @@ using System.Collections.Generic; using EntityGraphQL.Schema.Directives; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Represents a type in the schema +/// +public interface ISchemaType { /// - /// Represents a type in the schema + /// The Dotnet type the schema type maps to /// - public interface ISchemaType - { - /// - /// The Dotnet type the schema type maps to - /// - Type TypeDotnet { get; } + Type TypeDotnet { get; } - /// - /// The GraphQL type - Scalar, InputObject, Interface, Enum, Object, etc - /// - GqlTypes GqlType { get; } - string Name { get; } - string? Description { get; set; } + /// + /// The GraphQL type - Scalar, InputObject, Interface, Enum, Object, etc + /// + GqlTypes GqlType { get; } + string Name { get; } + string? Description { get; set; } - /// - /// True if GqlType is GqlTypeEnum.Input - /// - bool IsInput { get; } + /// + /// True if GqlType is GqlTypeEnum.Input + /// + bool IsInput { get; } - /// - /// True if GqlType is GqlTypeEnum.Interface - /// - bool IsInterface { get; } + /// + /// True if GqlType is GqlTypeEnum.Interface + /// + bool IsInterface { get; } - /// - /// True if GqlType is GqlTypeEnum.Enum - /// - bool IsEnum { get; } + /// + /// True if GqlType is GqlTypeEnum.Enum + /// + bool IsEnum { get; } - /// - /// True if GqlType is GqlTypeEnum.Scalar - /// - bool IsScalar { get; } + /// + /// True if GqlType is GqlTypeEnum.Scalar + /// + bool IsScalar { get; } - ISchemaProvider Schema { get; } + ISchemaProvider Schema { get; } - /// - /// If the type in a query requires a selection { } - /// - bool RequiresSelection { get; } - IList BaseTypesReadOnly { get; } - IList PossibleTypesReadOnly { get; } + /// + /// If the type in a query requires a selection { } + /// + bool RequiresSelection { get; } + IList BaseTypesReadOnly { get; } + IList PossibleTypesReadOnly { get; } - IList Directives { get; } - ISchemaType AddDirective(ISchemaDirective directive); - void ApplyAttributes(IEnumerable attributes); - RequiredAuthorization? RequiredAuthorization { get; set; } - IField GetField(string identifier, QueryRequestContext? requestContext); - bool GetField(string identifier, QueryRequestContext? requestContext, out IField? field); - IEnumerable GetFields(); - bool HasField(string identifier, QueryRequestContext? requestContext); - ISchemaType AddAllFields(SchemaBuilderOptions? options = null); - void AddFields(IEnumerable fields); - IField AddField(IField field); - void RemoveField(string name); + IList Directives { get; } + ISchemaType AddDirective(ISchemaDirective directive); + void ApplyAttributes(IEnumerable attributes); + RequiredAuthorization? RequiredAuthorization { get; set; } + IField GetField(string identifier, QueryRequestContext? requestContext); + bool GetField(string identifier, QueryRequestContext? requestContext, out IField? field); + IEnumerable GetFields(); + bool HasField(string identifier, QueryRequestContext? requestContext); + ISchemaType AddAllFields(SchemaBuilderOptions? options = null); + void AddFields(IEnumerable fields); + IField AddField(IField field); + void RemoveField(string name); - /// - /// Searches the dotnet type for any interfaces or base type and marks this schema type as implementing those interfaces in the schema. - /// - /// If true and the TClrType type is not already in the schema it will be added as an interface. If the type is in the schema it must be an interface - /// If true and addTypeIfNotInSchema = true and the type is added by this method (was not - /// - ISchemaType ImplementAllBaseTypes(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); - /// - /// Tells the schema that this type implements another type of TClrType. - /// - /// The dotnet type this schema type implements - /// If true and the TClrType type is not already in the schema it will be added as an interface. If the type is in the schema it must be an interface - /// If true and addTypeIfNotInSchema = true and the type is added by this method (was not - /// in the schema before), all the fields on the implemented type will be added to the schema. e.g. .AddAllFields() is called on - /// the added type - /// + /// + /// Searches the dotnet type for any interfaces or base type and marks this schema type as implementing those interfaces in the schema. + /// + /// If true and the TClrType type is not already in the schema it will be added as an interface. If the type is in the schema it must be an interface + /// If true and addTypeIfNotInSchema = true and the type is added by this method (was not + /// + ISchemaType ImplementAllBaseTypes(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); + /// + /// Tells the schema that this type implements another type of TClrType. + /// + /// The dotnet type this schema type implements + /// If true and the TClrType type is not already in the schema it will be added as an interface. If the type is in the schema it must be an interface + /// If true and addTypeIfNotInSchema = true and the type is added by this method (was not + /// in the schema before), all the fields on the implemented type will be added to the schema. e.g. .AddAllFields() is called on + /// the added type + /// #pragma warning disable CA1716 - ISchemaType Implements(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); + ISchemaType Implements(bool addTypeIfNotInSchema = true, bool addAllFieldsOnAddedType = true); - /// - /// Tells the schema that this type implements another type of typeName. typeName needs to be an interface type existing in the schema - /// - /// - /// - ISchemaType Implements(string typeName); + /// + /// Tells the schema that this type implements another type of typeName. typeName needs to be an interface type existing in the schema + /// + /// + /// + ISchemaType Implements(string typeName); #pragma warning restore CA1716 - void Validate(object? value); + void Validate(object? value); - public event Action OnAddField; - public event Action OnValidate; - } + public event Action OnAddField; + public event Action OnValidate; } diff --git a/src/EntityGraphQL/Schema/MethodField.cs b/src/EntityGraphQL/Schema/MethodField.cs index aabb87ac..fb53aad2 100644 --- a/src/EntityGraphQL/Schema/MethodField.cs +++ b/src/EntityGraphQL/Schema/MethodField.cs @@ -160,15 +160,11 @@ ExecutionOptions executionOptions throw new EntityGraphQLValidationException(validationErrors); } - object? instance = null; // we create an instance _per request_ injecting any parameters to the constructor // We kind of treat a mutation class like an asp.net controller // and we do not want to register them in the service provider to avoid the same issues controllers would have // with different lifetime objects - if (instance == null) - { - instance = serviceProvider != null ? ActivatorUtilities.CreateInstance(serviceProvider, Method.DeclaringType!) : Activator.CreateInstance(Method.DeclaringType!); - } + object? instance = serviceProvider != null ? ActivatorUtilities.CreateInstance(serviceProvider, Method.DeclaringType!) : Activator.CreateInstance(Method.DeclaringType!); object? result; if (IsAsync) diff --git a/src/EntityGraphQL/Schema/Models/Introspection.cs b/src/EntityGraphQL/Schema/Models/Introspection.cs index 0ed944d1..4e8974d6 100644 --- a/src/EntityGraphQL/Schema/Models/Introspection.cs +++ b/src/EntityGraphQL/Schema/Models/Introspection.cs @@ -1,126 +1,125 @@ using System; using System.Collections.Generic; -namespace EntityGraphQL.Schema.Models +namespace EntityGraphQL.Schema.Models; + +public partial class Schema { - public partial class Schema + public Schema(TypeElement queryType, TypeElement? mutationType, TypeElement? subscriptionType, List types, List directives) { - public Schema(TypeElement queryType, TypeElement? mutationType, TypeElement? subscriptionType, List types, List directives) - { - QueryType = queryType; - MutationType = mutationType; - SubscriptionType = subscriptionType; - Types = types; - Directives = directives; - } + QueryType = queryType; + MutationType = mutationType; + SubscriptionType = subscriptionType; + Types = types; + Directives = directives; + } - public TypeElement QueryType { get; private set; } + public TypeElement QueryType { get; private set; } - public TypeElement? MutationType { get; private set; } + public TypeElement? MutationType { get; private set; } - public TypeElement? SubscriptionType { get; private set; } + public TypeElement? SubscriptionType { get; private set; } - public List Types { get; private set; } + public List Types { get; private set; } - public List Directives { get; private set; } - } + public List Directives { get; private set; } +} - public partial class TypeElement - { - public TypeElement() { } +public partial class TypeElement +{ + public TypeElement() { } - public TypeElement(string? kind, string? name) - { - Kind = kind; - Name = name; - } + public TypeElement(string? kind, string? name) + { + Kind = kind; + Name = name; + } - public string? Kind { get; set; } + public string? Kind { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - // Fields is added dynamically so it is lazily loaded + // Fields is added dynamically so it is lazily loaded - public InputValue[] InputFields { get; set; } = []; + public InputValue[] InputFields { get; set; } = []; - public TypeElement[] Interfaces { get; set; } = []; + public TypeElement[] Interfaces { get; set; } = []; - public EnumValue[] EnumValues { get; set; } = []; + public EnumValue[] EnumValues { get; set; } = []; - public TypeElement[] PossibleTypes { get; set; } = []; - public TypeElement? OfType { get; set; } - public bool OneField { get; set; } + public TypeElement[] PossibleTypes { get; set; } = []; + public TypeElement? OfType { get; set; } + public bool OneField { get; set; } - // may be non-null for custom SCALAR, otherwise null. - public string? SpecifiedByURL { get; set; } - } + // may be non-null for custom SCALAR, otherwise null. + public string? SpecifiedByURL { get; set; } +} - public partial class Field +public partial class Field +{ + public Field(string name, TypeElement type) { - public Field(string name, TypeElement type) - { - Name = name; - Type = type; - } + Name = name; + Type = type; + } - public string Name { get; private set; } + public string Name { get; private set; } - public string? Description { get; set; } + public string? Description { get; set; } - public IEnumerable Args { get; set; } = Array.Empty(); + public IEnumerable Args { get; set; } = Array.Empty(); - public TypeElement Type { get; private set; } + public TypeElement Type { get; private set; } - public bool IsDeprecated { get; set; } + public bool IsDeprecated { get; set; } - public string? DeprecationReason { get; set; } - } + public string? DeprecationReason { get; set; } +} - public class InputValue +public class InputValue +{ + public InputValue(string name, TypeElement type) { - public InputValue(string name, TypeElement type) - { - Name = name; - Type = type; - } - - public string Name { get; private set; } - public string? Description { get; set; } - public TypeElement Type { get; private set; } - public string? DefaultValue { get; set; } + Name = name; + Type = type; } - public partial class Directive + public string Name { get; private set; } + public string? Description { get; set; } + public TypeElement Type { get; private set; } + public string? DefaultValue { get; set; } +} + +public partial class Directive +{ + public Directive(string name) { - public Directive(string name) - { - Name = name; - } + Name = name; + } - public string Name { get; private set; } + public string Name { get; private set; } - public string? Description { get; set; } + public string? Description { get; set; } - public IEnumerable Locations { get; set; } = Array.Empty(); + public IEnumerable Locations { get; set; } = Array.Empty(); - public IEnumerable Args { get; set; } = Array.Empty(); - } + public IEnumerable Args { get; set; } = Array.Empty(); +} - public partial class EnumValue +public partial class EnumValue +{ + public EnumValue(string name) { - public EnumValue(string name) - { - Name = name; - } + Name = name; + } - public string Name { get; private set; } + public string Name { get; private set; } - public string? Description { get; set; } + public string? Description { get; set; } - public bool IsDeprecated { get; set; } + public bool IsDeprecated { get; set; } - public string? DeprecationReason { get; set; } - } + public string? DeprecationReason { get; set; } } diff --git a/src/EntityGraphQL/Schema/MutationField.cs b/src/EntityGraphQL/Schema/MutationField.cs index 66246f26..a4f70a23 100644 --- a/src/EntityGraphQL/Schema/MutationField.cs +++ b/src/EntityGraphQL/Schema/MutationField.cs @@ -1,22 +1,21 @@ using System.Reflection; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public class MutationField : MethodField { - public class MutationField : MethodField - { - public override GraphQLQueryFieldType FieldType { get; } = GraphQLQueryFieldType.Mutation; + public override GraphQLQueryFieldType FieldType { get; } = GraphQLQueryFieldType.Mutation; - public MutationField( - ISchemaProvider schema, - ISchemaType fromType, - string methodName, - GqlTypeInfo returnType, - MethodInfo method, - string description, - RequiredAuthorization requiredAuth, - bool isAsync, - SchemaBuilderOptions options - ) - : base(schema, fromType, methodName, returnType, method, description, requiredAuth, isAsync, options) { } - } + public MutationField( + ISchemaProvider schema, + ISchemaType fromType, + string methodName, + GqlTypeInfo returnType, + MethodInfo method, + string description, + RequiredAuthorization requiredAuth, + bool isAsync, + SchemaBuilderOptions options + ) + : base(schema, fromType, methodName, returnType, method, description, requiredAuth, isAsync, options) { } } diff --git a/src/EntityGraphQL/Schema/QueryCache.cs b/src/EntityGraphQL/Schema/QueryCache.cs index d66b5e4c..5d90d8d6 100644 --- a/src/EntityGraphQL/Schema/QueryCache.cs +++ b/src/EntityGraphQL/Schema/QueryCache.cs @@ -4,66 +4,65 @@ using System.Text; using EntityGraphQL.Compiler; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public class QueryCache : IDisposable { - public class QueryCache : IDisposable - { - private readonly MemoryCache cache; + private readonly MemoryCache cache; - public QueryCache() - { - cache = new MemoryCache("EntityGraphQL.QueryCache"); - } + public QueryCache() + { + cache = new MemoryCache("EntityGraphQL.QueryCache"); + } - public (GraphQLDocument?, string) GetCompiledQuery(string query, string? hash) - { - hash ??= ComputeHash(query); - var cached = GetCompiledQueryWithHash(hash); - return (cached, hash); - } + public (GraphQLDocument?, string) GetCompiledQuery(string query, string? hash) + { + hash ??= ComputeHash(query); + var cached = GetCompiledQueryWithHash(hash); + return (cached, hash); + } - public GraphQLDocument? GetCompiledQueryWithHash(string hash) - { - var cached = (GraphQLDocument?)cache.Get(hash); - return cached; - } + public GraphQLDocument? GetCompiledQueryWithHash(string hash) + { + var cached = (GraphQLDocument?)cache.Get(hash); + return cached; + } - public void AddCompiledQuery(string hash, GraphQLDocument compiledQuery) - { - cache.Add(hash, compiledQuery, new CacheItemPolicy { SlidingExpiration = new System.TimeSpan(0, 10, 0) }); - } + public void AddCompiledQuery(string hash, GraphQLDocument compiledQuery) + { + cache.Add(hash, compiledQuery, new CacheItemPolicy { SlidingExpiration = new System.TimeSpan(0, 10, 0) }); + } - public static string ComputeHash(string data) - { - using SHA256 sha256Hash = SHA256.Create(); - // ComputeHash - returns byte array + public static string ComputeHash(string data) + { + using SHA256 sha256Hash = SHA256.Create(); + // ComputeHash - returns byte array #if NET8_0_OR_GREATER - byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(data)); + byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(data)); #else - byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(data)); + byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(data)); #endif - return ByteToHexBitFiddle(bytes); - } + return ByteToHexBitFiddle(bytes); + } - // thanks https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/14333437#14333437 - private static string ByteToHexBitFiddle(byte[] bytes) + // thanks https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/14333437#14333437 + private static string ByteToHexBitFiddle(byte[] bytes) + { + char[] c = new char[bytes.Length * 2]; + int b; + for (int i = 0; i < bytes.Length; i++) { - char[] c = new char[bytes.Length * 2]; - int b; - for (int i = 0; i < bytes.Length; i++) - { - b = bytes[i] >> 4; - c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); - b = bytes[i] & 0xF; - c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); - } - return new string(c); + b = bytes[i] >> 4; + c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); + b = bytes[i] & 0xF; + c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); } + return new string(c); + } - public void Dispose() - { - GC.SuppressFinalize(this); - cache.Dispose(); - } + public void Dispose() + { + GC.SuppressFinalize(this); + cache.Dispose(); } } diff --git a/src/EntityGraphQL/Schema/RequiredAuthorization.cs b/src/EntityGraphQL/Schema/RequiredAuthorization.cs index 1231f18b..36c98543 100644 --- a/src/EntityGraphQL/Schema/RequiredAuthorization.cs +++ b/src/EntityGraphQL/Schema/RequiredAuthorization.cs @@ -1,78 +1,71 @@ using System.Collections.Generic; using System.Linq; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// Details on the authorisation required by a field or type +/// +public class RequiredAuthorization { /// - /// Details on the authorisation required by a field or type + /// Each item in the "first" list is AND claims and each in the inner list is OR claims + /// This means [Authorize(Roles = "Blah,Blah2")] is either of those roles + /// and + /// [Authorize(Roles = "Blah")] + /// [Authorize(Roles = "Blah2")] is both of those roles /// - public class RequiredAuthorization - { - /// - /// Each item in the "first" list is AND claims and each in the inner list is OR claims - /// This means [Authorize(Roles = "Blah,Blah2")] is either of those roles - /// and - /// [Authorize(Roles = "Blah")] - /// [Authorize(Roles = "Blah2")] is both of those roles - /// - private readonly List> requiredPolicies; - public IEnumerable> Policies - { - get => requiredPolicies; - } - private readonly List> requiredRoles; - public IEnumerable> Roles - { - get => requiredRoles; - } + private readonly List> requiredPolicies; + public IEnumerable> Policies => requiredPolicies; + private readonly List> requiredRoles; + public IEnumerable> Roles => requiredRoles; - public RequiredAuthorization() - { - requiredPolicies = new List>(); - requiredRoles = new List>(); - } + public RequiredAuthorization() + { + requiredPolicies = []; + requiredRoles = []; + } - /// - /// Create a new RequiredAuthorization object from a list of roles and/or policies - /// - /// Roles required - /// ASP.NET policies requried - public RequiredAuthorization(IEnumerable>? roles, IEnumerable>? policies) - { - requiredRoles = roles?.ToList() ?? new List>(); - requiredPolicies = policies?.ToList() ?? new List>(); - } + /// + /// Create a new RequiredAuthorization object from a list of roles and/or policies + /// + /// Roles required + /// ASP.NET policies requried + public RequiredAuthorization(IEnumerable>? roles, IEnumerable>? policies) + { + requiredRoles = roles?.ToList() ?? []; + requiredPolicies = policies?.ToList() ?? []; + } - public bool Any() => requiredPolicies.Count > 0 || requiredRoles.Count > 0; + public bool Any() => requiredPolicies.Count > 0 || requiredRoles.Count > 0; - public void RequiresAnyRole(params string[] roles) - { - requiredRoles.Add(roles.ToList()); - } + public void RequiresAnyRole(params string[] roles) + { + requiredRoles.Add(roles.ToList()); + } - public void RequiresAllRoles(params string[] roles) - { - requiredRoles.AddRange(roles.Select(s => new List { s })); - } + public void RequiresAllRoles(params string[] roles) + { + requiredRoles.AddRange(roles.Select(s => new List { s })); + } - public void RequiresAnyPolicy(params string[] policies) - { - requiredPolicies.Add(policies.ToList()); - } + public void RequiresAnyPolicy(params string[] policies) + { + requiredPolicies.Add(policies.ToList()); + } - public void RequiresAllPolicies(params string[] policies) - { - requiredPolicies.AddRange(policies.Select(s => new List { s })); - } + public void RequiresAllPolicies(params string[] policies) + { + requiredPolicies.AddRange(policies.Select(s => new List { s })); + } - public RequiredAuthorization Concat(RequiredAuthorization requiredAuthorization) - { - var newRequiredAuthorization = new RequiredAuthorization(); - newRequiredAuthorization.requiredPolicies.AddRange(requiredPolicies); - newRequiredAuthorization.requiredPolicies.AddRange(requiredAuthorization.requiredPolicies); - newRequiredAuthorization.requiredRoles.AddRange(requiredRoles); - newRequiredAuthorization.requiredRoles.AddRange(requiredAuthorization.requiredRoles); - return newRequiredAuthorization; - } + public RequiredAuthorization Concat(RequiredAuthorization requiredAuthorization) + { + var newRequiredAuthorization = new RequiredAuthorization(); + newRequiredAuthorization.requiredPolicies.AddRange(requiredPolicies); + newRequiredAuthorization.requiredPolicies.AddRange(requiredAuthorization.requiredPolicies); + newRequiredAuthorization.requiredRoles.AddRange(requiredRoles); + newRequiredAuthorization.requiredRoles.AddRange(requiredAuthorization.requiredRoles); + return newRequiredAuthorization; } } diff --git a/src/EntityGraphQL/Schema/SchemaBuilder.cs b/src/EntityGraphQL/Schema/SchemaBuilder.cs index 217c1c4a..d27e3a14 100644 --- a/src/EntityGraphQL/Schema/SchemaBuilder.cs +++ b/src/EntityGraphQL/Schema/SchemaBuilder.cs @@ -13,644 +13,632 @@ using Microsoft.Extensions.Logging; using Nullability; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// A simple schema provider to automatically create a query schema based on an object. +/// Commonly used with a DbContext. +/// +public static class SchemaBuilder { /// - /// A simple schema provider to automatically create a query schema based on an object. - /// Commonly used with a DbContext. + /// Apply any options not passed via the constructor /// - public static class SchemaBuilder + private static SchemaProvider ApplyOptions(SchemaProvider schema, SchemaBuilderSchemaOptions options) { - /// - /// Apply any options not passed via the constructor - /// - private static SchemaProvider ApplyOptions(SchemaProvider schema, SchemaBuilderSchemaOptions options) - { - schema.AllowedExceptions.AddRange(options.AllowedExceptions); - return schema; - } + schema.AllowedExceptions.AddRange(options.AllowedExceptions); + return schema; + } - /// - /// Create a new SchemaProvider with the query context of type TContext and using the SchemaBuilderSchemaOptions supplied or the default if null. - /// Note the schema is empty, you need to add types and fields. - /// - /// Query context type - /// SchemaBuilderSchemaOptions to configure the options of the schema provider created - /// A logger to use in the schema - /// - public static SchemaProvider Create(SchemaBuilderSchemaOptions? options = null, ILogger>? logger = null) - { - options ??= new SchemaBuilderSchemaOptions(); - var schema = new SchemaProvider(options.AuthorizationService, options.FieldNamer, logger, options.IntrospectionEnabled, options.IsDevelopment); - return ApplyOptions(schema, options); - } + /// + /// Create a new SchemaProvider with the query context of type TContext and using the SchemaBuilderSchemaOptions supplied or the default if null. + /// Note the schema is empty, you need to add types and fields. + /// + /// Query context type + /// SchemaBuilderSchemaOptions to configure the options of the schema provider created + /// A logger to use in the schema + /// + public static SchemaProvider Create(SchemaBuilderSchemaOptions? options = null, ILogger>? logger = null) + { + options ??= new SchemaBuilderSchemaOptions(); + var schema = new SchemaProvider(options.AuthorizationService, options.FieldNamer, logger, options.IntrospectionEnabled, options.IsDevelopment); + return ApplyOptions(schema, options); + } - /// - /// Given the type TContextType recursively create a query schema based on the public properties of the object. - /// - /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields - /// A logger to use in the schema - /// - /// - public static SchemaProvider FromObject(SchemaBuilderOptions? buildOptions = null, ILogger>? logger = null) - { - buildOptions ??= new SchemaBuilderOptions(); - var schemaOptions = new SchemaBuilderSchemaOptions(); + /// + /// Given the type TContextType recursively create a query schema based on the public properties of the object. + /// + /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields + /// A logger to use in the schema + /// + /// + public static SchemaProvider FromObject(SchemaBuilderOptions? buildOptions = null, ILogger>? logger = null) + { + buildOptions ??= new SchemaBuilderOptions(); + var schemaOptions = new SchemaBuilderSchemaOptions(); - var schema = new SchemaProvider(schemaOptions.AuthorizationService, schemaOptions.FieldNamer, logger, schemaOptions.IntrospectionEnabled, schemaOptions.IsDevelopment); - schema = ApplyOptions(schema, schemaOptions); - return FromObject(schema, buildOptions); - } + var schema = new SchemaProvider(schemaOptions.AuthorizationService, schemaOptions.FieldNamer, logger, schemaOptions.IntrospectionEnabled, schemaOptions.IsDevelopment); + schema = ApplyOptions(schema, schemaOptions); + return FromObject(schema, buildOptions); + } - /// - /// Given the type TContextType recursively create a query schema based on the public properties of the object. - /// - /// Options to create the SchemaProvider. - /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields - /// A logger to use in the schema - /// - /// - public static SchemaProvider FromObject( - SchemaBuilderSchemaOptions? schemaOptions, - SchemaBuilderOptions? buildOptions = null, - ILogger>? logger = null - ) + /// + /// Given the type TContextType recursively create a query schema based on the public properties of the object. + /// + /// Options to create the SchemaProvider. + /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields + /// A logger to use in the schema + /// + /// + public static SchemaProvider FromObject( + SchemaBuilderSchemaOptions? schemaOptions, + SchemaBuilderOptions? buildOptions = null, + ILogger>? logger = null + ) + { + buildOptions ??= new SchemaBuilderOptions(); + schemaOptions ??= new SchemaBuilderSchemaOptions(); + + var schema = new SchemaProvider(schemaOptions.AuthorizationService, schemaOptions.FieldNamer, logger, schemaOptions.IntrospectionEnabled, schemaOptions.IsDevelopment); + schemaOptions.PreBuildSchemaFromContext?.Invoke(schema); + schema = ApplyOptions(schema, schemaOptions); + return FromObject(schema, buildOptions); + } + + /// + /// Given the type TContextType recursively create a query schema based on the public properties of the object. Schema is added into the provider schema + /// + /// Schema to add types to. + /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields + /// + /// + internal static SchemaProvider FromObject(SchemaProvider schema, SchemaBuilderOptions options) + { + var contextType = typeof(TContextType); + var rootFields = GetFieldsFromObject(contextType, schema.Query(), schema, options, false); + foreach (var f in rootFields) { - if (buildOptions == null) - buildOptions = new SchemaBuilderOptions(); - if (schemaOptions == null) - schemaOptions = new SchemaBuilderSchemaOptions(); - - var schema = new SchemaProvider(schemaOptions.AuthorizationService, schemaOptions.FieldNamer, logger, schemaOptions.IntrospectionEnabled, schemaOptions.IsDevelopment); - schemaOptions.PreBuildSchemaFromContext?.Invoke(schema); - schema = ApplyOptions(schema, schemaOptions); - return FromObject(schema, buildOptions); + schema.Query().AddField(f); } + return schema; + } + + private static Field? MakeFieldWithIdArgumentIfExists(ISchemaProvider schema, ISchemaType schemaType, Type contextType, Field fieldProp, SchemaBuilderOptions options) + { + if (fieldProp.ResolveExpression == null) + throw new ArgumentException($"Field '{fieldProp.Name}' does not have a resolve function. This is required for AutoCreateIdArguments to work."); + if (!fieldProp.ResolveExpression.Type.IsEnumerableOrArray()) + return null; + var returnSchemaType = fieldProp.ReturnType.SchemaType; + var idFieldDef = returnSchemaType.GetFields().FirstOrDefault(f => f.Name == "id"); + if (idFieldDef == null) + return null; - /// - /// Given the type TContextType recursively create a query schema based on the public properties of the object. Schema is added into the provider schema - /// - /// Schema to add types to. - /// SchemaBuilderOptions to use to create the SchemaProvider and configure the rules for auto creating the schema types and fields - /// - /// - internal static SchemaProvider FromObject(SchemaProvider schema, SchemaBuilderOptions options) + if (idFieldDef.ResolveExpression == null) + throw new ArgumentException($"Field '{idFieldDef.Name}' does not have a resolve function. This is required for AutoCreateIdArguments to work."); + + // We need to build an anonymous type with id = RequiredField() + // Resulting lambda is (a, p) => a.Where(b => b.Id == p.Id).First() + // This allows us to "insert" .Select() (and .Include()) before the .First() + var requiredFieldType = typeof(RequiredField<>).MakeGenericType(idFieldDef.ResolveExpression.Type); + var fieldNameAndType = new Dictionary { { "id", requiredFieldType } }; + var argTypes = LinqRuntimeTypeBuilder.GetDynamicType(fieldNameAndType, fieldProp.Name); + var argTypesValue = Activator.CreateInstance(argTypes); + var argTypeParam = Expression.Parameter(argTypes, $"args_{argTypes.Name}"); + Type arrayContextType = returnSchemaType.TypeDotnet; + var arrayContextParam = Expression.Parameter(arrayContextType, $"arrcxt_{arrayContextType.Name}"); + var ctxId = Expression.PropertyOrField(arrayContextParam, "Id"); + Expression argId = Expression.PropertyOrField(argTypeParam, "id"); + argId = Expression.Property(argId, "Value"); // call RequiredField<>.Value to get the real type without a convert + var idBody = Expression.MakeBinary(ExpressionType.Equal, ctxId, argId); + var idLambda = Expression.Lambda(idBody, new[] { arrayContextParam }); + Expression body = ExpressionUtil.MakeCallOnQueryable(nameof(Queryable.Where), [arrayContextType], fieldProp.ResolveExpression, idLambda); + + body = ExpressionUtil.MakeCallOnQueryable(nameof(Queryable.FirstOrDefault), [arrayContextType], body); + var contextParam = Expression.Parameter(contextType, $"cxt_{contextType.Name}"); + var lambdaParams = new[] { contextParam, argTypeParam }; + body = new ParameterReplacer().Replace(body, fieldProp.FieldParam!, contextParam); + var selectionExpression = Expression.Lambda(body, lambdaParams); + var name = fieldProp.Name.Singularize(); + if (name == null || name == fieldProp.Name) { - var contextType = typeof(TContextType); - var rootFields = GetFieldsFromObject(contextType, schema.Query(), schema, options, false); - foreach (var f in rootFields) - { - schema.Query().AddField(f); - } - return schema; + // If we can't singularize it (or it returns the same name) just use the name plus something as GraphQL doesn't support field overloads + name = $"{fieldProp.Name}ById"; } + var f = new Field( + schema, + schemaType, + name, + selectionExpression, + $"Return a {fieldProp.ReturnType.SchemaType.Name} by its Id", + argTypesValue, + new GqlTypeInfo(fieldProp.ReturnType.SchemaTypeGetter, selectionExpression.Body.Type), + fieldProp.RequiredAuthorization + ); + options.OnFieldCreated?.Invoke(f); + return f; + } + + public static List GetFieldsFromObject(Type type, ISchemaType fromType, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) + { + var fields = new List(); + // cache fields/properties + var param = Expression.Parameter(type, $"p_{type.Name}"); + if (type.IsArray || type.IsEnumerableOrArray()) + return fields; - private static Field? MakeFieldWithIdArgumentIfExists(ISchemaProvider schema, ISchemaType schemaType, Type contextType, Field fieldProp, SchemaBuilderOptions options) + foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - if (fieldProp.ResolveExpression == null) - throw new ArgumentException($"Field '{fieldProp.Name}' does not have a resolve function. This is required for AutoCreateIdArguments to work."); - if (!fieldProp.ResolveExpression.Type.IsEnumerableOrArray()) - return null; - var returnSchemaType = fieldProp.ReturnType.SchemaType; - var idFieldDef = returnSchemaType.GetFields().FirstOrDefault(f => f.Name == "id"); - if (idFieldDef == null) - return null; - - if (idFieldDef.ResolveExpression == null) - throw new ArgumentException($"Field '{idFieldDef.Name}' does not have a resolve function. This is required for AutoCreateIdArguments to work."); - - // We need to build an anonymous type with id = RequiredField() - // Resulting lambda is (a, p) => a.Where(b => b.Id == p.Id).First() - // This allows us to "insert" .Select() (and .Include()) before the .First() - var requiredFieldType = typeof(RequiredField<>).MakeGenericType(idFieldDef.ResolveExpression.Type); - var fieldNameAndType = new Dictionary { { "id", requiredFieldType } }; - var argTypes = LinqRuntimeTypeBuilder.GetDynamicType(fieldNameAndType, fieldProp.Name); - var argTypesValue = Activator.CreateInstance(argTypes); - var argTypeParam = Expression.Parameter(argTypes, $"args_{argTypes.Name}"); - Type arrayContextType = returnSchemaType.TypeDotnet; - var arrayContextParam = Expression.Parameter(arrayContextType, $"arrcxt_{arrayContextType.Name}"); - var ctxId = Expression.PropertyOrField(arrayContextParam, "Id"); - Expression argId = Expression.PropertyOrField(argTypeParam, "id"); - argId = Expression.Property(argId, "Value"); // call RequiredField<>.Value to get the real type without a convert - var idBody = Expression.MakeBinary(ExpressionType.Equal, ctxId, argId); - var idLambda = Expression.Lambda(idBody, new[] { arrayContextParam }); - Expression body = ExpressionUtil.MakeCallOnQueryable(nameof(Queryable.Where), [arrayContextType], fieldProp.ResolveExpression, idLambda); - - body = ExpressionUtil.MakeCallOnQueryable(nameof(Queryable.FirstOrDefault), [arrayContextType], body); - var contextParam = Expression.Parameter(contextType, $"cxt_{contextType.Name}"); - var lambdaParams = new[] { contextParam, argTypeParam }; - body = new ParameterReplacer().Replace(body, fieldProp.FieldParam!, contextParam); - var selectionExpression = Expression.Lambda(body, lambdaParams); - var name = fieldProp.Name.Singularize(); - if (name == null || name == fieldProp.Name) - { - // If we can't singularize it (or it returns the same name) just use the name plus something as GraphQL doesn't support field overloads - name = $"{fieldProp.Name}ById"; - } - var f = new Field( - schema, - schemaType, - name, - selectionExpression, - $"Return a {fieldProp.ReturnType.SchemaType.Name} by its Id", - argTypesValue, - new GqlTypeInfo(fieldProp.ReturnType.SchemaTypeGetter, selectionExpression.Body.Type), - fieldProp.RequiredAuthorization - ); - options.OnFieldCreated?.Invoke(f); - return f; + var f = ProcessFieldOrPropertyIntoField(fromType, prop, param, schema, options, isInputType)?.ToList(); + if (f != null) + fields.AddRange(f); } - - public static List GetFieldsFromObject(Type type, ISchemaType fromType, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) + foreach (var prop in type.GetFields(BindingFlags.Instance | BindingFlags.Public)) { - var fields = new List(); - // cache fields/properties - var param = Expression.Parameter(type, $"p_{type.Name}"); - if (type.IsArray || type.IsEnumerableOrArray()) - return fields; - - foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - var f = ProcessFieldOrPropertyIntoField(fromType, prop, param, schema, options, isInputType)?.ToList(); - if (f != null) - fields.AddRange(f); - } - foreach (var prop in type.GetFields(BindingFlags.Instance | BindingFlags.Public)) - { - var f = ProcessFieldOrPropertyIntoField(fromType, prop, param, schema, options, isInputType)?.ToList(); - if (f != null) - fields.AddRange(f); - } - foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static)) - { - var attribute = method.GetCustomAttribute(); - if (attribute == null) - continue; + var f = ProcessFieldOrPropertyIntoField(fromType, prop, param, schema, options, isInputType)?.ToList(); + if (f != null) + fields.AddRange(f); + } + foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static)) + { + var attribute = method.GetCustomAttribute(); + if (attribute == null) + continue; - var f = ProcessMethodIntoField(fromType, method, param, schema, options, isInputType); + var f = ProcessMethodIntoField(fromType, method, param, schema, options, isInputType); - if (f != null) - fields.Add(f); - } - return fields; + if (f != null) + fields.Add(f); } + return fields; + } - internal static BaseField? ProcessMethodIntoField(ISchemaType fromType, MethodInfo method, ParameterExpression param, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) - { - if (!ShouldIncludeMember(method, options, isInputType)) - return null; + internal static BaseField? ProcessMethodIntoField(ISchemaType fromType, MethodInfo method, ParameterExpression param, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) + { + if (!ShouldIncludeMember(method, options, isInputType)) + return null; - (string name, string description) = GetNameAndDescription(method, schema); + (string name, string description) = GetNameAndDescription(method, schema); - options ??= new SchemaBuilderOptions(); - var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); - var requiredClaims = schema.AuthorizationService.GetRequiredAuthFromMember(method); + options ??= new SchemaBuilderOptions(); + var isAsync = method.GetCustomAttribute() != null || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); + var requiredClaims = schema.AuthorizationService.GetRequiredAuthFromMember(method); - LambdaExpression? le = null; - // Fields for the schema (no services to [GraphQLArguments] that get expanded) - Dictionary? fieldSchemaArgs = null; - Dictionary fieldServices = new(); - Dictionary typesFlattenToSchema = new(); - Type? fieldArgType = null; + LambdaExpression? le = null; + // Fields for the schema (no services to [GraphQLArguments] that get expanded) + Dictionary? fieldSchemaArgs = null; + Dictionary fieldServices = []; + Dictionary typesFlattenToSchema = []; + Type? fieldArgType = null; - if (method.GetParameters().Length > 0) + if (method.GetParameters().Length > 0) + { + // This needs args, services and the [GraphQLArguments] types (not the expanded ones on the schema) + var argsForCallExpression = new Dictionary(); + // Arg type for the created type that holds all the method args + // this is current how query field args work. They expect 1 type that has all the schema args as props + var fieldDotnetArgtypes = new Dictionary(); + fieldSchemaArgs = []; + // typesFlattenToSchema is the dotnet types that were flattened to the GQL schema args from [GraphQLArguments] + var argumentsFromMethod = GetGraphQlSchemaArgumentsFromMethod(schema, method, options, out typesFlattenToSchema); + // figure out the arg type + foreach (var item in argumentsFromMethod) { - // This needs args, services and the [GraphQLArguments] types (not the expanded ones on the schema) - var argsForCallExpression = new Dictionary(); - // Arg type for the created type that holds all the method args - // this is current how query field args work. They expect 1 type that has all the schema args as props - var fieldDotnetArgtypes = new Dictionary(); - fieldSchemaArgs = new Dictionary(); - // typesFlattenToSchema is the dotnet types that were flattened to the GQL schema args from [GraphQLArguments] - var argumentsFromMethod = GetGraphQlSchemaArgumentsFromMethod(schema, method, options, out typesFlattenToSchema); - // figure out the arg type - foreach (var item in argumentsFromMethod) + if (item.IsService) + continue; + if (item.ShouldFlatten) { - if (item.IsService) - continue; - if (item.ShouldFlatten) - { - foreach (var arg in item.FlattenArgs!) - { - fieldSchemaArgs.Add(arg.ArgName, arg.ArgType!); - fieldDotnetArgtypes.Add(arg.ArgName, arg.ArgType!.RawType); - } - } - else + foreach (var arg in item.FlattenArgs!) { - fieldSchemaArgs.Add(item.ArgName, item.ArgType!); - fieldDotnetArgtypes.Add(item.ArgName, item.ArgType!.RawType); + fieldSchemaArgs.Add(arg.ArgName, arg.ArgType!); + fieldDotnetArgtypes.Add(arg.ArgName, arg.ArgType!.RawType); } } - fieldArgType = LinqRuntimeTypeBuilder.GetDynamicType(fieldDotnetArgtypes, method.Name)!; - var argTypeParam = Expression.Parameter(fieldArgType, $"args_{fieldArgType.Name}"); - // build argument expressions for the call - foreach (var item in argumentsFromMethod) + else { - if (item.IsService) - { - fieldServices.Add(item.ArgName, Expression.Parameter(item.ServiceType!, item.ArgName)); - argsForCallExpression.Add(item.ArgName, fieldServices[item.ArgName]!); - } - else if (item.ShouldFlatten) - { - // need to create expression new ArgType { Prop1 = args.Prop1, Prop2 = args.Prop2 } - var propExpressions = new Dictionary(); - foreach (var arg in item.FlattenArgs!) - { - propExpressions.Add(arg.ArgType!.DotnetName, Expression.PropertyOrField(argTypeParam!, arg.ArgName)); - } - var newExpArg = - ExpressionUtil.CreateNewExpression(propExpressions, item.FlattenType!, true) - ?? throw new EntityQuerySchemaException($"Could not create expression for argument {item.ArgName} of type {item.ArgType!.RawType.Name}"); - argsForCallExpression.Add(item.ArgName, newExpArg); - } - else - { - argsForCallExpression.Add(item.ArgName, Expression.PropertyOrField(argTypeParam!, item.ArgName)); - } + fieldSchemaArgs.Add(item.ArgName, item.ArgType!); + fieldDotnetArgtypes.Add(item.ArgName, item.ArgType!.RawType); } - - var call = Expression.Call(method.IsStatic ? null : param, method, argsForCallExpression.Values); - var lambdaParams = new[] { param, argTypeParam }.Concat(fieldServices.Values); - le = Expression.Lambda(call, lambdaParams); } - else + fieldArgType = LinqRuntimeTypeBuilder.GetDynamicType(fieldDotnetArgtypes, method.Name)!; + var argTypeParam = Expression.Parameter(fieldArgType, $"args_{fieldArgType.Name}"); + // build argument expressions for the call + foreach (var item in argumentsFromMethod) { - var call = Expression.Call(param, method); - le = Expression.Lambda(call, param); + if (item.IsService) + { + fieldServices.Add(item.ArgName, Expression.Parameter(item.ServiceType!, item.ArgName)); + argsForCallExpression.Add(item.ArgName, fieldServices[item.ArgName]!); + } + else if (item.ShouldFlatten) + { + // need to create expression new ArgType { Prop1 = args.Prop1, Prop2 = args.Prop2 } + var propExpressions = new Dictionary(); + foreach (var arg in item.FlattenArgs!) + { + propExpressions.Add(arg.ArgType!.DotnetName, Expression.PropertyOrField(argTypeParam!, arg.ArgName)); + } + var newExpArg = + ExpressionUtil.CreateNewExpression(propExpressions, item.FlattenType!, true) + ?? throw new EntityQuerySchemaException($"Could not create expression for argument {item.ArgName} of type {item.ArgType!.RawType.Name}"); + argsForCallExpression.Add(item.ArgName, newExpArg); + } + else + { + argsForCallExpression.Add(item.ArgName, Expression.PropertyOrField(argTypeParam!, item.ArgName)); + } } - var baseReturnType = GetBaseReturnType(schema, le.ReturnType, options); - - if (options.IgnoreTypes.Contains(baseReturnType)) - return null; + var call = Expression.Call(method.IsStatic ? null : param, method, argsForCallExpression.Values); + var lambdaParams = new[] { param, argTypeParam }.Concat(fieldServices.Values); + le = Expression.Lambda(call, lambdaParams); + } + else + { + var call = Expression.Call(param, method); + le = Expression.Lambda(call, param); + } - var schemaType = CacheType(baseReturnType, schema, options, false); + var baseReturnType = GetBaseReturnType(schema, le.ReturnType, options); - var nullabilityInfo = method.GetNullabilityInfo(); - var returnTypeInfo = - schema.GetCustomTypeMapping(le.ReturnType) - ?? new GqlTypeInfo(() => schemaType != null ? schemaType : schema.GetSchemaType(baseReturnType, isInputType, null), le.Body.Type, nullabilityInfo); - var field = new Field(schema, fromType, name, le, description, fieldSchemaArgs, returnTypeInfo, requiredClaims); - options.OnFieldCreated?.Invoke(field); + if (options.IgnoreTypes.Contains(baseReturnType)) + return null; - if (fieldServices.Count > 0) - { - field.Services = fieldServices.Values.ToList(); - field.ExpressionArgumentType = fieldArgType; - } + var schemaType = CacheType(baseReturnType, schema, options, false); - field.ApplyAttributes(method.GetCustomAttributes()); + var nullabilityInfo = method.GetNullabilityInfo(); + var returnTypeInfo = schema.GetCustomTypeMapping(le.ReturnType) ?? new GqlTypeInfo(() => schemaType ?? schema.GetSchemaType(baseReturnType, isInputType, null), le.Body.Type, nullabilityInfo); + var field = new Field(schema, fromType, name, le, description, fieldSchemaArgs, returnTypeInfo, requiredClaims); + options.OnFieldCreated?.Invoke(field); - return field; + if (fieldServices.Count > 0) + { + field.Services = fieldServices.Values.ToList(); + field.ExpressionArgumentType = fieldArgType; } - private static bool ShouldIncludeMember(MemberInfo prop, SchemaBuilderOptions options, bool isInputType) - { - if (options.IgnoreProps.Contains(prop.Name) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromQuery(prop)) - return false; + field.ApplyAttributes(method.GetCustomAttributes()); - if (isInputType && GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) - return false; + return field; + } - if (prop is PropertyInfo propertyInfo && propertyInfo.GetIndexParameters().Length > 0) - { - return false; - } + private static bool ShouldIncludeMember(MemberInfo prop, SchemaBuilderOptions options, bool isInputType) + { + if (options.IgnoreProps.Contains(prop.Name) || GraphQLIgnoreAttribute.ShouldIgnoreMemberFromQuery(prop)) + return false; - foreach (var attribute in prop.GetCustomAttributes()) - { - if (options.IgnoreAttributes.Contains(attribute.GetType())) - return false; - } + if (isInputType && GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(prop)) + return false; - return true; + if (prop is PropertyInfo propertyInfo && propertyInfo.GetIndexParameters().Length > 0) + { + return false; } - private static IEnumerable? ProcessFieldOrPropertyIntoField( - ISchemaType fromType, - MemberInfo prop, - ParameterExpression param, - ISchemaProvider schema, - SchemaBuilderOptions options, - bool isInputType - ) + foreach (var attribute in prop.GetCustomAttributes()) { - if (!ShouldIncludeMember(prop, options, isInputType)) - yield break; + if (options.IgnoreAttributes.Contains(attribute.GetType())) + return false; + } - (string name, string description) = GetNameAndDescription(prop, schema); + return true; + } - LambdaExpression? le = null; - le = prop.MemberType switch - { - MemberTypes.Property => Expression.Lambda(Expression.Property(param, prop.Name), param), - MemberTypes.Field => Expression.Lambda(Expression.Field(param, prop.Name), param), - _ => throw new NotImplementedException($"{nameof(ProcessFieldOrPropertyIntoField)} unknown MemberType: {prop.MemberType}"), - }; - var requiredClaims = schema.AuthorizationService.GetRequiredAuthFromMember(prop); + private static IEnumerable? ProcessFieldOrPropertyIntoField( + ISchemaType fromType, + MemberInfo prop, + ParameterExpression param, + ISchemaProvider schema, + SchemaBuilderOptions options, + bool isInputType + ) + { + if (!ShouldIncludeMember(prop, options, isInputType)) + yield break; - var baseReturnType = GetBaseReturnType(schema, le.ReturnType, options); + (string name, string description) = GetNameAndDescription(prop, schema); - if (options.IgnoreTypes.Contains(baseReturnType)) - yield break; + LambdaExpression? le = null; + le = prop.MemberType switch + { + MemberTypes.Property => Expression.Lambda(Expression.Property(param, prop.Name), param), + MemberTypes.Field => Expression.Lambda(Expression.Field(param, prop.Name), param), + _ => throw new NotImplementedException($"{nameof(ProcessFieldOrPropertyIntoField)} unknown MemberType: {prop.MemberType}"), + }; + var requiredClaims = schema.AuthorizationService.GetRequiredAuthFromMember(prop); - var schemaType = CacheType(baseReturnType, schema, options, isInputType); + var baseReturnType = GetBaseReturnType(schema, le.ReturnType, options); - var nullabilityInfo = prop.GetNullabilityInfo(); - // see if there is a direct type mapping from the expression return to to something. - // otherwise build the type info - var returnTypeInfo = - schema.GetCustomTypeMapping(le.ReturnType) - ?? new GqlTypeInfo(() => schemaType != null ? schemaType : schema.GetSchemaType(baseReturnType, isInputType, null), le.Body.Type, nullabilityInfo); - var field = new Field(schema, fromType, name, le, description, null, returnTypeInfo, requiredClaims); - options.OnFieldCreated?.Invoke(field); + if (options.IgnoreTypes.Contains(baseReturnType)) + yield break; - if (options.AutoCreateFieldWithIdArguments && (!schema.HasType(prop.DeclaringType!) || schema.GetSchemaType(prop.DeclaringType!, isInputType, null).GqlType != GqlTypes.InputObject)) + var schemaType = CacheType(baseReturnType, schema, options, isInputType); + + var nullabilityInfo = prop.GetNullabilityInfo(); + // see if there is a direct type mapping from the expression return to to something. + // otherwise build the type info + var returnTypeInfo = schema.GetCustomTypeMapping(le.ReturnType) ?? new GqlTypeInfo(() => schemaType ?? schema.GetSchemaType(baseReturnType, isInputType, null), le.Body.Type, nullabilityInfo); + var field = new Field(schema, fromType, name, le, description, null, returnTypeInfo, requiredClaims); + options.OnFieldCreated?.Invoke(field); + + if (options.AutoCreateFieldWithIdArguments && (!schema.HasType(prop.DeclaringType!) || schema.GetSchemaType(prop.DeclaringType!, isInputType, null).GqlType != GqlTypes.InputObject)) + { + // add non-plural field with argument of ID + var idArgField = MakeFieldWithIdArgumentIfExists(schema, fromType, prop.ReflectedType!, field, options); + if (idArgField != null) { - // add non-plural field with argument of ID - var idArgField = MakeFieldWithIdArgumentIfExists(schema, fromType, prop.ReflectedType!, field, options); - if (idArgField != null) - { - yield return idArgField; - } + yield return idArgField; } + } - field.ApplyAttributes(prop.GetCustomAttributes()); + field.ApplyAttributes(prop.GetCustomAttributes()); - yield return field; - } + yield return field; + } - private static Type GetBaseReturnType(ISchemaProvider schema, Type returnType, SchemaBuilderOptions options) + private static Type GetBaseReturnType(ISchemaProvider schema, Type returnType, SchemaBuilderOptions options) + { + // get the object type returned (ignoring list etc) so we know the context to find fields etc + var returnsTask = returnType.GetCustomAttribute() != null || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)); + if (returnsTask || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))) { - // get the object type returned (ignoring list etc) so we know the context to find fields etc - var returnsTask = returnType.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)); - if (returnsTask || (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))) - { - returnType = returnType.GetGenericArguments()[0]; - } - if (returnType.IsDictionary()) + returnType = returnType.GetGenericArguments()[0]; + } + if (returnType.IsDictionary()) + { + // check for dictionaries + if (options.AutoCreateNewComplexTypes) { - // check for dictionaries - if (options.AutoCreateNewComplexTypes) - { - Type[] genericTypeArguments = returnType.GenericTypeArguments; - returnType = typeof(KeyValuePair<,>).MakeGenericType(genericTypeArguments); - if (!schema.HasType(returnType)) - schema.AddScalarType( - returnType, - $"{genericTypeArguments[0].Name}{genericTypeArguments[1].Name}KeyValuePair", - $"Key value pair of {genericTypeArguments[0].Name} & {genericTypeArguments[1].Name}" - ); - } + Type[] genericTypeArguments = returnType.GenericTypeArguments; + returnType = typeof(KeyValuePair<,>).MakeGenericType(genericTypeArguments); + if (!schema.HasType(returnType)) + schema.AddScalarType( + returnType, + $"{genericTypeArguments[0].Name}{genericTypeArguments[1].Name}KeyValuePair", + $"Key value pair of {genericTypeArguments[0].Name} & {genericTypeArguments[1].Name}" + ); } - else - returnType = returnType.IsEnumerableOrArray() ? returnType.GetEnumerableOrArrayType()! : returnType.GetNonNullableType()!; - - Type baseReturnType = returnType; - if (baseReturnType.IsEnumerableOrArray()) - baseReturnType = baseReturnType.GetEnumerableOrArrayType()!; - return baseReturnType; } + else + returnType = returnType.IsEnumerableOrArray() ? returnType.GetEnumerableOrArrayType()! : returnType.GetNonNullableType()!; + + Type baseReturnType = returnType; + if (baseReturnType.IsEnumerableOrArray()) + baseReturnType = baseReturnType.GetEnumerableOrArrayType()!; + return baseReturnType; + } - internal static (string name, string description) GetNameAndDescription(MemberInfo prop, ISchemaProvider schema) + internal static (string name, string description) GetNameAndDescription(MemberInfo prop, ISchemaProvider schema) + { + var name = schema.SchemaFieldNamer(prop.Name); + var description = string.Empty; + var descAttribute = prop.GetCustomAttribute(false); + if (descAttribute != null) { - var name = schema.SchemaFieldNamer(prop.Name); - var description = string.Empty; - var descAttribute = (DescriptionAttribute?)prop.GetCustomAttribute(typeof(DescriptionAttribute), false); - if (descAttribute != null) - { - description = descAttribute.Description; - } + description = descAttribute.Description; + } - var attribute = prop.GetCustomAttribute(); - if (attribute != null) - { - if (!string.IsNullOrEmpty(attribute.Name)) - name = attribute.Name; + var attribute = prop.GetCustomAttribute(); + if (attribute != null) + { + if (!string.IsNullOrEmpty(attribute.Name)) + name = attribute.Name; - if (!string.IsNullOrEmpty(attribute.Description)) - description = attribute.Description; - } - return (name, description); + if (!string.IsNullOrEmpty(attribute.Description)) + description = attribute.Description; } + return (name, description); + } - internal static ISchemaType? CacheType(Type propType, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) + internal static ISchemaType? CacheType(Type propType, ISchemaProvider schema, SchemaBuilderOptions options, bool isInputType) + { + if (!schema.HasType(propType)) { - if (!schema.HasType(propType)) + var typeInfo = propType; + string description = string.Empty; + var d = typeInfo.GetCustomAttribute(false); + if (d != null) { - var typeInfo = propType; - string description = string.Empty; - var d = (DescriptionAttribute?)typeInfo.GetCustomAttribute(typeof(DescriptionAttribute), false); - if (d != null) - { - description = d.Description; - } + description = d.Description; + } - var typeName = BuildTypeName(propType); + var typeName = BuildTypeName(propType); - if ((options.AutoCreateNewComplexTypes && typeInfo.IsClass) || ((typeInfo.IsInterface || typeInfo.IsAbstract) && options.AutoCreateInterfaceTypes)) - { - var fieldCount = typeInfo.GetProperties().Length + typeInfo.GetFields().Length; + if ((options.AutoCreateNewComplexTypes && typeInfo.IsClass) || ((typeInfo.IsInterface || typeInfo.IsAbstract) && options.AutoCreateInterfaceTypes)) + { + var fieldCount = typeInfo.GetProperties().Length + typeInfo.GetFields().Length; - // add type before we recurse more that may also add the type - // dynamically call generic method - // hate this, but want to build the types with the right generics so you can extend them later. - // this is not the fastest, but only done on schema creation - var addMethod = (isInputType, typeInfo.IsInterface, typeInfo.IsAbstract, fieldCount) switch - { - (true, _, _, _) => nameof(ISchemaProvider.AddInputType), - (_, true, _, > 0) => nameof(ISchemaProvider.AddInterface), - (_, _, true, > 0) => nameof(ISchemaProvider.AddInterface), - (_, true, _, _) => nameof(ISchemaProvider.AddUnion), - (_, _, true, _) => nameof(ISchemaProvider.AddUnion), - _ => nameof(ISchemaProvider.AddType) - }; - - var method = schema.GetType().GetMethod(addMethod, [typeof(string), typeof(string)]); - if (method == null) - throw new EntityQuerySchemaException($"Could not find {addMethod} method on schema"); - method = method.MakeGenericMethod(propType); - var typeAdded = (ISchemaType)method.Invoke(schema, new object[] { typeName, description })!; - typeAdded.RequiredAuthorization = schema.AuthorizationService.GetRequiredAuthFromType(propType); - - var fields = GetFieldsFromObject(propType, typeAdded, schema, options, isInputType); - typeAdded.AddFields(fields); - - if (options.AutoCreateInterfaceTypes && !typeAdded.IsInput) - { - typeAdded.ImplementAllBaseTypes(true, true); - } - return typeAdded; - } - else if (options.AutoCreateEnumTypes && typeInfo.IsEnum && !schema.HasType(typeName)) + // add type before we recurse more that may also add the type + // dynamically call generic method + // hate this, but want to build the types with the right generics so you can extend them later. + // this is not the fastest, but only done on schema creation + var addMethod = (isInputType, typeInfo.IsInterface, typeInfo.IsAbstract, fieldCount) switch { - return schema.AddEnum(propType.Name, propType, description); - } - else if (options.AutoCreateEnumTypes && propType.IsNullableType() && Nullable.GetUnderlyingType(propType)!.IsEnum && !schema.HasType(Nullable.GetUnderlyingType(propType)!.Name)) + (true, _, _, _) => nameof(ISchemaProvider.AddInputType), + (_, true, _, > 0) => nameof(ISchemaProvider.AddInterface), + (_, _, true, > 0) => nameof(ISchemaProvider.AddInterface), + (_, true, _, _) => nameof(ISchemaProvider.AddUnion), + (_, _, true, _) => nameof(ISchemaProvider.AddUnion), + _ => nameof(ISchemaProvider.AddType), + }; + + var method = schema.GetType().GetMethod(addMethod, [typeof(string), typeof(string)]) ?? throw new EntityQuerySchemaException($"Could not find {addMethod} method on schema"); + method = method.MakeGenericMethod(propType); + var typeAdded = (ISchemaType)method.Invoke(schema, new object[] { typeName, description })!; + typeAdded.RequiredAuthorization = schema.AuthorizationService.GetRequiredAuthFromType(propType); + + var fields = GetFieldsFromObject(propType, typeAdded, schema, options, isInputType); + typeAdded.AddFields(fields); + + if (options.AutoCreateInterfaceTypes && !typeAdded.IsInput) { - Type type = Nullable.GetUnderlyingType(propType)!; - return schema.AddEnum(type.Name, type, description); + typeAdded.ImplementAllBaseTypes(true, true); } + return typeAdded; } - if (schema.TryGetSchemaType(propType, isInputType, out var schemaType, null)) + else if (options.AutoCreateEnumTypes && typeInfo.IsEnum && !schema.HasType(typeName)) { - return schemaType; + return schema.AddEnum(propType.Name, propType, description); + } + else if (options.AutoCreateEnumTypes && propType.IsNullableType() && Nullable.GetUnderlyingType(propType)!.IsEnum && !schema.HasType(Nullable.GetUnderlyingType(propType)!.Name)) + { + Type type = Nullable.GetUnderlyingType(propType)!; + return schema.AddEnum(type.Name, type, description); } - return null; } - - internal static string BuildTypeName(Type propType) + if (schema.TryGetSchemaType(propType, isInputType, out var schemaType, null)) { - return propType.IsGenericType ? $"{propType.Name[..propType.Name.IndexOf('`')]}{string.Join("", propType.GetGenericArguments().Select(BuildTypeName))}" : propType.Name; + return schemaType; } + return null; + } - public static GqlTypeInfo MakeGraphQlType(ISchemaProvider schema, bool isInputType, Type returnType, string? returnSchemaType, string fieldName, ISchemaType fromType) - { - Func typeGetter = !string.IsNullOrEmpty(returnSchemaType) - // We can look the type up by it's unique schema name - ? () => schema.Type(returnSchemaType) - // we need to look it up by the dotnet type - : () => - { - var getType = returnType.IsEnumerableOrArray() || returnType.IsNullableType() ? returnType.GetNonNullableOrEnumerableType() : returnType; - if (schema.TryGetSchemaType(getType, isInputType, out var schemaType, null)) - return schemaType!; - throw new EntityGraphQLCompilerException( - $"No schema type found for dotnet type '{getType.Name}'. Make sure you add it or add a type mapping. Lookup failed for field '{fieldName}' on type '{fromType.Name}'" - ); - }; - return new GqlTypeInfo(typeGetter, returnType); - } + internal static string BuildTypeName(Type propType) + { + return propType.IsGenericType ? $"{propType.Name[..propType.Name.IndexOf('`')]}{string.Join("", propType.GetGenericArguments().Select(BuildTypeName))}" : propType.Name; + } - public static IEnumerable GetGraphQlSchemaArgumentsFromMethod( - ISchemaProvider schema, - MethodInfo method, - SchemaBuilderOptions options, - out Dictionary flattenArgumentTypes - ) - { - flattenArgumentTypes = []; - var arguments = new List(); - foreach (var item in method.GetParameters()) + public static GqlTypeInfo MakeGraphQlType(ISchemaProvider schema, bool isInputType, Type returnType, string? returnSchemaType, string fieldName, ISchemaType fromType) + { + Func typeGetter = !string.IsNullOrEmpty(returnSchemaType) + ? () => schema.Type(returnSchemaType) // We can look the type up by it's unique schema name + // we need to look it up by the dotnet type + : () => { - if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) - continue; - - var inputType = item.ParameterType.IsEnumerableOrArray() ? item.ParameterType.GetEnumerableOrArrayType()! : item.ParameterType; - if (inputType.IsNullableType()) - inputType = inputType.GetGenericArguments()[0]; + var getType = returnType.IsEnumerableOrArray() || returnType.IsNullableType() ? returnType.GetNonNullableOrEnumerableType() : returnType; + if (schema.TryGetSchemaType(getType, isInputType, out var schemaType, null)) + return schemaType!; + throw new EntityGraphQLCompilerException( + $"No schema type found for dotnet type '{getType.Name}'. Make sure you add it or add a type mapping. Lookup failed for field '{fieldName}' on type '{fromType.Name}'" + ); + }; + return new GqlTypeInfo(typeGetter, returnType); + } - if (inputType == schema.QueryContextType) - { - arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType)); - continue; - } + public static IEnumerable GetGraphQlSchemaArgumentsFromMethod( + ISchemaProvider schema, + MethodInfo method, + SchemaBuilderOptions options, + out Dictionary flattenArgumentTypes + ) + { + flattenArgumentTypes = []; + var arguments = new List(); + foreach (var item in method.GetParameters()) + { + if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) + continue; - // primitive types are arguments or types already known in the schema - var shouldBeAddedAsArg = - item.ParameterType.IsPrimitive || (schema.HasType(inputType) && (schema.Type(inputType).IsInput || schema.Type(inputType).IsScalar || schema.Type(inputType).IsEnum)); + var inputType = item.ParameterType.IsEnumerableOrArray() ? item.ParameterType.GetEnumerableOrArrayType()! : item.ParameterType; + if (inputType.IsNullableType()) + inputType = inputType.GetGenericArguments()[0]; - // if hasServices then we expect attributes - var argumentsAttr = item.GetCustomAttribute() ?? item.ParameterType.GetTypeInfo().GetCustomAttribute(); - var inlineArgumentAttr = item.GetCustomAttribute() ?? item.ParameterType.GetTypeInfo().GetCustomAttribute(); - if (!shouldBeAddedAsArg && argumentsAttr == null && inlineArgumentAttr == null) - { - arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType)); - continue; - } + if (inputType == schema.QueryContextType) + { + arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType)); + continue; + } - if (argumentsAttr != null) - { - arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType, FlattenArguments(item.ParameterType, schema, options))); - flattenArgumentTypes.Add(item.Name!, item.ParameterType); - } - else - { - arguments.Add(new FieldArgInfo(schema.SchemaFieldNamer(item.Name!), ArgType.FromParameter(schema, item, item.HasDefaultValue ? item.DefaultValue : null))); + // primitive types are arguments or types already known in the schema + var shouldBeAddedAsArg = + item.ParameterType.IsPrimitive || (schema.HasType(inputType) && (schema.Type(inputType).IsInput || schema.Type(inputType).IsScalar || schema.Type(inputType).IsEnum)); - if (!schema.HasType(inputType) && options.AutoCreateInputTypes) - { - // use input type as it has resolved to a non list/nullable type - CacheType(inputType, schema, options, true); - } - } + // if hasServices then we expect attributes + var argumentsAttr = item.GetCustomAttribute() ?? item.ParameterType.GetTypeInfo().GetCustomAttribute(); + var inlineArgumentAttr = item.GetCustomAttribute() ?? item.ParameterType.GetTypeInfo().GetCustomAttribute(); + if (!shouldBeAddedAsArg && argumentsAttr == null && inlineArgumentAttr == null) + { + arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType)); + continue; } - return arguments; - } - private static IEnumerable FlattenArguments(Type argType, ISchemaProvider schema, SchemaBuilderOptions options) - { - foreach (var item in argType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + if (argumentsAttr != null) { - if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) - continue; - - yield return new FieldArgInfo(schema.SchemaFieldNamer(item.Name), ArgType.FromProperty(schema, item, null)); - var inputType = item.PropertyType.IsEnumerableOrArray() - ? item.PropertyType.GetEnumerableOrArrayType()! - : item.PropertyType.IsNullableType() - ? item.PropertyType.GetGenericArguments()[0] - : item.PropertyType; - if (!schema.HasType(inputType) && options.AutoCreateInputTypes) - { - CacheType(inputType, schema, options, true); - } + arguments.Add(new FieldArgInfo(item.Name!, item.ParameterType, FlattenArguments(item.ParameterType, schema, options))); + flattenArgumentTypes.Add(item.Name!, item.ParameterType); } - foreach (var item in argType.GetFields(BindingFlags.Instance | BindingFlags.Public)) + else { - if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) - continue; - yield return new FieldArgInfo(schema.SchemaFieldNamer(item.Name), ArgType.FromField(schema, item, null)); - var inputType = item.FieldType.IsEnumerableOrArray() - ? item.FieldType.GetEnumerableOrArrayType()! - : item.FieldType.IsNullableType() - ? item.FieldType.GetGenericArguments()[0] - : item.FieldType; + arguments.Add(new FieldArgInfo(schema.SchemaFieldNamer(item.Name!), ArgType.FromParameter(schema, item, item.HasDefaultValue ? item.DefaultValue : null))); + if (!schema.HasType(inputType) && options.AutoCreateInputTypes) { + // use input type as it has resolved to a non list/nullable type CacheType(inputType, schema, options, true); } } } + return arguments; } - public class FieldArgInfo + private static IEnumerable FlattenArguments(Type argType, ISchemaProvider schema, SchemaBuilderOptions options) { - public FieldArgInfo(string argName, Type flattenType, IEnumerable flattedArgs) + foreach (var item in argType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - ArgName = argName; - FlattenType = flattenType; - FlattenArgs = flattedArgs; + if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) + continue; + + yield return new FieldArgInfo(schema.SchemaFieldNamer(item.Name), ArgType.FromProperty(schema, item, null)); + var inputType = + item.PropertyType.IsEnumerableOrArray() ? item.PropertyType.GetEnumerableOrArrayType()! + : item.PropertyType.IsNullableType() ? item.PropertyType.GetGenericArguments()[0] + : item.PropertyType; + if (!schema.HasType(inputType) && options.AutoCreateInputTypes) + { + CacheType(inputType, schema, options, true); + } } - - public FieldArgInfo(string argName, ArgType argType) + foreach (var item in argType.GetFields(BindingFlags.Instance | BindingFlags.Public)) { - ArgName = argName; - ArgType = argType; + if (GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(item)) + continue; + yield return new FieldArgInfo(schema.SchemaFieldNamer(item.Name), ArgType.FromField(schema, item, null)); + var inputType = + item.FieldType.IsEnumerableOrArray() ? item.FieldType.GetEnumerableOrArrayType()! + : item.FieldType.IsNullableType() ? item.FieldType.GetGenericArguments()[0] + : item.FieldType; + if (!schema.HasType(inputType) && options.AutoCreateInputTypes) + { + CacheType(inputType, schema, options, true); + } } + } +} - public FieldArgInfo(string argName, Type serviceType) - { - ArgName = argName; - ServiceType = serviceType; - } +public class FieldArgInfo +{ + public FieldArgInfo(string argName, Type flattenType, IEnumerable flattedArgs) + { + ArgName = argName; + FlattenType = flattenType; + FlattenArgs = flattedArgs; + } - public string ArgName { get; } - public Type? FlattenType { get; } - public ArgType? ArgType { get; } - - /// - /// This argument is a service - /// - public bool IsService => ServiceType != null; - public Type? ServiceType { get; } - - /// - /// This argument should be flatten in the GraphQL schema. But not in the method call - /// - public bool ShouldFlatten => FlattenArgs != null; - public IEnumerable? FlattenArgs { get; } + public FieldArgInfo(string argName, ArgType argType) + { + ArgName = argName; + ArgType = argType; } + + public FieldArgInfo(string argName, Type serviceType) + { + ArgName = argName; + ServiceType = serviceType; + } + + public string ArgName { get; } + public Type? FlattenType { get; } + public ArgType? ArgType { get; } + + /// + /// This argument is a service + /// + public bool IsService => ServiceType != null; + public Type? ServiceType { get; } + + /// + /// This argument should be flatten in the GraphQL schema. But not in the method call + /// + public bool ShouldFlatten => FlattenArgs != null; + public IEnumerable? FlattenArgs { get; } } diff --git a/src/EntityGraphQL/Schema/SchemaGenerator.cs b/src/EntityGraphQL/Schema/SchemaGenerator.cs index 6e3ce1c8..22a651ad 100644 --- a/src/EntityGraphQL/Schema/SchemaGenerator.cs +++ b/src/EntityGraphQL/Schema/SchemaGenerator.cs @@ -9,279 +9,282 @@ // can remove this when we drop netstandard2.1 #pragma warning disable CA1305 -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public class SchemaGenerator { - public class SchemaGenerator + internal static string EscapeString(string? input) + { + if (input == null) + return string.Empty; + return input.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + internal static string Make(ISchemaProvider schema) { - internal static string EscapeString(string? input) + var rootQueryType = schema.GetSchemaType(schema.QueryContextType, false, null); + var mutationType = schema.Mutation().SchemaType; + var subscriptionType = schema.Subscription().SchemaType; + + var types = BuildSchemaTypes(schema); + + var schemaBuilder = new StringBuilder("schema {"); + schemaBuilder.AppendLine(); + schemaBuilder.AppendLine($"\tquery: {rootQueryType.Name}"); + bool outputMutation = mutationType.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)); + bool outputSubscription = subscriptionType.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)); + if (outputMutation) + schemaBuilder.AppendLine($"\tmutation: {mutationType.Name}"); + if (outputSubscription) + schemaBuilder.AppendLine($"\tsubscription: {subscriptionType.Name}"); + schemaBuilder.AppendLine("}"); + + schemaBuilder.AppendLine(); + + foreach (var item in schema.GetScalarTypes().Distinct().OrderBy(t => t.Name)) { - if (input == null) - return string.Empty; - return input.Replace("\\", "\\\\").Replace("\"", "\\\""); + if (!string.IsNullOrEmpty(item.Description)) + schemaBuilder.AppendLine($"\"\"\"{EscapeString(item.Description)}\"\"\""); + schemaBuilder.AppendLine($"scalar {item.Name}{GetDirectives(item.Directives)}"); } + schemaBuilder.AppendLine(); - internal static string Make(ISchemaProvider schema) + foreach (var directive in schema.GetDirectives().OrderBy(t => t.Name)) { - var rootQueryType = schema.GetSchemaType(schema.QueryContextType, false, null); - var mutationType = schema.Mutation().SchemaType; - var subscriptionType = schema.Subscription().SchemaType; - - var types = BuildSchemaTypes(schema); - - var schemaBuilder = new StringBuilder("schema {"); - schemaBuilder.AppendLine(); - schemaBuilder.AppendLine($"\tquery: {rootQueryType.Name}"); - bool outputMutation = mutationType.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)); - bool outputSubscription = subscriptionType.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)); - if (outputMutation) - schemaBuilder.AppendLine($"\tmutation: {mutationType.Name}"); - if (outputSubscription) - schemaBuilder.AppendLine($"\tsubscription: {subscriptionType.Name}"); - schemaBuilder.AppendLine("}"); - - schemaBuilder.AppendLine(); - - foreach (var item in schema.GetScalarTypes().Distinct().OrderBy(t => t.Name)) - { - if (!string.IsNullOrEmpty(item.Description)) - schemaBuilder.AppendLine($"\"\"\"{EscapeString(item.Description)}\"\"\""); - schemaBuilder.AppendLine($"scalar {item.Name}{GetDirectives(item.Directives)}"); - } - schemaBuilder.AppendLine(); - - foreach (var directive in schema.GetDirectives().OrderBy(t => t.Name)) - { - if (!string.IsNullOrEmpty(directive.Description)) - schemaBuilder.AppendLine($"\"\"\"{EscapeString(directive.Description)}\"\"\""); - schemaBuilder.AppendLine( - $"directive @{directive.Name}{GetDirectiveArgs(schema, directive)} on {string.Join(" | ", directive.Location.Select(i => Enum.GetName(typeof(ExecutableDirectiveLocation), i)))}" - ); - } - schemaBuilder.AppendLine(); + if (!string.IsNullOrEmpty(directive.Description)) + schemaBuilder.AppendLine($"\"\"\"{EscapeString(directive.Description)}\"\"\""); + schemaBuilder.AppendLine( +#if NETSTANDARD2_1 + $"directive @{directive.Name}{GetDirectiveArgs(schema, directive)} on {string.Join(" | ", directive.Location.Select(i => Enum.GetName(typeof(ExecutableDirectiveLocation), i)))}" +#else + $"directive @{directive.Name}{GetDirectiveArgs(schema, directive)} on {string.Join(" | ", directive.Location.Select(i => Enum.GetName(i)))}" +#endif + ); + } + schemaBuilder.AppendLine(); - schemaBuilder.Append(BuildEnumTypes(schema)); + schemaBuilder.Append(BuildEnumTypes(schema)); - schemaBuilder.AppendLine(OutputSchemaType(schema, schema.GetSchemaType(schema.QueryContextName, null))); + schemaBuilder.AppendLine(OutputSchemaType(schema, schema.GetSchemaType(schema.QueryContextName, null))); - schemaBuilder.Append(types); + schemaBuilder.Append(types); - if (outputMutation) - schemaBuilder.AppendLine(OutputSchemaType(schema, schema.Mutation().SchemaType)); - if (outputSubscription) - schemaBuilder.AppendLine(OutputSchemaType(schema, schema.Subscription().SchemaType)); + if (outputMutation) + schemaBuilder.AppendLine(OutputSchemaType(schema, schema.Mutation().SchemaType)); + if (outputSubscription) + schemaBuilder.AppendLine(OutputSchemaType(schema, schema.Subscription().SchemaType)); - return schemaBuilder.ToString(); - } + return schemaBuilder.ToString(); + } - private static string BuildEnumTypes(ISchemaProvider schema) + private static string BuildEnumTypes(ISchemaProvider schema) + { + var types = new StringBuilder(); + foreach (var typeItem in schema.GetNonContextTypes().OrderBy(t => t.Name)) { - var types = new StringBuilder(); - foreach (var typeItem in schema.GetNonContextTypes().OrderBy(t => t.Name)) - { - if (typeItem.Name.StartsWith("__", StringComparison.InvariantCulture) || !typeItem.IsEnum) - continue; - - if (!string.IsNullOrEmpty(typeItem.Description)) - types.AppendLine($"\"\"\"{EscapeString(typeItem.Description)}\"\"\""); - - types.AppendLine($"enum {typeItem.Name} {{"); - foreach (var field in typeItem.GetFields().OrderBy(t => t.Name)) - { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; + if (typeItem.Name.StartsWith("__", StringComparison.InvariantCulture) || !typeItem.IsEnum) + continue; - if (!string.IsNullOrEmpty(field.Description)) - types.AppendLine($"\t\"\"\"{EscapeString(field.Description)}\"\"\""); + if (!string.IsNullOrEmpty(typeItem.Description)) + types.AppendLine($"\"\"\"{EscapeString(typeItem.Description)}\"\"\""); - types.AppendLine($"\t{field.Name}{GetDirectives(field.DirectivesReadOnly)}"); - } - types.AppendLine("}"); - types.AppendLine(); - } - - return types.ToString(); - } - - private static string BuildSchemaTypes(ISchemaProvider schema) - { - var types = new StringBuilder(); - foreach (var typeItem in schema.GetNonContextTypes().OrderBy(t => t.Name)) + types.AppendLine($"enum {typeItem.Name} {{"); + foreach (var field in typeItem.GetFields().OrderBy(t => t.Name)) { - if ( - typeItem.Name.StartsWith("__", StringComparison.InvariantCulture) - || typeItem.IsEnum - || typeItem.IsScalar - || typeItem.Name == schema.Mutation().SchemaType.Name - || typeItem.Name == schema.Subscription().SchemaType.Name - ) + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) continue; - if (!typeItem.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)) && typeItem.GqlType != GqlTypes.Union && typeItem.BaseTypesReadOnly.Count == 0) - continue; + if (!string.IsNullOrEmpty(field.Description)) + types.AppendLine($"\t\"\"\"{EscapeString(field.Description)}\"\"\""); - types.AppendLine(OutputSchemaType(schema, typeItem)); + types.AppendLine($"\t{field.Name}{GetDirectives(field.DirectivesReadOnly)}"); } - - return types.ToString(); + types.AppendLine("}"); + types.AppendLine(); } - private static string GetDirectives(IEnumerable directives) - { - return string.Join("", directives.Select(d => " " + d.ToGraphQLSchemaString()).Distinct()); - } + return types.ToString(); + } - private static string GetGqlArgs(ISchemaProvider schema, IField field, string noArgs = "") + private static string BuildSchemaTypes(ISchemaProvider schema) + { + var types = new StringBuilder(); + foreach (var typeItem in schema.GetNonContextTypes().OrderBy(t => t.Name)) { - if (field.Arguments == null || !field.Arguments.Any() || field.ArgumentsAreInternal) - return noArgs; - - var all = field.Arguments.Select(f => - { - var arg = schema.SchemaFieldNamer(f.Key) + ": " + f.Value.Type.GqlTypeForReturnOrArgument; + if ( + typeItem.Name.StartsWith("__", StringComparison.InvariantCulture) + || typeItem.IsEnum + || typeItem.IsScalar + || typeItem.Name == schema.Mutation().SchemaType.Name + || typeItem.Name == schema.Subscription().SchemaType.Name + ) + continue; + + if (!typeItem.GetFields().Any(f => !f.Name.StartsWith("__", StringComparison.InvariantCulture)) && typeItem.GqlType != GqlTypes.Union && typeItem.BaseTypesReadOnly.Count == 0) + continue; + + types.AppendLine(OutputSchemaType(schema, typeItem)); + } - var defaultValue = GetArgDefaultValue(f.Value.DefaultValue, schema.SchemaFieldNamer); - if (!string.IsNullOrEmpty(defaultValue)) - { - arg += " = " + defaultValue; - } + return types.ToString(); + } - return arg; - }); + private static string GetDirectives(IEnumerable directives) + { + return string.Join("", directives.Select(d => " " + d.ToGraphQLSchemaString()).Distinct()); + } - var args = string.Join(", ", all); - return string.IsNullOrEmpty(args) ? string.Empty : $"({args})"; - } + private static string GetGqlArgs(ISchemaProvider schema, IField field, string noArgs = "") + { + if (field.Arguments == null || !field.Arguments.Any() || field.ArgumentsAreInternal) + return noArgs; - public static string? GetArgDefaultValue(object? value, Func fieldNamer) + var all = field.Arguments.Select(f => { - if (value == null || value == DBNull.Value) + var arg = schema.SchemaFieldNamer(f.Key) + ": " + f.Value.Type.GqlTypeForReturnOrArgument; + + var defaultValue = GetArgDefaultValue(f.Value.DefaultValue, schema.SchemaFieldNamer); + if (!string.IsNullOrEmpty(defaultValue)) { - return string.Empty; + arg += " = " + defaultValue; } - var ret = string.Empty; - var valueType = value.GetType(); + return arg; + }); - if (valueType == typeof(string)) - { - return $"\"{(((string)value == string.Empty) ? string.Empty : value)}\""; - } - if (valueType == typeof(bool)) - { - return value?.ToString()?.ToLower(CultureInfo.InvariantCulture); - } - else if (valueType.IsValueType) - { - return value?.ToString(); - } - else if (value is IEnumerable e) - { - return $"[{string.Join(", ", e.Cast().Select(item => GetArgDefaultValue(item, fieldNamer)).Where(item => item != null))}]"; - } - else if (valueType.IsConstructedGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) - { - if (((BaseEntityQueryType)value).HasValue) - { - var property = valueType.GetProperty("Query"); - return $"\"{property!.GetValue(value)}\""; - } - return string.Empty; - } - else if (value is object o) - { - ret += "{ "; - ret += string.Join( - ", ", - valueType - .GetProperties() - .Select(property => - { - var propValue = property.GetValue(o); - var propertyValue = GetArgDefaultValue(propValue, fieldNamer); - if (string.IsNullOrEmpty(propertyValue)) - return null; - - return $"{fieldNamer(property.Name)}: {propertyValue}"; - }) - .Where(i => i != null) - ); - ret += string.Join( - ", ", - valueType - .GetFields() - .Select(property => - { - var propValue = property.GetValue(o); - var propertyValue = GetArgDefaultValue(propValue, fieldNamer); - if (string.IsNullOrEmpty(propertyValue)) - return null; - - return $"{fieldNamer(property.Name)}: {propertyValue}"; - }) - .Where(i => i != null) - ); - ret += " }"; - } + var args = string.Join(", ", all); + return string.IsNullOrEmpty(args) ? string.Empty : $"({args})"; + } - return ret; + public static string? GetArgDefaultValue(object? value, Func fieldNamer) + { + if (value == null || value == DBNull.Value) + { + return string.Empty; } - private static string GetDirectiveArgs(ISchemaProvider schema, IDirectiveProcessor directive) - { - var args = directive.GetArguments(schema); - if (args == null || !args.Any()) - return string.Empty; + var ret = string.Empty; + var valueType = value.GetType(); - var allArgs = string.Join(", ", args.Select(f => f.Key + ": " + f.Value.Type.GqlTypeForReturnOrArgument)); - return string.IsNullOrEmpty(allArgs) ? string.Empty : $"({allArgs})"; + if (valueType == typeof(string)) + { + return $"\"{(((string)value == string.Empty) ? string.Empty : value)}\""; } - - private static string OutputSchemaType(ISchemaProvider schema, ISchemaType schemaType) + if (valueType == typeof(bool)) + { + return value?.ToString()?.ToLower(CultureInfo.InvariantCulture); + } + else if (valueType.IsValueType) + { + return value?.ToString(); + } + else if (value is IEnumerable e) + { + return $"[{string.Join(", ", e.Cast().Select(item => GetArgDefaultValue(item, fieldNamer)).Where(item => item != null))}]"; + } + else if (valueType.IsConstructedGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) + { + if (((BaseEntityQueryType)value).HasValue) + { + var property = valueType.GetProperty("Query"); + return $"\"{property!.GetValue(value)}\""; + } + return string.Empty; + } + else if (value is object o) { - var sb = new StringBuilder(); + ret += "{ "; + ret += string.Join( + ", ", + valueType + .GetProperties() + .Select(property => + { + var propValue = property.GetValue(o); + var propertyValue = GetArgDefaultValue(propValue, fieldNamer); + if (string.IsNullOrEmpty(propertyValue)) + return null; + + return $"{fieldNamer(property.Name)}: {propertyValue}"; + }) + .Where(i => i != null) + ); + ret += string.Join( + ", ", + valueType + .GetFields() + .Select(property => + { + var propValue = property.GetValue(o); + var propertyValue = GetArgDefaultValue(propValue, fieldNamer); + if (string.IsNullOrEmpty(propertyValue)) + return null; + + return $"{fieldNamer(property.Name)}: {propertyValue}"; + }) + .Where(i => i != null) + ); + ret += " }"; + } - if (!string.IsNullOrEmpty(schemaType.Description)) - sb.AppendLine($"\"\"\"{EscapeString(schemaType.Description)}\"\"\""); + return ret; + } - if (schemaType.GqlType == GqlTypes.Union) - { - if (schemaType.PossibleTypesReadOnly.Count == 0) - { - return string.Empty; - } + private static string GetDirectiveArgs(ISchemaProvider schema, IDirectiveProcessor directive) + { + var args = directive.GetArguments(schema); + if (args == null || !args.Any()) + return string.Empty; - sb.AppendLine($"union {schemaType.Name} = {string.Join(" | ", schemaType.PossibleTypesReadOnly.Select(i => i.Name))}"); - return sb.ToString(); - } + var allArgs = string.Join(", ", args.Select(f => f.Key + ": " + f.Value.Type.GqlTypeForReturnOrArgument)); + return string.IsNullOrEmpty(allArgs) ? string.Empty : $"({allArgs})"; + } - var type = schemaType.GqlType switch - { - GqlTypes.InputObject => "input", - GqlTypes.Interface => "interface", - GqlTypes.Union => "union", - _ => "type" - }; - - var implements = ""; - if (schemaType.BaseTypesReadOnly != null && schemaType.BaseTypesReadOnly.Count > 0) - { - implements += $" implements {string.Join(" & ", schemaType.BaseTypesReadOnly.Select(i => i.Name))}"; - } + private static string OutputSchemaType(ISchemaProvider schema, ISchemaType schemaType) + { + var sb = new StringBuilder(); - sb.AppendLine($"{type} {schemaType.Name}{implements}{GetDirectives(schemaType.Directives)} {{"); + if (!string.IsNullOrEmpty(schemaType.Description)) + sb.AppendLine($"\"\"\"{EscapeString(schemaType.Description)}\"\"\""); - foreach (var field in schemaType.GetFields().OrderBy(s => s.Name)) + if (schemaType.GqlType == GqlTypes.Union) + { + if (schemaType.PossibleTypesReadOnly.Count == 0) { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; - if (!string.IsNullOrEmpty(field.Description)) - sb.AppendLine($"\t\"\"\"{EscapeString(field.Description)}\"\"\""); - sb.AppendLine($"\t{schema.SchemaFieldNamer(field.Name)}{GetGqlArgs(schema, field)}: {field.ReturnType.GqlTypeForReturnOrArgument}{GetDirectives(field.DirectivesReadOnly)}"); + return string.Empty; } - sb.AppendLine("}"); + sb.AppendLine($"union {schemaType.Name} = {string.Join(" | ", schemaType.PossibleTypesReadOnly.Select(i => i.Name))}"); return sb.ToString(); } + + var type = schemaType.GqlType switch + { + GqlTypes.InputObject => "input", + GqlTypes.Interface => "interface", + GqlTypes.Union => "union", + _ => "type", + }; + + var implements = ""; + if (schemaType.BaseTypesReadOnly != null && schemaType.BaseTypesReadOnly.Count > 0) + { + implements += $" implements {string.Join(" & ", schemaType.BaseTypesReadOnly.Select(i => i.Name))}"; + } + + sb.AppendLine($"{type} {schemaType.Name}{implements}{GetDirectives(schemaType.Directives)} {{"); + + foreach (var field in schemaType.GetFields().OrderBy(s => s.Name)) + { + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; + if (!string.IsNullOrEmpty(field.Description)) + sb.AppendLine($"\t\"\"\"{EscapeString(field.Description)}\"\"\""); + sb.AppendLine($"\t{schema.SchemaFieldNamer(field.Name)}{GetGqlArgs(schema, field)}: {field.ReturnType.GqlTypeForReturnOrArgument}{GetDirectives(field.DirectivesReadOnly)}"); + } + sb.AppendLine("}"); + + return sb.ToString(); } } #pragma warning restore CA1305 diff --git a/src/EntityGraphQL/Schema/SchemaIntrospection.cs b/src/EntityGraphQL/Schema/SchemaIntrospection.cs index 6b900dbf..3a494d7f 100644 --- a/src/EntityGraphQL/Schema/SchemaIntrospection.cs +++ b/src/EntityGraphQL/Schema/SchemaIntrospection.cs @@ -1,329 +1,333 @@ -namespace EntityGraphQL.Schema +using System; +using System.Collections.Generic; +using System.Linq; +using EntityGraphQL.Extensions; +using EntityGraphQL.Schema.Models; +#if NETSTANDARD2_1 +using EntityGraphQL.Directives; +#endif + + +namespace EntityGraphQL.Schema; + +public static class SchemaIntrospection { - using System; - using System.Collections.Generic; - using System.Linq; - using EntityGraphQL.Directives; - using EntityGraphQL.Extensions; - using EntityGraphQL.Schema.Models; - - public static class SchemaIntrospection + /// + /// Creates an Introspection schema + /// + /// + /// + public static Models.Schema Make(ISchemaProvider schema) { - /// - /// Creates an Introspection schema - /// - /// - /// - /// - public static Schema Make(ISchemaProvider schema) + var types = new List { - var types = new List - { - new TypeElement("OBJECT", schema.QueryContextName) { Description = "The query type, represents all of the entry points into our object graph", OfType = null, }, - }; - types.AddRange(BuildQueryTypes(schema)); - types.AddRange(BuildInputTypes(schema)); - types.AddRange(BuildEnumTypes(schema)); - types.AddRange(BuildScalarTypes(schema)); - - var schemaDescription = new Schema( - new TypeElement(null, schema.QueryContextName), - schema.HasType(schema.Mutation().SchemaType.TypeDotnet) ? new TypeElement(null, schema.Mutation().SchemaType.Name) : null, - schema.HasType(schema.Subscription().SchemaType.TypeDotnet) ? new TypeElement(null, schema.Subscription().SchemaType.Name) : null, - types.OrderBy(x => x.Name).ToList(), - BuildDirectives(schema) - ); - - return schemaDescription; - } + new("OBJECT", schema.QueryContextName) { Description = "The query type, represents all of the entry points into our object graph", OfType = null }, + }; + types.AddRange(BuildQueryTypes(schema)); + types.AddRange(BuildInputTypes(schema)); + types.AddRange(BuildEnumTypes(schema)); + types.AddRange(BuildScalarTypes(schema)); + + var schemaDescription = new Models.Schema( + new TypeElement(null, schema.QueryContextName), + schema.HasType(schema.Mutation().SchemaType.TypeDotnet) ? new TypeElement(null, schema.Mutation().SchemaType.Name) : null, + schema.HasType(schema.Subscription().SchemaType.TypeDotnet) ? new TypeElement(null, schema.Subscription().SchemaType.Name) : null, + types.OrderBy(x => x.Name).ToList(), + BuildDirectives(schema) + ); + + return schemaDescription; + } - private static List BuildScalarTypes(ISchemaProvider schema) + private static List BuildScalarTypes(ISchemaProvider schema) + { + var types = new List(); + + foreach (var customScalar in schema.GetScalarTypes()) { - var types = new List(); + var typeElement = new TypeElement("SCALAR", customScalar.Name) { Description = customScalar.Description }; - foreach (var customScalar in schema.GetScalarTypes()) - { - var typeElement = new TypeElement("SCALAR", customScalar.Name) { Description = customScalar.Description }; + customScalar.Directives.ProcessType(typeElement); - customScalar.Directives.ProcessType(typeElement); + types.Add(typeElement); + } - types.Add(typeElement); - } + return types; + } - return types; - } + private static List BuildQueryTypes(ISchemaProvider schema) + { + var types = new List(); - private static List BuildQueryTypes(ISchemaProvider schema) + foreach (var st in schema.GetNonContextTypes().Where(s => !s.IsInput && !s.IsEnum && !s.IsScalar)) { - var types = new List(); + var kind = st.GqlType switch + { + GqlTypes.Interface => "INTERFACE", + GqlTypes.Union => "UNION", + _ => "OBJECT", + }; - foreach (var st in schema.GetNonContextTypes().Where(s => !s.IsInput && !s.IsEnum && !s.IsScalar)) + var typeElement = new TypeElement(kind, st.Name) { - var kind = st.GqlType switch - { - GqlTypes.Interface => "INTERFACE", - GqlTypes.Union => "UNION", - _ => "OBJECT" - }; - - var typeElement = new TypeElement(kind, st.Name) - { - Description = st.Description, - PossibleTypes = st.PossibleTypesReadOnly.Select(i => new TypeElement("OBJECT", i.Name))?.ToArray() ?? Array.Empty() - }; - - if (st.BaseTypesReadOnly != null && st.BaseTypesReadOnly.Count > 0) - { - typeElement.Interfaces = st.BaseTypesReadOnly.Select(baseType => new TypeElement("INTERFACE", baseType.Name)).ToArray(); - } + Description = st.Description, + PossibleTypes = st.PossibleTypesReadOnly.Select(i => new TypeElement("OBJECT", i.Name))?.ToArray() ?? Array.Empty(), + }; - types.Add(typeElement); + if (st.BaseTypesReadOnly != null && st.BaseTypesReadOnly.Count > 0) + { + typeElement.Interfaces = st.BaseTypesReadOnly.Select(baseType => new TypeElement("INTERFACE", baseType.Name)).ToArray(); } - return types; + types.Add(typeElement); } - /// - /// Build INPUT Type to be used by Mutations - /// - /// - /// - /// Since Types and Inputs cannot have the same name, camelCase the name to prevent duplicates. - /// - /// - private static List BuildInputTypes(ISchemaProvider schema) + return types; + } + + /// + /// Build INPUT Type to be used by Mutations + /// + /// + /// + /// Since Types and Inputs cannot have the same name, camelCase the name to prevent duplicates. + /// + /// + private static List BuildInputTypes(ISchemaProvider schema) + { + var types = new List(); + + foreach (ISchemaType schemaType in schema.GetNonContextTypes().Where(s => s.IsInput)) { - var types = new List(); + if (schemaType.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; - foreach (ISchemaType schemaType in schema.GetNonContextTypes().Where(s => s.IsInput)) + var inputValues = new List(); + foreach (var field in schemaType.GetFields().Cast()) { - if (schemaType.Name.StartsWith("__", StringComparison.InvariantCulture)) + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) continue; - var inputValues = new List(); - foreach (var field in schemaType.GetFields().Cast()) - { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; + // Skip any property with special attribute + var property = schemaType.TypeDotnet.GetProperty(field.Name); + if (property != null && GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(property)) + continue; - // Skip any property with special attribute - var property = schemaType.TypeDotnet.GetProperty(field.Name); - if (property != null && GraphQLIgnoreAttribute.ShouldIgnoreMemberFromInput(property)) - continue; + // Skipping custom fields added to schema + if (field.ResolveExpression?.NodeType == System.Linq.Expressions.ExpressionType.Call) + continue; - // Skipping custom fields added to schema - if (field.ResolveExpression?.NodeType == System.Linq.Expressions.ExpressionType.Call) - continue; + // Skipping ENUM type + if (field.ReturnType.TypeDotnet.IsEnum) + continue; - // Skipping ENUM type - if (field.ReturnType.TypeDotnet.IsEnum) - continue; + inputValues.Add(new InputValue(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet, true)) { Description = field.Description }); + } - inputValues.Add(new InputValue(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet, true)) { Description = field.Description, }); - } + var typeElement = new TypeElement("INPUT_OBJECT", schemaType.Name) { Description = schemaType.Description, InputFields = inputValues.ToArray() }; - var typeElement = new TypeElement("INPUT_OBJECT", schemaType.Name) { Description = schemaType.Description, InputFields = inputValues.ToArray() }; + schemaType.Directives.ProcessType(typeElement); - schemaType.Directives.ProcessType(typeElement); + types.Add(typeElement); + } - types.Add(typeElement); - } + return types; + } - return types; - } + private static List BuildEnumTypes(ISchemaProvider schema) + { + var types = new List(); - private static List BuildEnumTypes(ISchemaProvider schema) + // filter to ENUM type ONLY! + foreach (ISchemaType schemaType in schema.GetNonContextTypes().Where(s => s.IsEnum)) { - var types = new List(); + var typeElement = new TypeElement("ENUM", schemaType.Name) { Description = schemaType.Description, EnumValues = [] }; + if (schemaType.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; + + var enumTypes = new List(); - // filter to ENUM type ONLY! - foreach (ISchemaType schemaType in schema.GetNonContextTypes().Where(s => s.IsEnum)) + foreach (var field in schemaType.GetFields().Cast()) { - var typeElement = new TypeElement("ENUM", schemaType.Name) { Description = schemaType.Description, EnumValues = Array.Empty() }; - if (schemaType.Name.StartsWith("__", StringComparison.InvariantCulture)) + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) continue; - var enumTypes = new List(); - - foreach (var field in schemaType.GetFields().Cast()) - { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; + var e = new EnumValue(field.Name) { Description = field.Description }; - var e = new EnumValue(field.Name) { Description = field.Description, }; + field.DirectivesReadOnly.ProcessEnumValue(e); - field.DirectivesReadOnly.ProcessEnumValue(e); - - enumTypes.Add(e); - } - - typeElement.EnumValues = enumTypes.ToArray(); - if (typeElement.EnumValues.Length > 0) - types.Add(typeElement); + enumTypes.Add(e); } - return types; + typeElement.EnumValues = enumTypes.ToArray(); + if (typeElement.EnumValues.Length > 0) + types.Add(typeElement); } - private static TypeElement BuildType(ISchemaProvider schema, GqlTypeInfo typeInfo, Type clrType, bool isInput = false) - { - // Is collection of objects? - var type = new TypeElement(); - if (clrType.IsEnumerableOrArray()) - { - type.Kind = "LIST"; - type.Name = null; - type.OfType = BuildType(schema, typeInfo, typeInfo.SchemaType.TypeDotnet, isInput); - } - else if (clrType.Name == "EntityQueryType`1") - { - type.Kind = "SCALAR"; - type.Name = "String"; - type.OfType = null; - } - else if (clrType.IsEnum) - { - type.Kind = "ENUM"; - type.Name = typeInfo.SchemaType.Name; - type.OfType = null; - } - else - { - type.Kind = typeInfo.SchemaType.IsScalar ? "SCALAR" : "OBJECT"; - type.OfType = null; - if (type.Kind == "OBJECT" && isInput) - { - type.Kind = "INPUT_OBJECT"; - } - type.Name = typeInfo.SchemaType.Name; - } - if (typeInfo.TypeNotNullable) - { - return new TypeElement("NON_NULL", null) { OfType = type }; - } + return types; + } - return type; + private static TypeElement BuildType(ISchemaProvider schema, GqlTypeInfo typeInfo, Type clrType, bool isInput = false) + { + // Is collection of objects? + var type = new TypeElement(); + if (clrType.IsEnumerableOrArray()) + { + type.Kind = "LIST"; + type.Name = null; + type.OfType = BuildType(schema, typeInfo, typeInfo.SchemaType.TypeDotnet, isInput); } - - /// - /// This is used in a lazy evaluated field as a graph can have circular dependencies - /// - /// - /// - /// - /// - public static Models.Field[] BuildFieldsForType(ISchemaProvider schema, string typeName) + else if (clrType.Name == "EntityQueryType`1") { - if (typeName == schema.QueryContextName) - { - return BuildRootQueryFields(schema); - } - if (typeName == schema.Mutation().SchemaType.Name) - { - return BuildMutationFields(schema); - } - - var fieldDescs = new List(); - if (!schema.HasType(typeName)) + type.Kind = "SCALAR"; + type.Name = "String"; + type.OfType = null; + } + else if (clrType.IsEnum) + { + type.Kind = "ENUM"; + type.Name = typeInfo.SchemaType.Name; + type.OfType = null; + } + else + { + type.Kind = typeInfo.SchemaType.IsScalar ? "SCALAR" : "OBJECT"; + type.OfType = null; + if (type.Kind == "OBJECT" && isInput) { - return fieldDescs.ToArray(); + type.Kind = "INPUT_OBJECT"; } - var type = schema.Type(typeName); - foreach (var field in type.GetFields()) - { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; + type.Name = typeInfo.SchemaType.Name; + } + if (typeInfo.TypeNotNullable) + { + return new TypeElement("NON_NULL", null) { OfType = type }; + } - var f = new Models.Field(schema.SchemaFieldNamer(field.Name), BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) - { - Args = BuildArgs(schema, field).ToArray(), - Description = field.Description, - }; + return type; + } - field.DirectivesReadOnly.ProcessField(f); + /// + /// This is used in a lazy evaluated field as a graph can have circular dependencies + /// + /// + /// + /// + public static Models.Field[] BuildFieldsForType(ISchemaProvider schema, string typeName) + { + if (typeName == schema.QueryContextName) + { + return BuildRootQueryFields(schema); + } + if (typeName == schema.Mutation().SchemaType.Name) + { + return BuildMutationFields(schema); + } - fieldDescs.Add(f); - } + var fieldDescs = new List(); + if (!schema.HasType(typeName)) + { return fieldDescs.ToArray(); } - - private static Models.Field[] BuildRootQueryFields(ISchemaProvider schema) + var type = schema.Type(typeName); + foreach (var field in type.GetFields()) { - var rootFields = new List(); + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; - foreach (var field in schema.Type(schema.QueryContextName).GetFields()) + var f = new Models.Field(schema.SchemaFieldNamer(field.Name), BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; - - // Skipping ENUM type - if (field.ReturnType.TypeDotnet.IsEnum) - continue; - - //== Fields ==// - var f = new Models.Field(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) { Args = BuildArgs(schema, field).ToArray(), Description = field.Description }; + Args = BuildArgs(schema, field).ToArray(), + Description = field.Description, + }; - field.DirectivesReadOnly.ProcessField(f); + field.DirectivesReadOnly.ProcessField(f); - rootFields.Add(f); - } - return rootFields.ToArray(); + fieldDescs.Add(f); } + return fieldDescs.ToArray(); + } + + private static Models.Field[] BuildRootQueryFields(ISchemaProvider schema) + { + var rootFields = new List(); - private static Models.Field[] BuildMutationFields(ISchemaProvider schema) + foreach (var field in schema.Type(schema.QueryContextName).GetFields()) { - var rootFields = new List(); + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; - foreach (var field in schema.GetSchemaType(schema.MutationType, false, null).GetFields()) - { - if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) - continue; + // Skipping ENUM type + if (field.ReturnType.TypeDotnet.IsEnum) + continue; - var args = BuildArgs(schema, field).ToArray(); - var f = new Models.Field(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) { Args = args, Description = field.Description }; + //== Fields ==// + var f = new Models.Field(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) { Args = BuildArgs(schema, field).ToArray(), Description = field.Description }; - field.DirectivesReadOnly.ProcessField(f); + field.DirectivesReadOnly.ProcessField(f); - rootFields.Add(f); - } - return rootFields.ToArray(); + rootFields.Add(f); } + return rootFields.ToArray(); + } + + private static Models.Field[] BuildMutationFields(ISchemaProvider schema) + { + var rootFields = new List(); - private static List BuildArgs(ISchemaProvider schema, IField field) + foreach (var field in schema.GetSchemaType(schema.MutationType, false, null).GetFields()) { - var args = new List(); - if (field.ArgumentsAreInternal) - return args; + if (field.Name.StartsWith("__", StringComparison.InvariantCulture)) + continue; - foreach (var arg in field.Arguments) - { - var type = BuildType(schema, arg.Value.Type, arg.Value.Type.TypeDotnet, true); + var args = BuildArgs(schema, field).ToArray(); + var f = new Models.Field(field.Name, BuildType(schema, field.ReturnType, field.ReturnType.TypeDotnet)) { Args = args, Description = field.Description }; - var stringValue = SchemaGenerator.GetArgDefaultValue(arg.Value.DefaultValue, schema.SchemaFieldNamer)?.Trim('"'); - var defaultValue = string.IsNullOrEmpty(stringValue) ? null : stringValue; + field.DirectivesReadOnly.ProcessField(f); - args.Add(new InputValue(arg.Key, type) { DefaultValue = defaultValue, Description = arg.Value.Description, }); - } + rootFields.Add(f); + } + return rootFields.ToArray(); + } + private static List BuildArgs(ISchemaProvider schema, IField field) + { + var args = new List(); + if (field.ArgumentsAreInternal) return args; - } - private static List BuildDirectives(ISchemaProvider schema) + foreach (var arg in field.Arguments) { - var directives = schema - .GetDirectives() - .Select(directive => new Directive(directive.Name) - { - Description = directive.Description, - Locations = directive.Location.Select(i => Enum.GetName(typeof(ExecutableDirectiveLocation), i))!, - Args = directive - .GetArguments(schema) - .Values.Select(arg => new InputValue(arg.Name, BuildType(schema, arg.Type, arg.Type.TypeDotnet, true)) { Description = arg.Description, DefaultValue = null, }) - .ToArray() - }) - .ToList(); - - return directives; + var type = BuildType(schema, arg.Value.Type, arg.Value.Type.TypeDotnet, true); + + var stringValue = SchemaGenerator.GetArgDefaultValue(arg.Value.DefaultValue, schema.SchemaFieldNamer)?.Trim('"'); + var defaultValue = string.IsNullOrEmpty(stringValue) ? null : stringValue; + + args.Add(new InputValue(arg.Key, type) { DefaultValue = defaultValue, Description = arg.Value.Description }); } + + return args; + } + + private static List BuildDirectives(ISchemaProvider schema) + { + var directives = schema + .GetDirectives() + .Select(directive => new Directive(directive.Name) + { + Description = directive.Description, +#if NETSTANDARD2_1 + Locations = directive.Location.Select(i => Enum.GetName(typeof(ExecutableDirectiveLocation), i))!, +#else + Locations = directive.Location.Select(i => Enum.GetName(i))!, +#endif + Args = directive + .GetArguments(schema) + .Values.Select(arg => new InputValue(arg.Name, BuildType(schema, arg.Type, arg.Type.TypeDotnet, true)) { Description = arg.Description, DefaultValue = null }) + .ToArray(), + }) + .ToList(); + + return directives; } } diff --git a/src/EntityGraphQL/Schema/SchemaProvider.cs b/src/EntityGraphQL/Schema/SchemaProvider.cs index cec01389..327a3b2d 100644 --- a/src/EntityGraphQL/Schema/SchemaProvider.cs +++ b/src/EntityGraphQL/Schema/SchemaProvider.cs @@ -23,28 +23,16 @@ namespace EntityGraphQL.Schema; /// Base Query object context. Ex. DbContext public class SchemaProvider : ISchemaProvider, IDisposable { - public Type QueryContextType - { - get { return queryType.TypeDotnet; } - } - public Type MutationType - { - get { return mutationType.SchemaType.TypeDotnet; } - } - public Type SubscriptionType - { - get { return subscriptionType.SchemaType.TypeDotnet; } - } + public Type QueryContextType => queryType.TypeDotnet; + public Type MutationType => mutationType.SchemaType.TypeDotnet; + public Type SubscriptionType => subscriptionType.SchemaType.TypeDotnet; public Func SchemaFieldNamer { get; } public IGqlAuthorizationService AuthorizationService { get; set; } private readonly Dictionary schemaTypes = []; private readonly Dictionary directives = []; private readonly QueryCache queryCache; - public string QueryContextName - { - get => queryType.Name; - } + public string QueryContextName => queryType.Name; private readonly SchemaType queryType; private readonly ILogger>? logger; @@ -53,11 +41,11 @@ public string QueryContextName private readonly bool isDevelopment; private readonly MutationType mutationType; private readonly SubscriptionType subscriptionType; - private readonly Dictionary attributeHandlers = new Dictionary(); + private readonly Dictionary attributeHandlers = []; public IDictionary TypeConverters { get; } = new Dictionary(); - public List AllowedExceptions { get; } = new(); + public List AllowedExceptions { get; } = []; // map some types to scalar types private readonly Dictionary customTypeMappings; @@ -230,7 +218,6 @@ public async Task ExecuteRequestAsync(QueryRequest gql, IServicePro /// A service provider used for looking up dependencies of field selections and mutations /// Optional user/ClaimsPrincipal to check access for queries /// - /// /// public QueryResult ExecuteRequestWithContext(QueryRequest gql, TContextType context, IServiceProvider? serviceProvider, ClaimsPrincipal? user, ExecutionOptions? options = null) { @@ -245,7 +232,6 @@ public QueryResult ExecuteRequestWithContext(QueryRequest gql, TContextType cont /// A service provider used for looking up dependencies of field selections and mutations /// Optional user/ClaimsPrincipal to check access for queries /// - /// /// public async Task ExecuteRequestWithContextAsync(QueryRequest gql, TContextType context, IServiceProvider? serviceProvider, ClaimsPrincipal? user, ExecutionOptions? options = null) { @@ -389,11 +375,9 @@ private GraphQLDocument CompileQueryWithCache(QueryRequest gql, ExecutionOptions public SchemaType AddType(string name, string? description) { var gqlType = - typeof(TBaseType).IsAbstract || typeof(TBaseType).IsInterface - ? GqlTypes.Interface - : typeof(TBaseType).IsEnum - ? GqlTypes.Enum - : GqlTypes.QueryObject; + typeof(TBaseType).IsAbstract || typeof(TBaseType).IsInterface ? GqlTypes.Interface + : typeof(TBaseType).IsEnum ? GqlTypes.Enum + : GqlTypes.QueryObject; var schemaType = new SchemaType(this, name, description, null, gqlType); FinishAddingType(typeof(TBaseType), schemaType); return schemaType; @@ -520,7 +504,6 @@ public ISchemaType AddScalarType(Type clrType, string gqlTypeName, string? descr /// /// Adds a new scalar type defined to the schema. Dotnet types of TType will be treated as gqlTypeName /// - /// Dotnet type to mapp to a scalar /// GraphQL scalar type name /// Description of the scalar type /// The added type for further changes via chaining @@ -802,7 +785,6 @@ public ISchemaType AddInterface(Type type, string name, string? description = nu /// /// Add an interface type to the schema /// - /// /// /// /// @@ -830,7 +812,6 @@ public ISchemaType AddUnion(Type type, string name, string? description = null) /// /// Add an union type to the schema /// - /// /// /// /// diff --git a/src/EntityGraphQL/Schema/SchemaType.cs b/src/EntityGraphQL/Schema/SchemaType.cs index 04e2015d..da17c947 100644 --- a/src/EntityGraphQL/Schema/SchemaType.cs +++ b/src/EntityGraphQL/Schema/SchemaType.cs @@ -66,7 +66,7 @@ public override ISchemaType AddAllFields(SchemaBuilderOptions? options = null) continue; var enumName = Enum.Parse(TypeDotnet, field.Name).ToString()!; - var description = (field.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute)?.Description; + var description = field.GetCustomAttribute()?.Description; var nullability = field.GetNullabilityInfo(); var gqlTypeInfo = new GqlTypeInfo(() => Schema.GetSchemaType(TypeDotnet, GqlType == GqlTypes.InputObject, null), TypeDotnet, nullability); var schemaField = new Field(Schema, this, enumName, null, description, null, gqlTypeInfo, Schema.AuthorizationService.GetRequiredAuthFromMember(field)); @@ -314,8 +314,7 @@ public void RemoveField(Expression> fieldSelection) /// public SchemaType RequiresAllRoles(params string[] roles) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAllRoles(roles); return this; } @@ -326,8 +325,7 @@ public SchemaType RequiresAllRoles(params string[] roles) /// public SchemaType RequiresAnyRole(params string[] roles) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAnyRole(roles); return this; } @@ -338,8 +336,7 @@ public SchemaType RequiresAnyRole(params string[] roles) /// public SchemaType RequiresAllPolicies(params string[] policies) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAllPolicies(policies); return this; } @@ -350,8 +347,7 @@ public SchemaType RequiresAllPolicies(params string[] policies) /// public SchemaType RequiresAnyPolicy(params string[] policies) { - if (RequiredAuthorization == null) - RequiredAuthorization = new RequiredAuthorization(); + RequiredAuthorization ??= new RequiredAuthorization(); RequiredAuthorization.RequiresAnyPolicy(policies); return this; } diff --git a/src/EntityGraphQL/Schema/SubscriptionField.cs b/src/EntityGraphQL/Schema/SubscriptionField.cs index d6898982..2ef06589 100644 --- a/src/EntityGraphQL/Schema/SubscriptionField.cs +++ b/src/EntityGraphQL/Schema/SubscriptionField.cs @@ -3,27 +3,26 @@ using EntityGraphQL.Compiler; using EntityGraphQL.Extensions; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +public class SubscriptionField : MethodField { - public class SubscriptionField : MethodField - { - public override GraphQLQueryFieldType FieldType { get; } = GraphQLQueryFieldType.Subscription; + public override GraphQLQueryFieldType FieldType { get; } = GraphQLQueryFieldType.Subscription; - public SubscriptionField( - ISchemaProvider schema, - ISchemaType fromType, - string methodName, - GqlTypeInfo returnType, - MethodInfo method, - string description, - RequiredAuthorization requiredAuth, - bool isAsync, - SchemaBuilderOptions options - ) - : base(schema, fromType, methodName, returnType, method, description, requiredAuth, isAsync, options) - { - if (!method.ReturnType.ImplementsGenericInterface(typeof(IObservable<>))) - throw new EntityGraphQLCompilerException($"Subscription {methodName} should return an IObservable<>"); - } + public SubscriptionField( + ISchemaProvider schema, + ISchemaType fromType, + string methodName, + GqlTypeInfo returnType, + MethodInfo method, + string description, + RequiredAuthorization requiredAuth, + bool isAsync, + SchemaBuilderOptions options + ) + : base(schema, fromType, methodName, returnType, method, description, requiredAuth, isAsync, options) + { + if (!method.ReturnType.ImplementsGenericInterface(typeof(IObservable<>))) + throw new EntityGraphQLCompilerException($"Subscription {methodName} should return an IObservable<>"); } } diff --git a/src/EntityGraphQL/Schema/Validators/ArgumentValidatorAttribute.cs b/src/EntityGraphQL/Schema/Validators/ArgumentValidatorAttribute.cs index da2b2d1e..b6fbed42 100644 --- a/src/EntityGraphQL/Schema/Validators/ArgumentValidatorAttribute.cs +++ b/src/EntityGraphQL/Schema/Validators/ArgumentValidatorAttribute.cs @@ -10,7 +10,7 @@ public class ArgumentValidatorAttribute : Attribute public ArgumentValidatorAttribute(Type validatorType) { if (!typeof(IArgumentValidator).IsAssignableFrom(validatorType)) - throw new ArgumentException($"{validatorType.Name} must implement {typeof(IArgumentValidator).Name}"); + throw new ArgumentException($"{validatorType.Name} must implement {nameof(IArgumentValidator)}"); Validator = (IArgumentValidator)Activator.CreateInstance(validatorType)!; } } diff --git a/src/EntityGraphQL/Schema/Validators/ArgumentValidatorContext.cs b/src/EntityGraphQL/Schema/Validators/ArgumentValidatorContext.cs index d7959e35..a3d78e47 100644 --- a/src/EntityGraphQL/Schema/Validators/ArgumentValidatorContext.cs +++ b/src/EntityGraphQL/Schema/Validators/ArgumentValidatorContext.cs @@ -5,7 +5,7 @@ namespace EntityGraphQL.Schema; public class ArgumentValidatorContext { - private readonly List errors = new(); + private readonly List errors = []; public ArgumentValidatorContext(IField field, object? argumentValues, MethodInfo? method = null) { @@ -32,10 +32,7 @@ public ArgumentValidatorContext(IField field, object? argumentValues, MethodInfo /// /// List of error messages that will be added to the result /// - public IReadOnlyList Errors - { - get => errors; - } + public IReadOnlyList Errors => errors; /// /// Add an error message to the result. This will prevent execution of the query diff --git a/src/EntityGraphQL/Schema/Validators/DataAnnotationsValidator.cs b/src/EntityGraphQL/Schema/Validators/DataAnnotationsValidator.cs index 79e02714..a2ad2fc4 100644 --- a/src/EntityGraphQL/Schema/Validators/DataAnnotationsValidator.cs +++ b/src/EntityGraphQL/Schema/Validators/DataAnnotationsValidator.cs @@ -4,124 +4,123 @@ using System.Linq; using System.Threading.Tasks; -namespace EntityGraphQL.Schema.Validators +namespace EntityGraphQL.Schema.Validators; + +public class DataAnnotationsValidator : IArgumentValidator { - public class DataAnnotationsValidator : IArgumentValidator + public Task ValidateAsync(ArgumentValidatorContext context) { - public Task ValidateAsync(ArgumentValidatorContext context) - { - ValidateObjectRecursive(context, context.Arguments); - ValidateMethodArguments(context); + ValidateObjectRecursive(context, context.Arguments); + ValidateMethodArguments(context); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - /// - /// Validate the arguments against the validation attributes in the specified method - /// - /// - private static void ValidateMethodArguments(ArgumentValidatorContext context) + /// + /// Validate the arguments against the validation attributes in the specified method + /// + /// + private static void ValidateMethodArguments(ArgumentValidatorContext context) + { + if (context.Method != null && context.Arguments is IDictionary asDict) { - if (context.Method != null && context.Arguments is IDictionary asDict) + var methodParams = context.Method.GetParameters(); + foreach (var arg in context.Field.Arguments) { - var methodParams = context.Method.GetParameters(); - foreach (var arg in context.Field.Arguments) - { - // find the arg in the method params - var param = methodParams.FirstOrDefault(p => p.Name == arg.Key); - if (param == null) - continue; + // find the arg in the method params + var param = methodParams.FirstOrDefault(p => p.Name == arg.Key); + if (param == null) + continue; - asDict.TryGetValue(arg.Key, out var value); - if (value != null) + asDict.TryGetValue(arg.Key, out var value); + if (value != null) + { + var customAttributes = param.GetCustomAttributes(typeof(ValidationAttribute), true).OfType(); + if (customAttributes.Any()) { - var customAttributes = param.GetCustomAttributes(typeof(ValidationAttribute), true).OfType(); - if (customAttributes.Any()) + var results = new List(); + if (!Validator.TryValidateValue(value, new ValidationContext(value), results, customAttributes)) { - var results = new List(); - if (!Validator.TryValidateValue(value, new ValidationContext(value), results, customAttributes)) + results.ForEach(result => { - results.ForEach(result => + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) { - if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) - { - context.AddError(result.ErrorMessage); - } - }); - } + context.AddError(result.ErrorMessage); + } + }); } } } } } + } - /// - /// Validate the values of the object recursively against the validation attributes on the object itself - /// - /// - /// - private static void ValidateObjectRecursive(ArgumentValidatorContext context, object? obj) - { - if (obj == null) - return; + /// + /// Validate the values of the object recursively against the validation attributes on the object itself + /// + /// + /// + private static void ValidateObjectRecursive(ArgumentValidatorContext context, object? obj) + { + if (obj == null) + return; - if (obj is IEnumerable asEnumerable && obj is not string) + if (obj is IEnumerable asEnumerable && obj is not string) + { + foreach (var enumObj in asEnumerable) { - foreach (var enumObj in asEnumerable) - { - ValidateObjectRecursive(context, enumObj); - } + ValidateObjectRecursive(context, enumObj); } - else - { - var results = new List(); + } + else + { + var results = new List(); - if (!Validator.TryValidateObject(obj, new ValidationContext(obj), results, true)) + if (!Validator.TryValidateObject(obj, new ValidationContext(obj), results, true)) + { + results.ForEach(result => { - results.ForEach(result => + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) { - if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) - { - context.AddError(result.ErrorMessage); - } - }); - } - - if (obj is string || obj.GetType().IsValueType) - return; - - var properties = obj!.GetType().GetProperties().Where(prop => prop.CanRead && prop.GetIndexParameters().Length == 0).ToList(); + context.AddError(result.ErrorMessage); + } + }); + } - foreach (var property in properties) - { - var value = property.GetValue(obj, null); + if (obj is string || obj.GetType().IsValueType) + return; - if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) - { - continue; - } + var properties = obj!.GetType().GetProperties().Where(prop => prop.CanRead && prop.GetIndexParameters().Length == 0).ToList(); - if (property.PropertyType.GetGenericArguments().Length > 0 && property.PropertyType.GetGenericTypeDefinition() == typeof(RequiredField<>)) - { - if (value == null) - { - context.AddError($"missing required argument '{property.Name}'"); - } - continue; - } + foreach (var property in properties) + { + var value = property.GetValue(obj, null); - if (property.PropertyType.GetGenericArguments().Length > 0 && property.PropertyType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) - { - continue; - } + if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) + { + continue; + } + if (property.PropertyType.GetGenericArguments().Length > 0 && property.PropertyType.GetGenericTypeDefinition() == typeof(RequiredField<>)) + { if (value == null) { - continue; + context.AddError($"missing required argument '{property.Name}'"); } + continue; + } - ValidateObjectRecursive(context, value); + if (property.PropertyType.GetGenericArguments().Length > 0 && property.PropertyType.GetGenericTypeDefinition() == typeof(EntityQueryType<>)) + { + continue; } + + if (value == null) + { + continue; + } + + ValidateObjectRecursive(context, value); } } } diff --git a/src/EntityGraphQL/Schema/Validators/IArgumentValidator.cs b/src/EntityGraphQL/Schema/Validators/IArgumentValidator.cs index 17aa099d..80542357 100644 --- a/src/EntityGraphQL/Schema/Validators/IArgumentValidator.cs +++ b/src/EntityGraphQL/Schema/Validators/IArgumentValidator.cs @@ -1,13 +1,12 @@ using System.Threading.Tasks; -namespace EntityGraphQL.Schema +namespace EntityGraphQL.Schema; + +/// +/// An argument validator is invoked after the field expression and arguments are built but before execution of the query. +/// Use it to perform validation before execution. To stop execution add any errors to the context. +/// +public interface IArgumentValidator { - /// - /// An argument validator is invoked after the field expression and arguments are built but before execution of the query. - /// Use it to perform validation before execution. To stop execution add any errors to the context. - /// - public interface IArgumentValidator - { - Task ValidateAsync(ArgumentValidatorContext context); - } + Task ValidateAsync(ArgumentValidatorContext context); } diff --git a/src/EntityGraphQL/Subscriptions/GraphQLSubscribeResult.cs b/src/EntityGraphQL/Subscriptions/GraphQLSubscribeResult.cs index baad408d..e6068794 100644 --- a/src/EntityGraphQL/Subscriptions/GraphQLSubscribeResult.cs +++ b/src/EntityGraphQL/Subscriptions/GraphQLSubscribeResult.cs @@ -1,25 +1,24 @@ using System; using EntityGraphQL.Compiler; -namespace EntityGraphQL.Subscriptions -{ - public class GraphQLSubscribeResult - { - public Type EventType { get; } +namespace EntityGraphQL.Subscriptions; - private readonly object observable; +public class GraphQLSubscribeResult +{ + public Type EventType { get; } - public GraphQLSubscriptionStatement SubscriptionStatement { get; } - public GraphQLSubscriptionField Field { get; } + private readonly object observable; - public GraphQLSubscribeResult(Type eventType, object result, GraphQLSubscriptionStatement graphQLSubscriptionStatement, GraphQLSubscriptionField node) - { - EventType = eventType; - observable = result; - SubscriptionStatement = graphQLSubscriptionStatement; - Field = node; - } + public GraphQLSubscriptionStatement SubscriptionStatement { get; } + public GraphQLSubscriptionField Field { get; } - public object GetObservable() => observable; + public GraphQLSubscribeResult(Type eventType, object result, GraphQLSubscriptionStatement graphQLSubscriptionStatement, GraphQLSubscriptionField node) + { + EventType = eventType; + observable = result; + SubscriptionStatement = graphQLSubscriptionStatement; + Field = node; } + + public object GetObservable() => observable; } diff --git a/src/examples/AzureFunctionApp/AgeService.cs b/src/examples/AzureFunctionApp/AgeService.cs index bab6b603..ef0b93dd 100644 --- a/src/examples/AzureFunctionApp/AgeService.cs +++ b/src/examples/AzureFunctionApp/AgeService.cs @@ -1,15 +1,14 @@ using System; -namespace demo +namespace demo; + +/// +/// Very poor example of injecting a service into fields +/// +public class AgeService { - /// - /// Very poor example of injecting a service into fields - /// - public class AgeService + public int Calc(DateTime dob) { - public int Calc(DateTime dob) - { - return (int)((DateTime.Now - dob).TotalDays / 365); - } + return (int)((DateTime.Now - dob).TotalDays / 365); } } diff --git a/src/examples/AzureFunctionApp/DemoContext.cs b/src/examples/AzureFunctionApp/DemoContext.cs index f4c1e8de..035515f5 100644 --- a/src/examples/AzureFunctionApp/DemoContext.cs +++ b/src/examples/AzureFunctionApp/DemoContext.cs @@ -6,109 +6,108 @@ #nullable disable -namespace demo -{ - public class DemoContext : DbContext - { - public DemoContext(DbContextOptions opts) - : base(opts) { } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - } +namespace demo; - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity().HasKey(d => d.PersonId); - builder.Entity().HasKey(d => d.PersonId); - builder.Entity().HasOne(d => d.Director).WithMany(p => p.DirectorOf).HasForeignKey(d => d.DirectorId); - builder.Entity().HasMany(p => p.ActorIn).WithOne(a => a.Person); - builder.Entity().HasMany(p => p.WriterOf).WithOne(a => a.Person); - } - - [Description("Collection of Movies")] - public DbSet Movies { get; set; } - - [Description("Collection of Peoples")] - public DbSet People { get; set; } - - [Description("Collection of Actors")] - public DbSet Actors { get; set; } - public Dictionary Attributes { get; set; } = - new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" }, - }; - } +public class DemoContext : DbContext +{ + public DemoContext(DbContextOptions opts) + : base(opts) { } - public class Movie + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public uint Id { get; set; } - public string Name { get; set; } - - [Description("Enum of Genre")] - public Genre Genre { get; set; } - public DateTime Released { get; set; } - public List Actors { get; set; } - public List Writers { get; set; } - public Person Director { get; set; } - public uint? DirectorId { get; set; } - public double Rating { get; internal set; } + base.OnConfiguring(optionsBuilder); } - public class Actor + protected override void OnModelCreating(ModelBuilder builder) { - public uint PersonId { get; set; } - public Person Person { get; set; } - public uint MovieId { get; set; } - public Movie Movie { get; set; } + builder.Entity().HasKey(d => d.PersonId); + builder.Entity().HasKey(d => d.PersonId); + builder.Entity().HasOne(d => d.Director).WithMany(p => p.DirectorOf).HasForeignKey(d => d.DirectorId); + builder.Entity().HasMany(p => p.ActorIn).WithOne(a => a.Person); + builder.Entity().HasMany(p => p.WriterOf).WithOne(a => a.Person); } - public class Writer - { - public uint PersonId { get; set; } - public Person Person { get; set; } - public uint MovieId { get; set; } - public Movie Movie { get; set; } - } + [Description("Collection of Movies")] + public DbSet Movies { get; set; } - public enum Genre - { - [Description("Action movie type")] - Action, + [Description("Collection of Peoples")] + public DbSet People { get; set; } - [Description("Drama movie type")] - Drama, + [Description("Collection of Actors")] + public DbSet Actors { get; set; } + public Dictionary Attributes { get; set; } = + new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" }, + { "key3", "value3" }, + }; +} - [Description("Comedy movie type")] - Comedy, +public class Movie +{ + public uint Id { get; set; } + public string Name { get; set; } + + [Description("Enum of Genre")] + public Genre Genre { get; set; } + public DateTime Released { get; set; } + public List Actors { get; set; } + public List Writers { get; set; } + public Person Director { get; set; } + public uint? DirectorId { get; set; } + public double Rating { get; internal set; } +} - [Description("Horror movie type")] - Horror, +public class Actor +{ + public uint PersonId { get; set; } + public Person Person { get; set; } + public uint MovieId { get; set; } + public Movie Movie { get; set; } +} - [Description("Scifi movie type")] - Scifi, - } +public class Writer +{ + public uint PersonId { get; set; } + public Person Person { get; set; } + public uint MovieId { get; set; } + public Movie Movie { get; set; } +} - public class Person - { - public uint Id { get; set; } - - [GraphQLNotNull] - public string FirstName { get; set; } - - [GraphQLNotNull] - public string LastName { get; set; } - public DateTime Dob { get; set; } - public List ActorIn { get; set; } - public List WriterOf { get; set; } - public List DirectorOf { get; set; } - public DateTime? Died { get; set; } - public bool IsDeleted { get; set; } - } +public enum Genre +{ + [Description("Action movie type")] + Action, + + [Description("Drama movie type")] + Drama, + + [Description("Comedy movie type")] + Comedy, + + [Description("Horror movie type")] + Horror, + + [Description("Scifi movie type")] + Scifi, +} + +public class Person +{ + public uint Id { get; set; } + + [GraphQLNotNull] + public string FirstName { get; set; } + + [GraphQLNotNull] + public string LastName { get; set; } + public DateTime Dob { get; set; } + public List ActorIn { get; set; } + public List WriterOf { get; set; } + public List DirectorOf { get; set; } + public DateTime? Died { get; set; } + public bool IsDeleted { get; set; } } #nullable restore diff --git a/src/examples/AzureFunctionApp/Functions/GraphQL.cs b/src/examples/AzureFunctionApp/Functions/GraphQL.cs index 5c4628ce..e33f1480 100644 --- a/src/examples/AzureFunctionApp/Functions/GraphQL.cs +++ b/src/examples/AzureFunctionApp/Functions/GraphQL.cs @@ -13,57 +13,56 @@ using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; -namespace AzureFunctionApp.Functions +namespace AzureFunctionApp.Functions; + +public class GraphQL { - public class GraphQL + private readonly DemoContext _dbContext; + private readonly SchemaProvider _schemaProvider; + private readonly IServiceProvider? _serviceProvider; + private readonly ClaimsPrincipalAccessor? _claimsPrincipalAccessor; + + public GraphQL(DemoContext dbContext, SchemaProvider schemaProvider, IServiceProvider? serviceProvider, ClaimsPrincipalAccessor? claimsPrincipalAccessor) { - private readonly DemoContext _dbContext; - private readonly SchemaProvider _schemaProvider; - private readonly IServiceProvider? _serviceProvider; - private readonly ClaimsPrincipalAccessor? _claimsPrincipalAccessor; + _dbContext = dbContext; + _schemaProvider = schemaProvider; + _serviceProvider = serviceProvider; + _claimsPrincipalAccessor = claimsPrincipalAccessor; + } - public GraphQL(DemoContext dbContext, SchemaProvider schemaProvider, IServiceProvider? serviceProvider, ClaimsPrincipalAccessor? claimsPrincipalAccessor) + [FunctionName("GraphQL")] + public async Task GraphQlEndpoint([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "post" }, Route = "graphql")] HttpRequestMessage req) + { + var principal = _claimsPrincipalAccessor?.GetClaimsPrincipal(); + if (principal == null || !principal.Claims.Any()) { - _dbContext = dbContext; - _schemaProvider = schemaProvider; - _serviceProvider = serviceProvider; - _claimsPrincipalAccessor = claimsPrincipalAccessor; + return req.CreateErrorResponse(HttpStatusCode.Unauthorized, "Not logged in"); } - [FunctionName("GraphQL")] - public async Task GraphQlEndpoint([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "post" }, Route = "graphql")] HttpRequestMessage req) - { - var principal = _claimsPrincipalAccessor?.GetClaimsPrincipal(); - if (principal == null || !principal.Claims.Any()) - { - return req.CreateErrorResponse(HttpStatusCode.Unauthorized, "Not logged in"); - } - - var body = await req.Content!.ReadAsStringAsync(); - - var jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; - jsonOptions.Converters.Add(new JsonStringEnumConverter()); - var query = JsonSerializer.Deserialize(body, jsonOptions); - - if (query is null) - { - return new BadRequestObjectResult("No query or invalid query syntax in request body"); - } + var body = await req.Content!.ReadAsStringAsync(); - var results = _schemaProvider.ExecuteRequestWithContext(query, _dbContext, _serviceProvider, principal, new ExecutionOptions() { }); + var jsonOptions = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + jsonOptions.Converters.Add(new JsonStringEnumConverter()); + var query = JsonSerializer.Deserialize(body, jsonOptions); - if (results != null && results.Errors != null && results.Errors.Any()) - { - Console.Write(results.Errors.First().Message); - } - - return results; + if (query is null) + { + return new BadRequestObjectResult("No query or invalid query syntax in request body"); } - [FunctionName("GraphQLSchema")] - public string GraphQLSchema([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "get" }, Route = "graphql-schema")] HttpRequestMessage req) + var results = _schemaProvider.ExecuteRequestWithContext(query, _dbContext, _serviceProvider, principal, new ExecutionOptions() { }); + + if (results != null && results.Errors != null && results.Errors.Any()) { - return _schemaProvider.ToGraphQLSchemaString(); + Console.Write(results.Errors.First().Message); } + + return results; + } + + [FunctionName("GraphQLSchema")] + public string GraphQLSchema([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "get" }, Route = "graphql-schema")] HttpRequestMessage req) + { + return _schemaProvider.ToGraphQLSchemaString(); } } diff --git a/src/examples/AzureFunctionApp/GraphQLSchema.cs b/src/examples/AzureFunctionApp/GraphQLSchema.cs index fd38cff6..447dac49 100644 --- a/src/examples/AzureFunctionApp/GraphQLSchema.cs +++ b/src/examples/AzureFunctionApp/GraphQLSchema.cs @@ -5,54 +5,53 @@ using EntityGraphQL.Schema; using EntityGraphQL.Schema.FieldExtensions; -namespace demo +namespace demo; + +public class GraphQLSchema { - public class GraphQLSchema + public static void ConfigureSchema(SchemaProvider demoSchema) { - public static void ConfigureSchema(SchemaProvider demoSchema) + // Add custom root fields + demoSchema.UpdateQuery(queryType => + { + demoSchema.AddType>("PersonConnection", "Metadata about a person connection (paging over people)").AddAllFields(); + queryType.AddField("writers", db => db.People.Where(p => p.WriterOf.Any()), "List of writers"); + queryType.AddField("directors", db => db.People.Where(p => p.DirectorOf.Any()), "List of directors").UseSort(); + + queryType.ReplaceField("actors", (db) => db.Actors.Select(a => a.Person), "actors paged by connection & edges and orderable").UseFilter().UseSort().UseConnectionPaging(); + + queryType.AddField("actorsOffset", (db) => db.Actors.Select(a => a.Person).OrderBy(a => a.Id), "Actors with offset paging").UseOffsetPaging(); + }); + + // Add calculated fields to a type + demoSchema.UpdateType(type => { - // Add custom root fields - demoSchema.UpdateQuery(queryType => - { - demoSchema.AddType>("PersonConnection", "Metadata about a person connection (paging over people)").AddAllFields(); - queryType.AddField("writers", db => db.People.Where(p => p.WriterOf.Any()), "List of writers"); - queryType.AddField("directors", db => db.People.Where(p => p.DirectorOf.Any()), "List of directors").UseSort(); - - queryType.ReplaceField("actors", (db) => db.Actors.Select(a => a.Person), "actors paged by connection & edges and orderable").UseFilter().UseSort().UseConnectionPaging(); - - queryType.AddField("actorsOffset", (db) => db.Actors.Select(a => a.Person).OrderBy(a => a.Id), "Actors with offset paging").UseOffsetPaging(); - }); - - // Add calculated fields to a type - demoSchema.UpdateType(type => - { - type.AddField("name", l => $"{l.FirstName} {l.LastName}", "Person's name"); - // really poor example of using services e.g. you could just do below but pretend the service does something crazy like calls an API - // type.AddField("age", l => (int)((DateTime.Now - l.Dob).TotalDays / 365), "Show the person's age"); - // AgeService needs to be added to the ServiceProvider - type.AddField("age", "Show the person's age").Resolve((person, ageService) => ageService.Calc(person.Dob)); - type.AddField( - "filteredDirectorOf", - new { filter = ArgumentHelper.EntityQuery() }, - (person, args) => person.DirectorOf.WhereWhen(args.filter, args.filter.HasValue).OrderBy(a => a.Name), - "Get Director of based on filter" - ); - type.ReplaceField("writerOf", m => m.WriterOf.Select(a => a.Movie), "Movies they wrote"); - type.ReplaceField("actorIn", m => m.ActorIn.Select(a => a.Movie), "Movies they acted in"); - }); - - // replace fields. e.g. remove a many-to-many relationship - demoSchema.UpdateType(type => - { - type.ReplaceField("actors", m => m.Actors.Select(a => a.Person), "Actors in the movie"); - type.ReplaceField("writers", m => m.Writers.Select(a => a.Person), "Writers in the movie"); - }); - - // add some mutations (always last, or after the types they require have been added) - demoSchema.AddInputType("Detail", "Detail item").AddAllFields(); - demoSchema.Mutation().AddFrom(); - - File.WriteAllText("schema.graphql", demoSchema.ToGraphQLSchemaString()); - } + type.AddField("name", l => $"{l.FirstName} {l.LastName}", "Person's name"); + // really poor example of using services e.g. you could just do below but pretend the service does something crazy like calls an API + // type.AddField("age", l => (int)((DateTime.Now - l.Dob).TotalDays / 365), "Show the person's age"); + // AgeService needs to be added to the ServiceProvider + type.AddField("age", "Show the person's age").Resolve((person, ageService) => ageService.Calc(person.Dob)); + type.AddField( + "filteredDirectorOf", + new { filter = ArgumentHelper.EntityQuery() }, + (person, args) => person.DirectorOf.WhereWhen(args.filter, args.filter.HasValue).OrderBy(a => a.Name), + "Get Director of based on filter" + ); + type.ReplaceField("writerOf", m => m.WriterOf.Select(a => a.Movie), "Movies they wrote"); + type.ReplaceField("actorIn", m => m.ActorIn.Select(a => a.Movie), "Movies they acted in"); + }); + + // replace fields. e.g. remove a many-to-many relationship + demoSchema.UpdateType(type => + { + type.ReplaceField("actors", m => m.Actors.Select(a => a.Person), "Actors in the movie"); + type.ReplaceField("writers", m => m.Writers.Select(a => a.Person), "Writers in the movie"); + }); + + // add some mutations (always last, or after the types they require have been added) + demoSchema.AddInputType("Detail", "Detail item").AddAllFields(); + demoSchema.Mutation().AddFrom(); + + File.WriteAllText("schema.graphql", demoSchema.ToGraphQLSchemaString()); } } diff --git a/src/examples/AzureFunctionApp/Infrastructure/ClaimsPrincipalAccessor.cs b/src/examples/AzureFunctionApp/Infrastructure/ClaimsPrincipalAccessor.cs index 0c257c80..4cf5b2a7 100644 --- a/src/examples/AzureFunctionApp/Infrastructure/ClaimsPrincipalAccessor.cs +++ b/src/examples/AzureFunctionApp/Infrastructure/ClaimsPrincipalAccessor.cs @@ -6,50 +6,49 @@ using System.Text.Json; using Microsoft.AspNetCore.Http; -namespace demo.Infrastructure +namespace demo.Infrastructure; + +public class ClaimsPrincipalAccessor { - public class ClaimsPrincipalAccessor + private IHttpContextAccessor _contextAccessor; + + public ClaimsPrincipalAccessor(IHttpContextAccessor contextAccessor) { - private IHttpContextAccessor _contextAccessor; + _contextAccessor = contextAccessor; + } - public ClaimsPrincipalAccessor(IHttpContextAccessor contextAccessor) - { - _contextAccessor = contextAccessor; - } + private class ClientPrincipal + { + public string IdentityProvider { get; set; } = string.Empty; + public string UserId { get; set; } = string.Empty; + public string UserDetails { get; set; } = string.Empty; + public IEnumerable UserRoles { get; set; } = []; + } - private class ClientPrincipal - { - public string IdentityProvider { get; set; } = string.Empty; - public string UserId { get; set; } = string.Empty; - public string UserDetails { get; set; } = string.Empty; - public IEnumerable UserRoles { get; set; } = []; - } + public ClaimsPrincipal GetClaimsPrincipal() + { + var context = _contextAccessor.HttpContext; + var req = context?.Request; + var principal = new ClientPrincipal(); - public ClaimsPrincipal GetClaimsPrincipal() + if (req?.Headers?.TryGetValue("x-ms-client-principal", out var header) ?? false) { - var context = _contextAccessor.HttpContext; - var req = context?.Request; - var principal = new ClientPrincipal(); + var data = header[0]!; + var decoded = Convert.FromBase64String(data); + var json = Encoding.UTF8.GetString(decoded); + principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - if (req?.Headers?.TryGetValue("x-ms-client-principal", out var header) ?? false) - { - var data = header[0]!; - var decoded = Convert.FromBase64String(data); - var json = Encoding.UTF8.GetString(decoded); - principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + if (principal == null) + throw new BadHttpRequestException("Failed to parse client-principal"); - if (principal == null) - throw new BadHttpRequestException("Failed to parse client-principal"); + principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase) ?? new List(); - principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase) ?? new List(); + var identity = new ClaimsIdentity(principal.IdentityProvider); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId)); - var identity = new ClaimsIdentity(principal.IdentityProvider); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId)); - - return new ClaimsPrincipal(identity); - } - - return new ClaimsPrincipal(); + return new ClaimsPrincipal(identity); } + + return new ClaimsPrincipal(); } } diff --git a/src/examples/AzureFunctionApp/Mutations/DemoMutations.cs b/src/examples/AzureFunctionApp/Mutations/DemoMutations.cs index d7507898..b8e01164 100644 --- a/src/examples/AzureFunctionApp/Mutations/DemoMutations.cs +++ b/src/examples/AzureFunctionApp/Mutations/DemoMutations.cs @@ -6,157 +6,156 @@ using EntityGraphQL; using EntityGraphQL.Schema; -namespace demo.Mutations +namespace demo.Mutations; + +public class DemoMutations { - public class DemoMutations + [GraphQLMutation("Example of a mutation that takes 0 arguments")] + public Expression> ExampleNoArgs(DemoContext db) { - [GraphQLMutation("Example of a mutation that takes 0 arguments")] - public Expression> ExampleNoArgs(DemoContext db) - { - // do something smart here with db - db.Movies.Add(new Movie()); - - return ctx => ctx.Movies.First(); - } + // do something smart here with db + db.Movies.Add(new Movie()); - [GraphQLMutation("Example of a mutation that does not use the context or argments but does use registered services")] - public int ExampleNoArgsWithService(AgeService ageService) - { - // we returning a scalar, you do not require the Expression<> - // AgeService registered in DI. Use it here - return ageService.Calc(new Person().Dob); - } - - /// - /// Mutation methods must be marked with the [GraphQLMutation] attribute. - /// - /// This mutation can be used like - /// mutation MyMutation($genre: String!, $name: String!, $released: String!) { - /// addMovie(genre: $genre, name: $name, released: $released) { - /// id name - /// } - /// } - /// - /// The first parameter must be the Schema context. This lets you operate on that context. In this case the EF DB Context - /// The second parameter is a class that has public fields or properties matching the argument names you want to use in the mutation - /// - [GraphQLMutation("Add a new Movie object")] - public Expression> AddMovie(DemoContext db, AddMovieArgs args) - { - var movie = new Movie - { - Genre = args.Genre, - Name = args.Name, - Released = args.Released, - Rating = args.Rating, - }; - db.Movies.Add(movie); - db.SaveChanges(); - return ctx => ctx.Movies.First(m => m.Id == movie.Id); - } - - [GraphQLMutation] - public Expression>? AddActor(DemoContext db, [GraphQLArguments] AddActorArgs args, IGraphQLValidator validator) - { - if (string.IsNullOrEmpty(args.FirstName)) - validator.AddError("Name argument is required"); - if (db.Movies.FirstOrDefault(m => m.Id == args.MovieId) == null) - validator.AddError("MovieId not found"); - // ... do more validation - - if (validator.HasErrors) - return null; - - // we're here and valid - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.FirstName, - LastName = args.LastName, - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.First(p => p.Id == person.Id); - } - - /// - /// Poor example showing you how to return a list of items as a result - /// - /// - /// - /// - [GraphQLMutation] - public Expression>> AddActor2(DemoContext db, [GraphQLArguments] AddActorArgs args) - { - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.FirstName, - LastName = args.LastName, - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); - } - - [GraphQLMutation] - public Expression>> AddActor3(DemoContext db, [GraphQLArguments] AddActor3Args args) - { - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.Names.First(), - LastName = args.Names.Last(), - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); - } + return ctx => ctx.Movies.First(); } -#nullable disable + [GraphQLMutation("Example of a mutation that does not use the context or argments but does use registered services")] + public int ExampleNoArgsWithService(AgeService ageService) + { + // we returning a scalar, you do not require the Expression<> + // AgeService registered in DI. Use it here + return ageService.Calc(new Person().Dob); + } /// - /// Must be a public class. Public fields and Properties are the mutation's arguments + /// Mutation methods must be marked with the [GraphQLMutation] attribute. + /// + /// This mutation can be used like + /// mutation MyMutation($genre: String!, $name: String!, $released: String!) { + /// addMovie(genre: $genre, name: $name, released: $released) { + /// id name + /// } + /// } /// - [GraphQLArguments] - public class AddMovieArgs + /// The first parameter must be the Schema context. This lets you operate on that context. In this case the EF DB Context + /// The second parameter is a class that has public fields or properties matching the argument names you want to use in the mutation + /// + [GraphQLMutation("Add a new Movie object")] + public Expression> AddMovie(DemoContext db, AddMovieArgs args) { - public Genre Genre; - - [Required(AllowEmptyStrings = false, ErrorMessage = "Movie Name is required")] - public string Name { get; set; } - public double Rating { get; set; } - public DateTime Released; - public Detail Details { get; set; } + var movie = new Movie + { + Genre = args.Genre, + Name = args.Name, + Released = args.Released, + Rating = args.Rating, + }; + db.Movies.Add(movie); + db.SaveChanges(); + return ctx => ctx.Movies.First(m => m.Id == movie.Id); } - public class Detail + [GraphQLMutation] + public Expression>? AddActor(DemoContext db, [GraphQLArguments] AddActorArgs args, IGraphQLValidator validator) { - public string Description { get; set; } + if (string.IsNullOrEmpty(args.FirstName)) + validator.AddError("Name argument is required"); + if (db.Movies.FirstOrDefault(m => m.Id == args.MovieId) == null) + validator.AddError("MovieId not found"); + // ... do more validation + + if (validator.HasErrors) + return null; + + // we're here and valid + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.FirstName, + LastName = args.LastName, + }; + db.People.Add(person); + var actor = new Actor { MovieId = args.MovieId, Person = person }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.First(p => p.Id == person.Id); } - public class AddActorArgs + /// + /// Poor example showing you how to return a list of items as a result + /// + /// + /// + /// + [GraphQLMutation] + public Expression>> AddActor2(DemoContext db, [GraphQLArguments] AddActorArgs args) { - public string FirstName { get; set; } - public string LastName { get; set; } - public uint MovieId { get; set; } + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.FirstName, + LastName = args.LastName, + }; + db.People.Add(person); + var actor = new Actor { MovieId = args.MovieId, Person = person }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); } - public class AddActor3Args + [GraphQLMutation] + public Expression>> AddActor3(DemoContext db, [GraphQLArguments] AddActor3Args args) { - public List Names { get; set; } - public uint MovieId { get; set; } + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.Names.First(), + LastName = args.Names.Last(), + }; + db.People.Add(person); + var actor = new Actor { MovieId = args.MovieId, Person = person }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); } +} -#nullable restore +#nullable disable + +/// +/// Must be a public class. Public fields and Properties are the mutation's arguments +/// +[GraphQLArguments] +public class AddMovieArgs +{ + public Genre Genre; + + [Required(AllowEmptyStrings = false, ErrorMessage = "Movie Name is required")] + public string Name { get; set; } + public double Rating { get; set; } + public DateTime Released; + public Detail Details { get; set; } +} + +public class Detail +{ + public string Description { get; set; } } + +public class AddActorArgs +{ + public string FirstName { get; set; } + public string LastName { get; set; } + public uint MovieId { get; set; } +} + +public class AddActor3Args +{ + public List Names { get; set; } + public uint MovieId { get; set; } +} + +#nullable restore diff --git a/src/examples/AzureFunctionApp/Startup.cs b/src/examples/AzureFunctionApp/Startup.cs index aa15ab08..5fbedfc2 100644 --- a/src/examples/AzureFunctionApp/Startup.cs +++ b/src/examples/AzureFunctionApp/Startup.cs @@ -7,30 +7,29 @@ [assembly: FunctionsStartup(typeof(demo.Startup))] -namespace demo +namespace demo; + +public class Startup : FunctionsStartup { - public class Startup : FunctionsStartup + public override void Configure(IFunctionsHostBuilder builder) { - public override void Configure(IFunctionsHostBuilder builder) - { - builder.Services.AddDbContext(opt => opt.UseSqlite("Filename=demo.db")); + builder.Services.AddDbContext(opt => opt.UseSqlite("Filename=demo.db")); - builder.Services.AddSingleton(); - builder.Services.AddTransient(); + builder.Services.AddSingleton(); + builder.Services.AddTransient(); - builder - .Services.AddGraphQLSchema(options => + builder + .Services.AddGraphQLSchema(options => + { + options.PreBuildSchemaFromContext = schema => { - options.PreBuildSchemaFromContext = schema => - { - // add in needed mappings for our context - schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings"); - }; - options.ConfigureSchema = GraphQLSchema.ConfigureSchema; - // below this will generate the field names as they are from the reflected dotnet types - i.e matching the case - // builder.FieldNamer = name => name; - }) - .AddGraphQLValidator(); - } + // add in needed mappings for our context + schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings"); + }; + options.ConfigureSchema = GraphQLSchema.ConfigureSchema; + // below this will generate the field names as they are from the reflected dotnet types - i.e matching the case + // builder.FieldNamer = name => name; + }) + .AddGraphQLValidator(); } } diff --git a/src/examples/demo/AgeService.cs b/src/examples/demo/AgeService.cs index bab6b603..ef0b93dd 100644 --- a/src/examples/demo/AgeService.cs +++ b/src/examples/demo/AgeService.cs @@ -1,15 +1,14 @@ using System; -namespace demo +namespace demo; + +/// +/// Very poor example of injecting a service into fields +/// +public class AgeService { - /// - /// Very poor example of injecting a service into fields - /// - public class AgeService + public int Calc(DateTime dob) { - public int Calc(DateTime dob) - { - return (int)((DateTime.Now - dob).TotalDays / 365); - } + return (int)((DateTime.Now - dob).TotalDays / 365); } } diff --git a/src/examples/demo/DemoContext.cs b/src/examples/demo/DemoContext.cs index a70592e5..c89a0a74 100644 --- a/src/examples/demo/DemoContext.cs +++ b/src/examples/demo/DemoContext.cs @@ -6,126 +6,125 @@ using EntityGraphQL.Schema.FieldExtensions; using Microsoft.EntityFrameworkCore; -namespace demo +namespace demo; + +public class DemoContext : DbContext { - public class DemoContext : DbContext + public DemoContext(DbContextOptions opts) + : base(opts) { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public DemoContext(DbContextOptions opts) - : base(opts) { } + base.OnConfiguring(optionsBuilder); + } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - } + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().HasKey(d => d.PersonId); + builder.Entity().HasKey(d => d.PersonId); + builder.Entity().HasOne(d => d.Director).WithMany(p => p.DirectorOf).HasForeignKey(d => d.DirectorId); + builder.Entity().HasMany(p => p.ActorIn).WithOne(a => a.Person); + builder.Entity().HasMany(p => p.WriterOf).WithOne(a => a.Person); + } - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity().HasKey(d => d.PersonId); - builder.Entity().HasKey(d => d.PersonId); - builder.Entity().HasOne(d => d.Director).WithMany(p => p.DirectorOf).HasForeignKey(d => d.DirectorId); - builder.Entity().HasMany(p => p.ActorIn).WithOne(a => a.Person); - builder.Entity().HasMany(p => p.WriterOf).WithOne(a => a.Person); - } + [Description("Collection of Movies")] + [UseFilter] + [UseSort] + [UseConnectionPaging] + public DbSet Movies { get; set; } - [Description("Collection of Movies")] - [UseFilter] - [UseSort] - [UseConnectionPaging] - public DbSet Movies { get; set; } - - [Description("Collection of Peoples")] - public DbSet People { get; set; } - - [Description("Collection of Actors")] - public DbSet Actors { get; set; } - public Dictionary Attributes { get; set; } = - new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" }, - { "key3", "value3" }, - }; - } + [Description("Collection of Peoples")] + public DbSet People { get; set; } - public class Movie + [Description("Collection of Actors")] + public DbSet Actors { get; set; } + public Dictionary Attributes { get; set; } = + new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" }, + { "key3", "value3" }, + }; +} + +public class Movie +{ + public uint Id { get; set; } + public required string Name { get; set; } + + [Description("Enum of Genre")] + public virtual Genre Genre { get; set; } + public virtual DateTime Released { get; set; } + public virtual List Actors { get; set; } = []; + public virtual List Writers { get; set; } = []; + public virtual required Person Director { get; set; } + public uint? DirectorId { get; set; } + public double Rating { get; set; } + public uint CreatedBy { get; set; } + + [GraphQLField] + [Projectable] + public uint DirectorAgeAtRelease => (uint)((Released - Director.Dob).Days / 365); + + [GraphQLField] + public uint[] AgesOfActorsAtRelease() { - public uint Id { get; set; } - public string Name { get; set; } - - [Description("Enum of Genre")] - public virtual Genre Genre { get; set; } - public virtual DateTime Released { get; set; } - public virtual List Actors { get; set; } - public virtual List Writers { get; set; } - public virtual Person Director { get; set; } - public uint? DirectorId { get; set; } - public double Rating { get; set; } - public uint CreatedBy { get; set; } - - [GraphQLField] - [Projectable] - public uint DirectorAgeAtRelease => (uint)((Released - Director.Dob).Days / 365); - - [GraphQLField] - public uint[] AgesOfActorsAtRelease() + var ages = new List(); + foreach (var actor in Actors) { - var ages = new List(); - foreach (var actor in Actors) - { - ages.Add((uint)((Released - actor.Person.Dob).Days / 365)); - } - return ages.ToArray(); + ages.Add((uint)((Released - actor.Person.Dob).Days / 365)); } + return ages.ToArray(); } +} - public class Actor - { - public uint PersonId { get; set; } - public virtual Person Person { get; set; } - public uint MovieId { get; set; } - public virtual Movie Movie { get; set; } - } +public class Actor +{ + public uint PersonId { get; set; } + public virtual required Person Person { get; set; } + public uint MovieId { get; set; } + public virtual required Movie Movie { get; set; } +} - public class Writer - { - public uint PersonId { get; set; } - public virtual Person Person { get; set; } - public uint MovieId { get; set; } - public virtual Movie Movie { get; set; } - } +public class Writer +{ + public uint PersonId { get; set; } + public virtual required Person Person { get; set; } + public uint MovieId { get; set; } + public virtual required Movie Movie { get; set; } +} - public enum Genre - { - [Description("Action movie type")] - Action, +public enum Genre +{ + [Description("Action movie type")] + Action, - [Description("Drama movie type")] - Drama, + [Description("Drama movie type")] + Drama, - [Description("Comedy movie type")] - Comedy, + [Description("Comedy movie type")] + Comedy, - [Description("Horror movie type")] - Horror, + [Description("Horror movie type")] + Horror, - [Description("Scifi movie type")] - Scifi, - } + [Description("Scifi movie type")] + Scifi, +} - public class Person - { - public uint Id { get; set; } - - [GraphQLNotNull] - public string FirstName { get; set; } - - [GraphQLNotNull] - public string LastName { get; set; } - public DateTime Dob { get; set; } - public virtual List ActorIn { get; set; } - public virtual List WriterOf { get; set; } - public virtual List DirectorOf { get; set; } - public DateTime? Died { get; set; } - public bool IsDeleted { get; set; } - } +public class Person +{ + public uint Id { get; set; } + + [GraphQLNotNull] + public required string FirstName { get; set; } + + [GraphQLNotNull] + public required string LastName { get; set; } + public DateTime Dob { get; set; } + public virtual List ActorIn { get; set; } = []; + public virtual List WriterOf { get; set; } = []; + public virtual List DirectorOf { get; set; } = []; + public DateTime? Died { get; set; } + public bool IsDeleted { get; set; } } diff --git a/src/examples/demo/GraphQLSchema.cs b/src/examples/demo/GraphQLSchema.cs index c5f4cb49..581b7fd0 100644 --- a/src/examples/demo/GraphQLSchema.cs +++ b/src/examples/demo/GraphQLSchema.cs @@ -5,68 +5,67 @@ using EntityGraphQL.Schema; using EntityGraphQL.Schema.FieldExtensions; -namespace demo +namespace demo; + +public class GraphQLSchema { - public class GraphQLSchema + public static void ConfigureSchema(SchemaProvider demoSchema) { - public static void ConfigureSchema(SchemaProvider demoSchema) + // Add custom root fields + demoSchema.UpdateQuery(queryType => { - // Add custom root fields - demoSchema.UpdateQuery(queryType => - { - demoSchema.AddType>("PersonConnection", "Metadata about a person connection (paging over people)").AddAllFields(); - queryType.AddField("writers", db => db.People.Where(p => p.WriterOf.Any()), "List of writers"); - queryType.AddField("directors", db => db.People.Where(p => p.DirectorOf.Any()), "List of directors").UseSort((Person et) => et.LastName, SortDirection.ASC); + demoSchema.AddType>("PersonConnection", "Metadata about a person connection (paging over people)").AddAllFields(); + queryType.AddField("writers", db => db.People.Where(p => p.WriterOf.Any()), "List of writers"); + queryType.AddField("directors", db => db.People.Where(p => p.DirectorOf.Any()), "List of directors").UseSort((Person et) => et.LastName, SortDirection.ASC); - queryType.ReplaceField("actors", (db) => db.Actors.Select(a => a.Person), "actors paged by connection & edges and orderable").UseFilter().UseSort().UseConnectionPaging(); + queryType.ReplaceField("actors", (db) => db.Actors.Select(a => a.Person), "actors paged by connection & edges and orderable").UseFilter().UseSort().UseConnectionPaging(); - queryType.AddField("actorsOffset", (db) => db.Actors.Select(a => a.Person).OrderBy(a => a.Id), "Actors with offset paging").UseOffsetPaging(); - }); + queryType.AddField("actorsOffset", (db) => db.Actors.Select(a => a.Person).OrderBy(a => a.Id), "Actors with offset paging").UseOffsetPaging(); + }); - // Add calculated fields to a type - demoSchema.UpdateType(type => - { - type.AddField("name", l => $"{l.FirstName} {l.LastName}", "Person's name"); - // really poor example of using services e.g. you could just do below but pretend the service does something crazy like calls an API - // type.AddField("age", l => (int)((DateTime.Now - l.Dob).TotalDays / 365), "Show the person's age"); - // AgeService needs to be added to the ServiceProvider - type.AddField("age", "Show the person's age").Resolve((person, ageService) => ageService.Calc(person.Dob)); - type.AddField( - "filteredDirectorOf", - new { filter = ArgumentHelper.EntityQuery() }, - (person, args) => person.DirectorOf.WhereWhen(args.filter, args.filter.HasValue).OrderBy(a => a.Name), - "Get Director of based on filter" - ); - type.ReplaceField("writerOf", m => m.WriterOf.Select(a => a.Movie), "Movies they wrote"); - type.ReplaceField("actorIn", m => m.ActorIn.Select(a => a.Movie), "Movies they acted in"); - }); + // Add calculated fields to a type + demoSchema.UpdateType(type => + { + type.AddField("name", l => $"{l.FirstName} {l.LastName}", "Person's name"); + // really poor example of using services e.g. you could just do below but pretend the service does something crazy like calls an API + // type.AddField("age", l => (int)((DateTime.Now - l.Dob).TotalDays / 365), "Show the person's age"); + // AgeService needs to be added to the ServiceProvider + type.AddField("age", "Show the person's age").Resolve((person, ageService) => ageService.Calc(person.Dob)); + type.AddField( + "filteredDirectorOf", + new { filter = ArgumentHelper.EntityQuery() }, + (person, args) => person.DirectorOf.WhereWhen(args.filter, args.filter.HasValue).OrderBy(a => a.Name), + "Get Director of based on filter" + ); + type.ReplaceField("writerOf", m => m.WriterOf.Select(a => a.Movie), "Movies they wrote"); + type.ReplaceField("actorIn", m => m.ActorIn.Select(a => a.Movie), "Movies they acted in"); + }); - // replace fields. e.g. remove a many-to-many relationship - demoSchema.UpdateType(type => - { - type.ReplaceField("actors", m => m.Actors.Select(a => a.Person), "Actors in the movie"); - type.ReplaceField("writers", m => m.Writers.Select(a => a.Person), "Writers in the movie"); - type.AddField("contributedBy", "User who added this movie").Resolve((movie, users) => users.GetUser(movie.CreatedBy)); - }); + // replace fields. e.g. remove a many-to-many relationship + demoSchema.UpdateType(type => + { + type.ReplaceField("actors", m => m.Actors.Select(a => a.Person), "Actors in the movie"); + type.ReplaceField("writers", m => m.Writers.Select(a => a.Person), "Writers in the movie"); + type.AddField("contributedBy", "User who added this movie").Resolve((movie, users) => users.GetUser(movie.CreatedBy)); + }); - // let's pretend the Users come from another service - demoSchema.AddType( - "User", - "A user of the system", - userType => - { - userType.AddAllFields(); - // create a connection back to data from our main context - userType.AddField("moviesContributed", "Movies this user added").Resolve((user, context) => context.Movies.Where(m => m.CreatedBy == user.Id)); - } - ); - demoSchema.Query().AddField("users", "List of users").Resolve((_, users) => users.GetUsers()); + // let's pretend the Users come from another service + demoSchema.AddType( + "User", + "A user of the system", + userType => + { + userType.AddAllFields(); + // create a connection back to data from our main context + userType.AddField("moviesContributed", "Movies this user added").Resolve((user, context) => context.Movies.Where(m => m.CreatedBy == user.Id)); + } + ); + demoSchema.Query().AddField("users", "List of users").Resolve((_, users) => users.GetUsers()); - // add some mutations - demoSchema.AddInputType("Detail", "Detail item").AddAllFields(); - demoSchema.Mutation().AddFrom(); + // add some mutations + demoSchema.AddInputType("Detail", "Detail item").AddAllFields(); + demoSchema.Mutation().AddFrom(); - File.WriteAllText("schema.graphql", demoSchema.ToGraphQLSchemaString()); - } + File.WriteAllText("schema.graphql", demoSchema.ToGraphQLSchemaString()); } } diff --git a/src/examples/demo/Mutations/DemoMutations.cs b/src/examples/demo/Mutations/DemoMutations.cs index efab6e71..f0f06089 100644 --- a/src/examples/demo/Mutations/DemoMutations.cs +++ b/src/examples/demo/Mutations/DemoMutations.cs @@ -6,153 +6,174 @@ using EntityGraphQL; using EntityGraphQL.Schema; -namespace demo.Mutations +namespace demo.Mutations; + +public class DemoMutations { - public class DemoMutations + [GraphQLMutation("Example of a mutation that takes 0 arguments")] + public Expression> ExampleNoArgs(DemoContext db) { - [GraphQLMutation("Example of a mutation that takes 0 arguments")] - public Expression> ExampleNoArgs(DemoContext db) - { - // do something smart here with db - db.Movies.Add(new Movie()); - - return ctx => ctx.Movies.First(); - } - - [GraphQLMutation("Example of a mutation that does not use the context or argments but does use registered services")] - public int ExampleNoArgsWithService(AgeService ageService) - { - // we returning a scalar, you do not require the Expression<> - // AgeService registered in DI. Use it here - return ageService.Calc(new Person().Dob); - } - - /// - /// Mutation methods must be marked with the [GraphQLMutation] attribute. - /// - /// This mutation can be used like - /// mutation MyMutation($genre: String!, $name: String!, $released: String!) { - /// addMovie(genre: $genre, name: $name, released: $released) { - /// id name - /// } - /// } - /// - /// The first parameter must be the Schema context. This lets you operate on that context. In this case the EF DB Context - /// The second parameter is a class that has public fields or properties matching the argument names you want to use in the mutation - /// - [GraphQLMutation("Add a new Movie object")] - public Expression> AddMovie(DemoContext db, AddMovieArgs args) - { - var movie = new Movie + // do something smart here with db + db.Movies.Add( + new Movie { - Genre = args.Genre, - Name = args.Name, - Released = args.Released, - Rating = args.Rating, - }; - db.Movies.Add(movie); - db.SaveChanges(); - return ctx => ctx.Movies.First(m => m.Id == movie.Id); - } - - [GraphQLMutation] - public Expression> AddActor(DemoContext db, [GraphQLArguments] AddActorArgs args, IGraphQLValidator validator) - { - if (string.IsNullOrEmpty(args.FirstName)) - validator.AddError("Name argument is required"); - if (db.Movies.FirstOrDefault(m => m.Id == args.MovieId) == null) - validator.AddError("MovieId not found"); - // ... do more validation + Name = "Example Movie", + Director = new Person { FirstName = "Example", LastName = "Director" }, + } + ); - if (validator.HasErrors) - return null; + return ctx => ctx.Movies.First(); + } - // we're here and valid - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.FirstName, - LastName = args.LastName, - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.First(p => p.Id == person.Id); - } - - /// - /// Poor example showing you how to return a list of items as a result - /// - /// - /// - /// - [GraphQLMutation] - public Expression>> AddActor2(DemoContext db, [GraphQLArguments] AddActorArgs args) - { - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.FirstName, - LastName = args.LastName, - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); - } - - [GraphQLMutation] - public Expression>> AddActor3(DemoContext db, [GraphQLArguments] AddActor3Args args) - { - var person = new Person - { - Id = (uint)new Random().Next(), - FirstName = args.Names.First(), - LastName = args.Names.Last(), - }; - db.People.Add(person); - var actor = new Actor { MovieId = args.MovieId, Person = person, }; - db.Actors.Add(actor); - db.SaveChanges(); - - return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); - } + [GraphQLMutation("Example of a mutation that does not use the context or arguments but does use registered services")] + public int ExampleNoArgsWithService(AgeService ageService) + { + // we returning a scalar, you do not require the Expression<> + // AgeService registered in DI. Use it here + return ageService.Calc(new Person { FirstName = "Example", LastName = "Bob" }.Dob); } /// - /// Must be a public class. Public fields and Properties are the mutation's arguments + /// Mutation methods must be marked with the [GraphQLMutation] attribute. + /// + /// This mutation can be used like + /// mutation MyMutation($genre: String!, $name: String!, $released: String!) { + /// addMovie(genre: $genre, name: $name, released: $released) { + /// id name + /// } + /// } /// - [GraphQLArguments] - public class AddMovieArgs + /// The first parameter must be the Schema context. This lets you operate on that context. In this case the EF DB Context + /// The second parameter is a class that has public fields or properties matching the argument names you want to use in the mutation + /// + [GraphQLMutation("Add a new Movie object")] + public Expression> AddMovie(DemoContext db, AddMovieArgs args) { - public Genre Genre; - - [Required(AllowEmptyStrings = false, ErrorMessage = "Movie Name is required")] - public string Name { get; set; } - public double Rating { get; set; } - public DateTime Released; - public Detail Details { get; set; } + var movie = new Movie + { + Genre = args.Genre, + Name = args.Name, + Released = args.Released, + Rating = args.Rating, + Director = new Person { FirstName = "Example", LastName = "Director" }, + }; + db.Movies.Add(movie); + db.SaveChanges(); + return ctx => ctx.Movies.First(m => m.Id == movie.Id); } - public class Detail + [GraphQLMutation] + public Expression>? AddActor(DemoContext db, [GraphQLArguments] AddActorArgs args, IGraphQLValidator validator) { - public string Description { get; set; } + if (string.IsNullOrEmpty(args.FirstName)) + validator.AddError("Name argument is required"); + if (db.Movies.FirstOrDefault(m => m.Id == args.MovieId) == null) + validator.AddError("MovieId not found"); + // ... do more validation + + if (validator.HasErrors) + return null; + + // we're here and valid + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.FirstName, + LastName = args.LastName, + }; + db.People.Add(person); + var actor = new Actor + { + MovieId = args.MovieId, + Movie = new Movie { Name = "Movie 3", Director = person }, + Person = person, + }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.First(p => p.Id == person.Id); } - public class AddActorArgs + /// + /// Poor example showing you how to return a list of items as a result + /// + /// + /// + /// + [GraphQLMutation] + public Expression>> AddActor2(DemoContext db, [GraphQLArguments] AddActorArgs args) { - public string FirstName { get; set; } - public string LastName { get; set; } - public uint MovieId { get; set; } + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.FirstName, + LastName = args.LastName, + }; + db.People.Add(person); + var actor = new Actor + { + MovieId = args.MovieId, + Movie = new Movie { Name = "Movie 4", Director = person }, + Person = person, + }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); } - public class AddActor3Args + [GraphQLMutation] + public Expression>> AddActor3(DemoContext db, [GraphQLArguments] AddActor3Args args) { - public List Names { get; set; } - public uint MovieId { get; set; } + var person = new Person + { + Id = (uint)new Random().Next(), + FirstName = args.Names.First(), + LastName = args.Names.Last(), + }; + db.People.Add(person); + var actor = new Actor + { + MovieId = args.MovieId, + Movie = new Movie { Name = "Movie 4", Director = person }, + Person = person, + }; + db.Actors.Add(actor); + db.SaveChanges(); + + return (ctx) => ctx.People.Where(p => p.FirstName == person.FirstName); } } + +/// +/// Must be a public class. Public fields and Properties are the mutation's arguments +/// +[GraphQLArguments] +public class AddMovieArgs +{ + public Genre Genre; + + [Required(AllowEmptyStrings = false, ErrorMessage = "Movie Name is required")] + public required string Name { get; set; } + public double Rating { get; set; } + public DateTime Released; + public Detail? Details { get; set; } +} + +public class Detail +{ + public required string Description { get; set; } +} + +public class AddActorArgs +{ + public required string FirstName { get; set; } + public required string LastName { get; set; } + public uint MovieId { get; set; } +} + +public class AddActor3Args +{ + public required List Names { get; set; } + public uint MovieId { get; set; } +} diff --git a/src/examples/demo/Program.cs b/src/examples/demo/Program.cs index 5774edd3..54581c25 100755 --- a/src/examples/demo/Program.cs +++ b/src/examples/demo/Program.cs @@ -2,15 +2,14 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -namespace demo +namespace demo; + +public class Program { - public class Program + public static void Main() { - public static void Main() - { - var host = new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseIISIntegration().UseStartup().Build(); + var host = new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseIISIntegration().UseStartup().Build(); - host.Run(); - } + host.Run(); } } diff --git a/src/examples/demo/Startup.cs b/src/examples/demo/Startup.cs index bc24b895..8a255430 100755 --- a/src/examples/demo/Startup.cs +++ b/src/examples/demo/Startup.cs @@ -13,185 +13,188 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace demo +namespace demo; + +public class Startup { - public class Startup + public Startup(IWebHostEnvironment env) { - public Startup(IWebHostEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(opt => - opt.UseLazyLoadingProxies().UseSqlite("Filename=demo.db") - // .UseProjectables() - ); + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } - services.AddSingleton(); - services.AddSingleton(); + public IConfigurationRoot Configuration { get; } - services.AddLogging(logging => - { - logging.AddConsole(configure => Configuration.GetSection("Logging")); - logging.AddDebug(); - }); - // add schema provider so we don't need to create it every time - // if you want to override json serialization - say PascalCase response - // You will also need to override the default fieldNamer in SchemaProvider - // var jsonOptions = new JsonSerializerOptions - // { - // // the generated types use fields - // IncludeFields = true, - // }; - // services.AddSingleton(new DefaultGraphQLRequestDeserializer(jsonOptions)); - // services.AddSingleton(new DefaultGraphQLResponseSerializer(jsonOptions)); - // Or you could override the whole interface and do something other than JSON - - services - .AddGraphQLSchema(options => - { - options.PreBuildSchemaFromContext = schema => - { - // add in needed mappings for our context - schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings"); - }; - options.ConfigureSchema = GraphQLSchema.ConfigureSchema; - // below this will generate the field names as they are from the reflected dotnet types - i.e matching the case - // builder.FieldNamer = name => name; - }) - .AddGraphQLValidator(); - - services.AddRouting(); - services - .AddControllers() - .AddJsonOptions(opts => - { - // configure JSON serializer like this if you are return GraphQL execution results in your own controller - // assuming you want the default behavior of serializing GraphQL execution results to JSON - opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - opts.JsonSerializerOptions.IncludeFields = true; - opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, DemoContext db) + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => + opt.UseLazyLoadingProxies().UseSqlite("Filename=demo.db") + // .UseProjectables() + ); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddLogging(logging => { - CreateData(db); + logging.AddConsole(configure => Configuration.GetSection("Logging")); + logging.AddDebug(); + }); + // add schema provider so we don't need to create it every time + // if you want to override json serialization - say PascalCase response + // You will also need to override the default fieldNamer in SchemaProvider + // var jsonOptions = new JsonSerializerOptions + // { + // // the generated types use fields + // IncludeFields = true, + // }; + // services.AddSingleton(new DefaultGraphQLRequestDeserializer(jsonOptions)); + // services.AddSingleton(new DefaultGraphQLResponseSerializer(jsonOptions)); + // Or you could override the whole interface and do something other than JSON - app.UseFileServer(); + services + .AddGraphQLSchema(options => + { + options.PreBuildSchemaFromContext = schema => + { + // add in needed mappings for our context + schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings"); + }; + options.ConfigureSchema = GraphQLSchema.ConfigureSchema; + // below this will generate the field names as they are from the reflected dotnet types - i.e matching the case + // builder.FieldNamer = name => name; + }) + .AddGraphQLValidator(); - app.UseRouting(); - app.UseEndpoints(endpoints => + services.AddRouting(); + services + .AddControllers() + .AddJsonOptions(opts => { - endpoints.MapControllers(); - endpoints.MapGraphQL( - options: new ExecutionOptions - { - BeforeRootFieldExpressionBuild = (exp, op, field) => - { - if (exp.Type.IsGenericTypeQueryable()) - return Expression.Call( - typeof(EntityFrameworkQueryableExtensions), - nameof(EntityFrameworkQueryableExtensions.TagWith), - [exp.Type.GetGenericArguments()[0]], - exp, - Expression.Constant($"GQL op: {op ?? "n/a"}, field: {field}") - ); - return exp; - }, -#if DEBUG - IncludeDebugInfo = true -#endif - } - ); + // configure JSON serializer like this if you are return GraphQL execution results in your own controller + // assuming you want the default behavior of serializing GraphQL execution results to JSON + opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + opts.JsonSerializerOptions.IncludeFields = true; + opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }); - } + } - private static void CreateData(DemoContext db) - { - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, DemoContext db) + { + CreateData(db); - // add test data - var shawshank = new Movie - { - Name = "The Shawshank Redemption", - Genre = Genre.Drama, - Released = new DateTime(1994, 10, 14), - Rating = 9.2, - CreatedBy = 1, - Director = new Person - { - FirstName = "Frank", - LastName = "Darabont", - Dob = new DateTime(1959, 1, 28), - }, - Actors = new List + app.UseFileServer(); + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapGraphQL( + options: new ExecutionOptions { - new Actor + BeforeRootFieldExpressionBuild = (exp, op, field) => { - Person = new Person - { - Dob = new DateTime(1958, 10, 16), - FirstName = "Tim", - LastName = "Robbins", - }, + if (exp.Type.IsGenericTypeQueryable()) + return Expression.Call( + typeof(EntityFrameworkQueryableExtensions), + nameof(EntityFrameworkQueryableExtensions.TagWith), + [exp.Type.GetGenericArguments()[0]], + exp, + Expression.Constant($"GQL op: {op ?? "n/a"}, field: {field}") + ); + return exp; }, +#if DEBUG + IncludeDebugInfo = true +#endif } - }; - db.Movies.Add(shawshank); - var francis = new Person + ); + }); + } + + private static void CreateData(DemoContext db) + { + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + + // add test data + var shawshank = new Movie + { + Name = "The Shawshank Redemption", + Genre = Genre.Drama, + Released = new DateTime(1994, 10, 14), + Rating = 9.2, + CreatedBy = 1, + Director = new Person { - Dob = new DateTime(1939, 4, 7), - FirstName = "Francis", - LastName = "Coppola", - }; - var godfather = new Movie + FirstName = "Frank", + LastName = "Darabont", + Dob = new DateTime(1959, 1, 28), + }, + }; + shawshank.Actors = + [ + new Actor { - Name = "The Godfather", - Genre = Genre.Drama, - Released = new DateTime(1972, 3, 24), - Rating = 9.2, - Director = francis, - }; - godfather.Actors = new List + Person = new Person + { + Dob = new DateTime(1958, 10, 16), + FirstName = "Tim", + LastName = "Robbins", + }, + Movie = shawshank, + }, + ]; + + db.Movies.Add(shawshank); + var francis = new Person + { + Dob = new DateTime(1939, 4, 7), + FirstName = "Francis", + LastName = "Coppola", + }; + var godfather = new Movie + { + Name = "The Godfather", + Genre = Genre.Drama, + Released = new DateTime(1972, 3, 24), + Rating = 9.2, + Director = francis, + }; + godfather.Actors = + [ + new Actor { - new Actor + Person = new Person { - Person = new Person - { - Dob = new DateTime(1924, 4, 3), - Died = new DateTime(2004, 7, 1), - FirstName = "Marlon", - LastName = "Brando", - }, + Dob = new DateTime(1924, 4, 3), + Died = new DateTime(2004, 7, 1), + FirstName = "Marlon", + LastName = "Brando", }, - new Actor + Movie = godfather, + }, + new Actor + { + Person = new Person { - Person = new Person - { - Dob = new DateTime(1940, 4, 25), - FirstName = "Al", - LastName = "Pacino", - }, + Dob = new DateTime(1940, 4, 25), + FirstName = "Al", + LastName = "Pacino", }, - }; - godfather.Writers = new List { new Writer { Person = francis, } }; + Movie = godfather, + }, + ]; + godfather.Writers = [new Writer { Person = francis, Movie = godfather }]; - db.Movies.Add(godfather); + db.Movies.Add(godfather); - db.SaveChanges(); - } + db.SaveChanges(); } } diff --git a/src/examples/demo/User.cs b/src/examples/demo/User.cs index d1817556..31eddc29 100644 --- a/src/examples/demo/User.cs +++ b/src/examples/demo/User.cs @@ -6,8 +6,8 @@ namespace demo; public class User { public uint Id { get; set; } - public string Name { get; set; } - public string Email { get; set; } + public required string Name { get; set; } + public required string Email { get; set; } } // This could fetch users from some external API or other system @@ -21,24 +21,24 @@ public IEnumerable GetUsers() { Id = 1, Name = "John", - Email = "john@example.com" + Email = "john@example.com", }, new User { Id = 2, Name = "Jane", - Email = "jane@example.com" + Email = "jane@example.com", }, new User { Id = 3, Name = "Bob", - Email = "bob@example.com" + Email = "bob@example.com", }, }; } - public User GetUser(uint createdBy) + public User? GetUser(uint createdBy) { return GetUsers().FirstOrDefault(u => u.Id == createdBy); } diff --git a/src/examples/demo/demo.csproj b/src/examples/demo/demo.csproj index 9d5b5d34..87d6d719 100755 --- a/src/examples/demo/demo.csproj +++ b/src/examples/demo/demo.csproj @@ -4,6 +4,7 @@ net8.0 false true + enable diff --git a/src/examples/demo/schema.graphql b/src/examples/demo/schema.graphql index 5cf825d6..fbaa81ac 100644 --- a/src/examples/demo/schema.graphql +++ b/src/examples/demo/schema.graphql @@ -55,7 +55,7 @@ type Query { actors(filter: String, sort: [QueryDirectorsSortInput!], first: Int, after: String, last: Int, before: String): PersonConnection """Actors with offset paging""" actorsOffset(skip: Int, take: Int): PersonPage - attributes: [StringKeyValuePair!] + attributes: [StringKeyValuePair!]! """List of directors""" directors(sort: [QueryDirectorsSortInput!] = [{ lastName: ASC }]): [Person!] """Return a Movie by its Id""" @@ -63,7 +63,7 @@ type Query { """Collection of Movies""" movies(filter: String, sort: [QueryMoviesSortInput!], first: Int, after: String, last: Int, before: String): MovieConnection """Collection of Peoples""" - people: [Person!] + people: [Person!]! """Return a Person by its Id""" person(id: Int!): Person """List of users""" @@ -73,9 +73,9 @@ type Query { } type Actor { - movie: Movie + movie: Movie! movieId: Int! - person: Person + person: Person! personId: Int! } @@ -88,23 +88,23 @@ type ConnectionEdgePerson { """Detail item""" input Detail { - description: String + description: String! } type Movie { """Actors in the movie""" actors: [Person!] - agesOfActorsAtRelease: [Int!] + agesOfActorsAtRelease: [Int!]! """User who added this movie""" contributedBy: User createdBy: Int! - director: Person + director: Person! directorAgeAtRelease: Int! directorId: Int """Enum of Genre""" genre: Genre! id: Int! - name: String + name: String! rating: Float! released: Date! """Writers in the movie""" @@ -147,7 +147,7 @@ type Person { """Show the person's age""" age: Int! died: Date - directorOf: [Movie!] + directorOf: [Movie!]! dob: Date! """Get Director of based on filter""" filteredDirectorOf(filter: String): [Movie!] @@ -209,29 +209,29 @@ input QueryMoviesSortInput { """A user of the system""" type User { - email: String + email: String! id: Int! """Movies this user added""" moviesContributed: [Movie!] - name: String + name: String! } type Writer { - movie: Movie + movie: Movie! movieId: Int! - person: Person + person: Person! personId: Int! } type Mutation { - addActor(firstName: String, lastName: String, movieId: Int!): Person - addActor2(firstName: String, lastName: String, movieId: Int!): [Person!] - addActor3(names: [String!], movieId: Int!): [Person!] + addActor(firstName: String!, lastName: String!, movieId: Int!): Person! + addActor2(firstName: String!, lastName: String!, movieId: Int!): [Person!]! + addActor3(names: [String!]!, movieId: Int!): [Person!]! """Add a new Movie object""" - addMovie(name: String!, rating: Float!, details: Detail, genre: Genre!, released: Date!): Movie + addMovie(name: String!, rating: Float!, details: Detail, genre: Genre!, released: Date!): Movie! """Example of a mutation that takes 0 arguments""" - exampleNoArgs: Movie - """Example of a mutation that does not use the context or argments but does use registered services""" + exampleNoArgs: Movie! + """Example of a mutation that does not use the context or arguments but does use registered services""" exampleNoArgsWithService: Int! } diff --git a/src/examples/subscriptions/Mutations/ChatMutations.cs b/src/examples/subscriptions/Mutations/ChatMutations.cs index 5e03fbe0..bdccb8b9 100644 --- a/src/examples/subscriptions/Mutations/ChatMutations.cs +++ b/src/examples/subscriptions/Mutations/ChatMutations.cs @@ -2,24 +2,23 @@ using EntityGraphQL.Schema; using subscriptions.Services; -namespace subscriptions.Mutations +namespace subscriptions.Mutations; + +public class ChatMutations { - public class ChatMutations + [GraphQLMutation] + public static Expression> PostMessage(string message, string user, ChatContext db, ChatService chat) { - [GraphQLMutation] - public static Expression> PostMessage(string message, string user, ChatContext db, ChatService chat) - { - var postedMessage = chat.PostMessage(db, message, user); - // using the expression allows us to join back to the main schema if we want - return ctx => ctx.Messages.First(message => message.Id == postedMessage.Id); - } + var postedMessage = chat.PostMessage(db, message, user); + // using the expression allows us to join back to the main schema if we want + return ctx => ctx.Messages.First(message => message.Id == postedMessage.Id); + } - [GraphQLMutation] - public static Expression> PostMessageEvent(string message, string user, ChatContext db, ChatEventService chatEvents) - { - var postedMessage = chatEvents.PostEvent(db, MessageEventType.New, message, user); - // using the expression allows us to join back to the main schema if we want - return ctx => ctx.Messages.First(message => message.Id == postedMessage.Id); - } + [GraphQLMutation] + public static Expression> PostMessageEvent(string message, string user, ChatContext db, ChatEventService chatEvents) + { + var postedMessage = chatEvents.PostEvent(db, MessageEventType.New, message, user); + // using the expression allows us to join back to the main schema if we want + return ctx => ctx.Messages.First(message => message.Id == postedMessage.Id); } } diff --git a/src/examples/subscriptions/Program.cs b/src/examples/subscriptions/Program.cs index 96910294..8a52bea9 100644 --- a/src/examples/subscriptions/Program.cs +++ b/src/examples/subscriptions/Program.cs @@ -38,10 +38,7 @@ app.UseRouting(); app.UseGraphQLWebSockets(); -app.UseEndpoints(endpoints => -{ - endpoints.MapGraphQL(); -}); +app.MapGraphQL(); using (var scope = app.Services.CreateScope()) { diff --git a/src/examples/subscriptions/Services/ChatEventService.cs b/src/examples/subscriptions/Services/ChatEventService.cs index e2d917be..fe4a0ddd 100644 --- a/src/examples/subscriptions/Services/ChatEventService.cs +++ b/src/examples/subscriptions/Services/ChatEventService.cs @@ -49,7 +49,7 @@ public enum MessageEventType { New, Edit, - Delete + Delete, } public class MessageEvent diff --git a/src/examples/subscriptions/Subscriptions/ChatSubscriptions.cs b/src/examples/subscriptions/Subscriptions/ChatSubscriptions.cs index 2856c644..0e3a01c5 100644 --- a/src/examples/subscriptions/Subscriptions/ChatSubscriptions.cs +++ b/src/examples/subscriptions/Subscriptions/ChatSubscriptions.cs @@ -1,22 +1,20 @@ -using System; using System.Linq.Expressions; using EntityGraphQL.Schema; using subscriptions.Services; -namespace subscriptions.Subscriptions +namespace subscriptions.Subscriptions; + +public class ChatSubscriptions { - public class ChatSubscriptions + [GraphQLSubscription("Example of a subscription")] + public IObservable OnMessage(ChatService chat) { - [GraphQLSubscription("Example of a subscription")] - public IObservable OnMessage(ChatService chat) - { - return chat.Subscribe(); - } + return chat.Subscribe(); + } - [GraphQLSubscription("Example of a subscription that allows querying relations back on the main context")] - public IObservable>> OnMessageEvent(ChatEventService chat) - { - return chat.Subscribe(); - } + [GraphQLSubscription("Example of a subscription that allows querying relations back on the main context")] + public IObservable>> OnMessageEvent(ChatEventService chat) + { + return chat.Subscribe(); } } diff --git a/src/tests/EntityGraphQL.AspNet.Tests/AuthorizeDirective.cs b/src/tests/EntityGraphQL.AspNet.Tests/AuthorizeDirective.cs index af654e6d..db2bfb76 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/AuthorizeDirective.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/AuthorizeDirective.cs @@ -2,58 +2,57 @@ using EntityGraphQL.Schema.Directives; using Microsoft.AspNetCore.Authorization; -namespace EntityGraphQL.AspNet.Tests +namespace EntityGraphQL.AspNet.Tests; + +public class AuthorizeAttributeHandler : IExtensionAttributeHandler { - public class AuthorizeAttributeHandler : IExtensionAttributeHandler - { - public IEnumerable AttributeTypes => new Type[] { typeof(AuthorizeAttribute), typeof(GraphQLAuthorizePolicyAttribute) }; + public IEnumerable AttributeTypes => new Type[] { typeof(AuthorizeAttribute), typeof(GraphQLAuthorizePolicyAttribute) }; - public void ApplyExtension(IField field, Attribute attribute) + public void ApplyExtension(IField field, Attribute attribute) + { + if (attribute is AuthorizeAttribute authAttribute) { - if (attribute is AuthorizeAttribute authAttribute) - { - field.AddDirective(new AuthorizeDirective(authAttribute)); - } - else if (attribute is GraphQLAuthorizePolicyAttribute policyAttribute) - { - field.AddDirective(new AuthorizeDirective(policyAttribute)); - } + field.AddDirective(new AuthorizeDirective(authAttribute)); } - - public void ApplyExtension(ISchemaType type, Attribute attribute) + else if (attribute is GraphQLAuthorizePolicyAttribute policyAttribute) { - if (attribute is AuthorizeAttribute authAttribute) - { - type.AddDirective(new AuthorizeDirective(authAttribute)); - } - else if (attribute is GraphQLAuthorizePolicyAttribute policyAttribute) - { - type.AddDirective(new AuthorizeDirective(policyAttribute)); - } + field.AddDirective(new AuthorizeDirective(policyAttribute)); } } - public class AuthorizeDirective : ISchemaDirective + public void ApplyExtension(ISchemaType type, Attribute attribute) { - public AuthorizeDirective(AuthorizeAttribute authorize) + if (attribute is AuthorizeAttribute authAttribute) { - Roles = authorize.Roles; - Policies = new List() { authorize.Policy! }; + type.AddDirective(new AuthorizeDirective(authAttribute)); } - - public AuthorizeDirective(GraphQLAuthorizePolicyAttribute authorize) + else if (attribute is GraphQLAuthorizePolicyAttribute policyAttribute) { - Policies = authorize.Policies; + type.AddDirective(new AuthorizeDirective(policyAttribute)); } + } +} - public IEnumerable Location => new[] { TypeSystemDirectiveLocation.QueryObject, TypeSystemDirectiveLocation.FieldDefinition }; +public class AuthorizeDirective : ISchemaDirective +{ + public AuthorizeDirective(AuthorizeAttribute authorize) + { + Roles = authorize.Roles; + Policies = [authorize.Policy!]; + } - public string? Roles { get; } - public List Policies { get; } + public AuthorizeDirective(GraphQLAuthorizePolicyAttribute authorize) + { + Policies = authorize.Policies; + } - public string ToGraphQLSchemaString() - { - return $"@authorize(roles: \"{Roles}\", policies: \"{string.Join(", ", Policies)}\")"; - } + public IEnumerable Location => new[] { TypeSystemDirectiveLocation.QueryObject, TypeSystemDirectiveLocation.FieldDefinition }; + + public string? Roles { get; } + public List Policies { get; } + + public string ToGraphQLSchemaString() + { + return $"@authorize(roles: \"{Roles}\", policies: \"{string.Join(", ", Policies)}\")"; } } diff --git a/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQL.AspNet.Tests.csproj b/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQL.AspNet.Tests.csproj index cf42e55e..0a9616d2 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQL.AspNet.Tests.csproj +++ b/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQL.AspNet.Tests.csproj @@ -1,24 +1,26 @@ - net8.0 + net9.0 enable enable true false + true - + - - - - + + + + + diff --git a/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQLEndpointRouteExtensionsTests.cs b/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQLEndpointRouteExtensionsTests.cs new file mode 100644 index 00000000..36b2d73b --- /dev/null +++ b/src/tests/EntityGraphQL.AspNet.Tests/EntityGraphQLEndpointRouteExtensionsTests.cs @@ -0,0 +1,194 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Mvc.Testing; +using Xunit; + +namespace EntityGraphQL.AspNet.Tests; + +/// +/// Testing we follow https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md +/// +public class EntityGraphQLEndpointRouteExtensionsTests : IClassFixture> +{ + private readonly HttpClient client; + + public EntityGraphQLEndpointRouteExtensionsTests(WebApplicationFactory factory) + { + client = factory.CreateClient(); + } + + [Fact] + public async Task GraphQL_Endpoint_No_Accept_Header_Ok() + { + var graphqlRequest = new { query = "{ hello }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var response = await client.PostAsync("/graphql", requestBody); + + // will default to JSON + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_Returns_Correct_ResponseType_Json() + { + var graphqlRequest = new { query = "{ hello }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Content.Headers.ContentType?.MediaType == "application/json", "Content-Type is not application/json."); + + await CheckResponseIsValid(response); + } + + [Fact] + public async Task GraphQL_Endpoint_Returns_Correct_ResponseType_Graphql() + { + var graphqlRequest = new { query = "{ hello }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.Content.Headers.ContentType?.MediaType == "application/graphql-response+json", "Content-Type is not application/graphql-response+json."); + + await CheckResponseIsValid(response); + } + + [Fact] + public async Task GraphQL_Endpoint_Returns_Correct_ResponseType_Error() + { + var graphqlRequest = new { query = "{ hello }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("something/else")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_Returns_Ok_With_Error_For_Invalid_Query() + { + var graphqlRequest = new { query = "query { nonExistentField }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True( + response.Content.Headers.ContentType?.MediaType == "application/graphql-response+json" || response.Content.Headers.ContentType?.MediaType == "application/json", + "Content-Type is not application/graphql-response+json or application/json." + ); + var responseString = await response.Content.ReadAsStringAsync(); + var json = JsonNode.Parse(responseString); + + Assert.NotNull(json); + Assert.False(json.AsObject().ContainsKey("data"), "Expected 'data' field to be absent, but it exists in the JSON response."); + Assert.NotNull(json["errors"]); + Assert.True(json["errors"]!.AsArray().Count > 0); + } + + [Fact] + public async Task GraphQL_Endpoint_Fails_Without_ContentType() + { + var graphqlRequest = new { query = "{ hello }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + requestBody.Headers.ContentType = null; + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_400_On_Invalid_Json_Request() + { + var requestBody = new StringContent(@"{ query: not valid json }", Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_400_On_No_body() + { + var requestBody = new StringContent(@"", Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_400_On_Invalid_Json_Fields() + { + var requestBody = new StringContent(@"{""qeury"": ""{ hello }""}", Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task GraphQL_Endpoint_200_On_Valid_Request_Invalid_GraphQL() + { + var graphqlRequest = new { query = "{" }; // invalid graphql + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var json = JsonNode.Parse(responseString); + + Assert.NotNull(json); + Assert.False(json.AsObject().ContainsKey("data"), "Expected 'data' field to be absent, but it exists in the JSON response."); + Assert.NotNull(json["errors"]); + Assert.True(json["errors"]!.AsArray().Count > 0); + } + + [Fact] + public async Task GraphQL_Endpoint_200_No_Data_Mutation_Error() + { + var graphqlRequest = new { query = "mutation { mutationFail }" }; + var requestBody = new StringContent(System.Text.Json.JsonSerializer.Serialize(graphqlRequest), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/graphql") { Content = requestBody }; + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var json = JsonNode.Parse(responseString); + + Assert.NotNull(json); + Assert.False(json.AsObject().ContainsKey("data"), "Expected 'data' field to be absent, but it exists in the JSON response."); + Assert.NotNull(json["errors"]); + Assert.Equal("This is a test error", json["errors"]!.AsArray()[0]!["message"]!.GetValue()); + } + + private static async Task CheckResponseIsValid(HttpResponseMessage response) + { + var responseString = await response.Content.ReadAsStringAsync(); + var json = JsonNode.Parse(responseString); + + Assert.NotNull(json); + Assert.NotNull(json["data"]); + Assert.NotNull(json["data"]!["hello"]); + Assert.Equal("world", json["data"]!["hello"]!.ToString()); + } +} diff --git a/src/tests/EntityGraphQL.AspNet.Tests/GraphQLWebSocketServerTests.cs b/src/tests/EntityGraphQL.AspNet.Tests/GraphQLWebSocketServerTests.cs index 68b765ac..821c689c 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/GraphQLWebSocketServerTests.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/GraphQLWebSocketServerTests.cs @@ -26,7 +26,7 @@ private static (GraphQLWebSocketServer, MockSocket, Mock { @@ -220,7 +220,7 @@ public async void TestNextIsSent() } [Fact] - public async void TestQueryOverWebsocket() + public async Task TestQueryOverWebsocket() { var (server, socket, httpContext) = Setup(); httpContext.Object.RequestServices.GetRequiredService().Messages.Add(new Message { Text = "Hello" }); @@ -235,7 +235,7 @@ public async void TestQueryOverWebsocket() { Id = id, Type = GraphQLWSMessageType.Subscribe, - Payload = new QueryRequest { Query = "query GetIt { messages { text } }" } + Payload = new QueryRequest { Query = "query GetIt { messages { text } }" }, } ); socket.InSequence(recvSeq).SetupReceiveCloseAsync(); @@ -251,7 +251,7 @@ public async void TestQueryOverWebsocket() } [Fact] - public async void TestMutationOverWebsocket() + public async Task TestMutationOverWebsocket() { var (server, socket, httpContext) = Setup(); httpContext @@ -276,7 +276,7 @@ public async void TestMutationOverWebsocket() { Id = id, Type = GraphQLWSMessageType.Subscribe, - Payload = new QueryRequest { Query = "mutation MutateIt { postMessage(text: \"hey\") { text } }" } + Payload = new QueryRequest { Query = "mutation MutateIt { postMessage(text: \"hey\") { text } }" }, } ); diff --git a/src/tests/EntityGraphQL.AspNet.Tests/MockSocket.cs b/src/tests/EntityGraphQL.AspNet.Tests/MockSocket.cs index 403e88f3..46367fbc 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/MockSocket.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/MockSocket.cs @@ -6,96 +6,95 @@ using Moq.Language; using Xunit; -namespace EntityGraphQL.AspNet.Tests +namespace EntityGraphQL.AspNet.Tests; + +internal static class MockSocketHelper { - internal static class MockSocketHelper + public static void SetupReceiveAsync(this ISetupConditionResult mock, GraphQLWSRequest request, Action? runAfter = null) { - public static void SetupReceiveAsync(this ISetupConditionResult mock, GraphQLWSRequest request, Action? runAfter = null) - { - SetupReceiveAsync(mock, JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }), runAfter); - } - - public static void SetupReceiveAsync(this ISetupConditionResult mock, string message, Action? runAfter = null) - { - var buffer = new ArraySegment(Encoding.UTF8.GetBytes(message)); - mock.Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync(new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, null, null)) - .Callback( - (ArraySegment segment, CancellationToken token) => - { - buffer.CopyTo(segment); - if (runAfter != null) - Task.Run(runAfter, token); - } - ); - } + SetupReceiveAsync(mock, JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }), runAfter); + } - public static void SetupReceiveCloseAsync(this ISetupConditionResult mock) - { - mock.Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync(new WebSocketReceiveResult(0, WebSocketMessageType.Close, true, WebSocketCloseStatus.NormalClosure, "Test over")); - } + public static void SetupReceiveAsync(this ISetupConditionResult mock, string message, Action? runAfter = null) + { + var buffer = new ArraySegment(Encoding.UTF8.GetBytes(message)); + mock.Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, null, null)) + .Callback( + (ArraySegment segment, CancellationToken token) => + { + buffer.CopyTo(segment); + if (runAfter != null) + Task.Run(runAfter, token); + } + ); + } - public static void SetupAndAssertSendAsync(this ISetupConditionResult mock, string expectedMessage) - { - var expectedResponse = new ArraySegment(Encoding.UTF8.GetBytes(expectedMessage)); - mock.Setup(s => s.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback( - (ArraySegment segment, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token) => - { - Assert.Equal(expectedResponse, segment); - } - ); - } + public static void SetupReceiveCloseAsync(this ISetupConditionResult mock) + { + mock.Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(new WebSocketReceiveResult(0, WebSocketMessageType.Close, true, WebSocketCloseStatus.NormalClosure, "Test over")); } - internal class MockSocket : Mock + public static void SetupAndAssertSendAsync(this ISetupConditionResult mock, string expectedMessage) { - private WebSocketCloseStatus? closed = null; + var expectedResponse = new ArraySegment(Encoding.UTF8.GetBytes(expectedMessage)); + mock.Setup(s => s.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback( + (ArraySegment segment, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token) => + { + Assert.Equal(expectedResponse, segment); + } + ); + } +} - public MockSocket() - { - Setup(s => s.CloseStatus).Returns(() => closed).Verifiable(); - Setup(s => s.State).Returns(() => closed.HasValue ? WebSocketState.Closed : WebSocketState.Open); - } +internal class MockSocket : Mock +{ + private WebSocketCloseStatus? closed = null; - public void SetupReceiveAsync(string message) - { - var buffer = new ArraySegment(Encoding.UTF8.GetBytes(message)); - Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync(new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, null, null)) - .Callback( - (ArraySegment segment, CancellationToken token) => - { - buffer.CopyTo(segment); - } - ); - } + public MockSocket() + { + Setup(s => s.CloseStatus).Returns(() => closed).Verifiable(); + Setup(s => s.State).Returns(() => closed.HasValue ? WebSocketState.Closed : WebSocketState.Open); + } + + public void SetupReceiveAsync(string message) + { + var buffer = new ArraySegment(Encoding.UTF8.GetBytes(message)); + Setup(s => s.ReceiveAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(new WebSocketReceiveResult(buffer.Count, WebSocketMessageType.Text, true, null, null)) + .Callback( + (ArraySegment segment, CancellationToken token) => + { + buffer.CopyTo(segment); + } + ); + } - internal void SetupAndAssertCloseAsync(int closeCode, string closeMessage) - { - Setup(s => s.CloseAsync(It.IsAny(), It.IsAny(), CancellationToken.None)) - .Returns(Task.CompletedTask) - .Callback( - (WebSocketCloseStatus status, string description, CancellationToken token) => - { - closed = status; - Assert.Equal(closeMessage, description); - Assert.Equal(closeCode, (int)status); - } - ); - } + internal void SetupAndAssertCloseAsync(int closeCode, string closeMessage) + { + Setup(s => s.CloseAsync(It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.CompletedTask) + .Callback( + (WebSocketCloseStatus status, string description, CancellationToken token) => + { + closed = status; + Assert.Equal(closeMessage, description); + Assert.Equal(closeCode, (int)status); + } + ); + } - internal void SetupAndAssertSendAsync(string expectedMessage) - { - var expectedResponse = new ArraySegment(Encoding.UTF8.GetBytes(expectedMessage)); - Setup(s => s.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback( - (ArraySegment segment, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token) => - { - Assert.Equal(expectedResponse, segment); - } - ); - } + internal void SetupAndAssertSendAsync(string expectedMessage) + { + var expectedResponse = new ArraySegment(Encoding.UTF8.GetBytes(expectedMessage)); + Setup(s => s.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback( + (ArraySegment segment, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token) => + { + Assert.Equal(expectedResponse, segment); + } + ); } } diff --git a/src/tests/EntityGraphQL.AspNet.Tests/PoliciesTests.cs b/src/tests/EntityGraphQL.AspNet.Tests/PoliciesTests.cs index 9cf420ee..5803b0b9 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/PoliciesTests.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/PoliciesTests.cs @@ -4,273 +4,272 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace EntityGraphQL.AspNet.Tests +namespace EntityGraphQL.AspNet.Tests; + +public class PoliciesTests { - public class PoliciesTests + [Fact] + public void TestAttributeOnTypeFromObject() { - [Fact] - public void TestAttributeOnTypeFromObject() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions + { + AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!), + PreBuildSchemaFromContext = (context) => { - AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!), - PreBuildSchemaFromContext = (context) => - { - context.AddAttributeHandler(new AuthorizeAttributeHandler()); - } - } - ); - Assert.Single(schema.Type().RequiredAuthorization!.Policies); - Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("type Project @authorize(roles: \"\", policies: \"admin\") {", sdl); - Assert.Contains("type: Int! @authorize(roles: \"\", policies: \"can-type\")", sdl); - } - - [Fact] - public void TestAttributeOnTypeAddType() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + context.AddAttributeHandler(new AuthorizeAttributeHandler()); + }, + } + ); + Assert.Single(schema.Type().RequiredAuthorization!.Policies); + Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("type Project @authorize(roles: \"\", policies: \"admin\") {", sdl); + Assert.Contains("type: Int! @authorize(roles: \"\", policies: \"can-type\")", sdl); + } - var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); - schema.AddType("Project"); + [Fact] + public void TestAttributeOnTypeAddType() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); - Assert.Single(schema.Type().RequiredAuthorization!.Policies); - Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); + schema.AddType("Project"); - [Fact] - public void TestMethodOnType() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + Assert.Single(schema.Type().RequiredAuthorization!.Policies); + Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } - ); + [Fact] + public void TestMethodOnType() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); - Assert.Empty(schema.Type().RequiredAuthorization!.Policies); + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } + ); - schema.Type().RequiresAnyPolicy("admin"); + Assert.Empty(schema.Type().RequiredAuthorization!.Policies); - Assert.Single(schema.Type().RequiredAuthorization!.Policies); - Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + schema.Type().RequiresAnyPolicy("admin"); - [Fact] - public void TestAttributeOnField() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + Assert.Single(schema.Type().RequiredAuthorization!.Policies); + Assert.Equal("admin", schema.Type().RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } - ); + [Fact] + public void TestAttributeOnField() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); - Assert.Single(schema.Type().GetField("type", null).RequiredAuthorization!.Policies); - Assert.Equal("can-type", schema.Type().GetField("type", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } + ); - [Fact] - public void TestAttributeOnFieldAddField() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + Assert.Single(schema.Type().GetField("type", null).RequiredAuthorization!.Policies); + Assert.Equal("can-type", schema.Type().GetField("type", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); + [Fact] + public void TestAttributeOnFieldAddField() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); - schema.AddType("Project", "All about the project").AddField(p => p.Type, "The type info"); + var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); - Assert.Single(schema.Type().GetField("type", null).RequiredAuthorization!.Policies); - Assert.Equal("can-type", schema.Type().GetField("type", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + schema.AddType("Project", "All about the project").AddField(p => p.Type, "The type info"); - [Fact] - public void TestMethodOnField() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + Assert.Single(schema.Type().GetField("type", null).RequiredAuthorization!.Policies); + Assert.Equal("can-type", schema.Type().GetField("type", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); + [Fact] + public void TestMethodOnField() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); - schema.AddType("Task", "All about tasks").AddField(p => p.IsActive, "Is it active").RequiresAllPolicies("admin"); + var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); - Assert.Single(schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies); - Assert.Equal("admin", schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + schema.AddType("Task", "All about tasks").AddField(p => p.IsActive, "Is it active").RequiresAllPolicies("admin"); - [Fact] - public void TestMethodAllOnField() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); + Assert.Single(schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies); + Assert.Equal("admin", schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); + [Fact] + public void TestMethodAllOnField() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + + var schema = new SchemaProvider(new PolicyOrRoleBasedAuthorization(services.GetService()!)); - schema.AddType("Task", "All about tasks").AddField(p => p.IsActive, "Is it active").RequiresAllPolicies("admin", "can-type"); + schema.AddType("Task", "All about tasks").AddField(p => p.IsActive, "Is it active").RequiresAllPolicies("admin", "can-type"); - Assert.Equal(2, schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.Count()); - Assert.Equal("admin", schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); - } + Assert.Equal(2, schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.Count()); + Assert.Equal("admin", schema.Type().GetField("isActive", null).RequiredAuthorization!.Policies.ElementAt(0).ElementAt(0)); + } - [Fact] - public void TestFieldIsSecured() + [Fact] + public void TestFieldIsSecured() + { + var serviceCollection = new ServiceCollection(); + static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); + static bool canType(ClaimsPrincipal user) => user.IsInRole("can-type"); + serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy }, { "can-type", canType } })); + var services = serviceCollection.BuildServiceProvider(); + + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } + ); + + var claims = new ClaimsIdentity([new Claim(ClaimTypes.Role, "admin")], "authed"); + var gql = new QueryRequest { - var serviceCollection = new ServiceCollection(); - static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); - static bool canType(ClaimsPrincipal user) => user.IsInRole("can-type"); - serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy }, { "can-type", canType } })); - var services = serviceCollection.BuildServiceProvider(); - - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } - ); - - var claims = new ClaimsIdentity([new Claim(ClaimTypes.Role, "admin")], "authed"); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ projects { type } - }" - }; + }", + }; - var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - Assert.NotNull(result.Errors); - Assert.Equal("Field 'projects' - You are not authorized to access the 'type' field on type 'Project'.", result.Errors!.First().Message); + Assert.NotNull(result.Errors); + Assert.Equal("Field 'projects' - You are not authorized to access the 'type' field on type 'Project'.", result.Errors!.First().Message); - claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin"), new Claim(ClaimTypes.Role, "can-type") }, "authed"); - result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin"), new Claim(ClaimTypes.Role, "can-type") }, "authed"); + result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - Assert.Null(result.Errors); - } + Assert.Null(result.Errors); + } - [Fact] - public void TestTypeIsSecured() - { - var serviceCollection = new ServiceCollection(); - static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); - serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy } })); - var services = serviceCollection.BuildServiceProvider(); + [Fact] + public void TestTypeIsSecured() + { + var serviceCollection = new ServiceCollection(); + static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); + serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy } })); + var services = serviceCollection.BuildServiceProvider(); - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } - ); + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } + ); - var claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "not-admin") }, "authed"); - var gql = new QueryRequest - { - Query = - @"{ + var claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "not-admin") }, "authed"); + var gql = new QueryRequest + { + Query = + @"{ projects { id } - }" - }; + }", + }; - var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - Assert.Equal("Field 'projects' - You are not authorized to access the 'Project' type returned by field 'projects'.", result.Errors!.First().Message); + Assert.Equal("Field 'projects' - You are not authorized to access the 'Project' type returned by field 'projects'.", result.Errors!.First().Message); - claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin") }, "authed"); - result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin") }, "authed"); + result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - Assert.Null(result.Errors); - } + Assert.Null(result.Errors); + } - [Fact] - public void TestNonTopLevelTypeIsSecured() - { - var serviceCollection = new ServiceCollection(); - static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); - serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy } })); - var services = serviceCollection.BuildServiceProvider(); + [Fact] + public void TestNonTopLevelTypeIsSecured() + { + var serviceCollection = new ServiceCollection(); + static bool adminPolicy(ClaimsPrincipal user) => user.IsInRole("admin"); + serviceCollection.AddSingleton(new DummyAuthService(new Dictionary> { { "admin", adminPolicy } })); + var services = serviceCollection.BuildServiceProvider(); - var schema = SchemaBuilder.FromObject( - new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } - ); + var schema = SchemaBuilder.FromObject( + new SchemaBuilderSchemaOptions { AuthorizationService = new PolicyOrRoleBasedAuthorization(services.GetService()!) } + ); - var claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "not-admin") }, "authed"); - var gql = new QueryRequest - { - Query = - @"{ + var claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "not-admin") }, "authed"); + var gql = new QueryRequest + { + Query = + @"{ tasks { project { id } } - }" - }; - - var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + }", + }; - Assert.Equal("Field 'tasks' - You are not authorized to access the 'Project' type returned by field 'project'.", result.Errors!.First().Message); + var result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin") }, "authed"); - result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); + Assert.Equal("Field 'tasks' - You are not authorized to access the 'Project' type returned by field 'project'.", result.Errors!.First().Message); - Assert.Null(result.Errors); - } + claims = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin") }, "authed"); + result = schema.ExecuteRequestWithContext(gql, new PolicyDataContext(), services, new ClaimsPrincipal(claims)); - internal class PolicyDataContext - { - public IEnumerable Projects { get; set; } = new List(); - public IEnumerable Tasks { get; set; } = new List(); - } + Assert.Null(result.Errors); + } - [Authorize("admin")] - internal class Project - { - public int Id { get; set; } + internal class PolicyDataContext + { + public IEnumerable Projects { get; set; } = new List(); + public IEnumerable Tasks { get; set; } = new List(); + } - [GraphQLAuthorizePolicy("can-type")] - public int Type { get; set; } - public IEnumerable? Tasks { get; set; } - } + [Authorize("admin")] + internal class Project + { + public int Id { get; set; } - internal class Task - { - public int Id { get; set; } - public string? Name { get; set; } - public bool IsActive { get; set; } - public Project? Project { get; set; } - } + [GraphQLAuthorizePolicy("can-type")] + public int Type { get; set; } + public IEnumerable? Tasks { get; set; } } - internal class DummyAuthService : IAuthorizationService + internal class Task { - private readonly Dictionary>? policies; + public int Id { get; set; } + public string? Name { get; set; } + public bool IsActive { get; set; } + public Project? Project { get; set; } + } +} - public DummyAuthService() { } +internal class DummyAuthService : IAuthorizationService +{ + private readonly Dictionary>? policies; - public DummyAuthService(Dictionary> policies) - { - this.policies = policies; - } + public DummyAuthService() { } - public Task AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable requirements) - { - throw new NotImplementedException(); - } + public DummyAuthService(Dictionary> policies) + { + this.policies = policies; + } - public Task AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName) - { - if (policies == null) - return Task.FromResult(AuthorizationResult.Failed()); - if (!policies.ContainsKey(policyName)) - return Task.FromResult(AuthorizationResult.Failed()); + public Task AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable requirements) + { + throw new NotImplementedException(); + } + + public Task AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName) + { + if (policies == null) + return Task.FromResult(AuthorizationResult.Failed()); + if (!policies.ContainsKey(policyName)) + return Task.FromResult(AuthorizationResult.Failed()); - return policies[policyName](user) ? Task.FromResult(AuthorizationResult.Success()) : Task.FromResult(AuthorizationResult.Failed()); - } + return policies[policyName](user) ? Task.FromResult(AuthorizationResult.Success()) : Task.FromResult(AuthorizationResult.Failed()); } } diff --git a/src/tests/EntityGraphQL.AspNet.Tests/RuntimeTypeJsonConverterTests.cs b/src/tests/EntityGraphQL.AspNet.Tests/RuntimeTypeJsonConverterTests.cs index 83734d4f..aed4ee03 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/RuntimeTypeJsonConverterTests.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/RuntimeTypeJsonConverterTests.cs @@ -13,7 +13,7 @@ public class RuntimeTypeJsonConverterTests public enum Enum { First, - Second + Second, } public class BaseClass @@ -35,7 +35,7 @@ public void SerializeSubTypes() { Id = 1, Name = "Fred", - NameField = "Included" + NameField = "Included", }; var result = JsonSerializer.Serialize(item); Assert.Equal("{\"Id\":1,\"E\":0}", result); @@ -94,7 +94,7 @@ struct Test [Fact] public void SerializeStruct() { - dynamic item = new Test { items = new List(), hasNextPage = false }; + dynamic item = new Test { items = [], hasNextPage = false }; var graphqlResponseSerializer = new DefaultGraphQLResponseSerializer(); var memoryStream = new MemoryStream(); @@ -111,8 +111,8 @@ public void SerializeDictionary() { { "test", - new Test { items = new List(), hasNextPage = false } - } + new Test { items = [], hasNextPage = false } + }, }; var graphqlResponseSerializer = new DefaultGraphQLResponseSerializer(); @@ -128,7 +128,7 @@ public void SerializeBoolean() { var item = new { value = false }; - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new RuntimeTypeJsonConverter()); @@ -145,7 +145,7 @@ public void SerializeStringWithDifferentEncoder() { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new RuntimeTypeJsonConverter()); @@ -159,7 +159,7 @@ public void SerializeString() { var item = new { value = "Nick's coffee" }; - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new RuntimeTypeJsonConverter()); @@ -172,7 +172,7 @@ public void SerializeNestedObject() { var item = new { child = new { value = 3.4 } }; - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new RuntimeTypeJsonConverter()); options.IncludeFields = true; @@ -196,17 +196,17 @@ public void SerializeQueryResult() { Id = 1, Name = "Fred", - NameField = "Included" + NameField = "Included", }, new SubClass() { Id = 2, Name = "Wilma", NameField = null, - E = Enum.Second - } + E = Enum.Second, + }, } - } + }, } ); diff --git a/src/tests/EntityGraphQL.AspNet.Tests/TestQueryContext.cs b/src/tests/EntityGraphQL.AspNet.Tests/TestQueryContext.cs index 87db84b9..4cb2d35c 100644 --- a/src/tests/EntityGraphQL.AspNet.Tests/TestQueryContext.cs +++ b/src/tests/EntityGraphQL.AspNet.Tests/TestQueryContext.cs @@ -1,54 +1,53 @@ using EntityGraphQL.Schema; using EntityGraphQL.Subscriptions; -namespace EntityGraphQL.AspNet.Tests +namespace EntityGraphQL.AspNet.Tests; + +internal class TestQueryContext { - internal class TestQueryContext - { - public List Messages { get; set; } = new List(); - } + public List Messages { get; set; } = []; +} - internal class TestSubscription +internal class TestSubscription +{ + [GraphQLSubscription("Example of a subscription")] + public static IObservable OnMessage(TestChatService chat) { - [GraphQLSubscription("Example of a subscription")] - public static IObservable OnMessage(TestChatService chat) - { - return chat.Subscribe(); - } + return chat.Subscribe(); } +} - internal class TestChatService - { - private readonly List messages = new(); - private readonly Broadcaster broadcaster = new(); +internal class TestChatService +{ + private readonly List messages = []; + private readonly Broadcaster broadcaster = new(); - public Message PostMessage(string message) + public Message PostMessage(string message) + { + var msg = new Message { - var msg = new Message - { - Id = Guid.NewGuid(), - Text = message, - Timestamp = DateTime.Now, - }; + Id = Guid.NewGuid(), + Text = message, + Timestamp = DateTime.Now, + }; - lock (messages) - messages.Add(msg); + lock (messages) + messages.Add(msg); - broadcaster.OnNext(msg); + broadcaster.OnNext(msg); - return msg; - } - - public IObservable Subscribe() - { - return broadcaster; - } + return msg; } - internal class Message + public IObservable Subscribe() { - public Guid Id { get; set; } - public string? Text { get; set; } - public DateTime Timestamp { get; set; } + return broadcaster; } } + +internal class Message +{ + public Guid Id { get; set; } + public string? Text { get; set; } + public DateTime Timestamp { get; set; } +} diff --git a/src/tests/EntityGraphQL.EF.Tests/EntityGraphQL.EF.Tests.csproj b/src/tests/EntityGraphQL.EF.Tests/EntityGraphQL.EF.Tests.csproj index 09848dcc..255b7a39 100644 --- a/src/tests/EntityGraphQL.EF.Tests/EntityGraphQL.EF.Tests.csproj +++ b/src/tests/EntityGraphQL.EF.Tests/EntityGraphQL.EF.Tests.csproj @@ -1,12 +1,12 @@  - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 enable - + true enable false - 12.0 + 13.0 @@ -21,15 +21,21 @@ + + + + + + - - - + + + diff --git a/src/tests/EntityGraphQL.EF.Tests/FieldExtensionsOnQueryableTests.cs b/src/tests/EntityGraphQL.EF.Tests/FieldExtensionsOnQueryableTests.cs index 86ff0802..731a480d 100644 --- a/src/tests/EntityGraphQL.EF.Tests/FieldExtensionsOnQueryableTests.cs +++ b/src/tests/EntityGraphQL.EF.Tests/FieldExtensionsOnQueryableTests.cs @@ -60,9 +60,9 @@ internal class ActorService { private static readonly IDictionary> peopleByMovies = new Dictionary>() { - [1] = [new("Alec Guinness") { Id = 1 }, new("Mark Hamill") { Id = 2 },], - [2] = [new("Carrie Fisher") { Id = 1 }, new("Mark Hamill") { Id = 2 },], - [3] = [new("Harrison Ford") { Id = 1 }, new("Mark Hamill") { Id = 2 },] + [1] = [new("Alec Guinness") { Id = 1 }, new("Mark Hamill") { Id = 2 }], + [2] = [new("Carrie Fisher") { Id = 1 }, new("Mark Hamill") { Id = 2 }], + [3] = [new("Harrison Ford") { Id = 1 }, new("Mark Hamill") { Id = 2 }], }; public static IEnumerable GetByMovie(int movieId) diff --git a/src/tests/EntityGraphQL.EF.Tests/FilterExtensionTests.cs b/src/tests/EntityGraphQL.EF.Tests/FilterExtensionTests.cs index 5a93f768..c2c33f20 100644 --- a/src/tests/EntityGraphQL.EF.Tests/FilterExtensionTests.cs +++ b/src/tests/EntityGraphQL.EF.Tests/FilterExtensionTests.cs @@ -17,7 +17,7 @@ public void SupportNullableDateTime() @"query Query($filter: String!) { actors(filter: $filter) { id name } }", - Variables = new QueryVariables { { "filter", "birthday > \"2024-09-08T07:00:00.000Z\"" } } + Variables = new QueryVariables { { "filter", "birthday > \"2024-09-08T07:00:00.000Z\"" } }, }; using var factory = new TestDbContextFactory(); var data = factory.CreateContext(); diff --git a/src/tests/EntityGraphQL.EF.Tests/ListToSingleTests.cs b/src/tests/EntityGraphQL.EF.Tests/ListToSingleTests.cs index 20c74c50..16dc106d 100644 --- a/src/tests/EntityGraphQL.EF.Tests/ListToSingleTests.cs +++ b/src/tests/EntityGraphQL.EF.Tests/ListToSingleTests.cs @@ -71,7 +71,7 @@ public void TestListToSingleWithFind() new List { new("Alec Guinness") { Id = 1 }, - new("Mark Hamill") { Id = 2 } + new("Mark Hamill") { Id = 2 }, } ); data.SaveChanges(); diff --git a/src/tests/EntityGraphQL.EF.Tests/NullHandlingTests.cs b/src/tests/EntityGraphQL.EF.Tests/NullHandlingTests.cs index d06a658f..71a0d84d 100644 --- a/src/tests/EntityGraphQL.EF.Tests/NullHandlingTests.cs +++ b/src/tests/EntityGraphQL.EF.Tests/NullHandlingTests.cs @@ -41,7 +41,7 @@ public void Uses_SelectWithNullCheck_ForExecuteServiceFieldsSeparatelyIsFalse() ); AssertExpression.Matches(compiledExpr, expr); return expr; - } + }, } ); @@ -83,7 +83,7 @@ public void DoesNotUse_SelectWithNullCheck_ForExecuteServiceFieldsSeparatelyIsTr ); AssertExpression.Matches(compiledExpr, expr); return expr; - } + }, } ); @@ -139,7 +139,7 @@ public void Uses_SelectWithNullCheck_ForExecuteServiceFieldsSeparatelyIsTrueWith ); AssertExpression.Matches(compiledExpr, expr); return expr; - } + }, } ); diff --git a/src/tests/EntityGraphQL.EF.Tests/ServicesWithQueryableTests.cs b/src/tests/EntityGraphQL.EF.Tests/ServicesWithQueryableTests.cs index ecd2cc60..b58d6954 100644 --- a/src/tests/EntityGraphQL.EF.Tests/ServicesWithQueryableTests.cs +++ b/src/tests/EntityGraphQL.EF.Tests/ServicesWithQueryableTests.cs @@ -26,7 +26,7 @@ public void TestServiceFieldWithQueryable() config { type } mainActor { name } } - }" + }", }; var serviceCollection = new ServiceCollection(); @@ -37,17 +37,7 @@ public void TestServiceFieldWithQueryable() serviceCollection.AddSingleton(data); var serviceProvider = serviceCollection.BuildServiceProvider(); data.Database.EnsureCreated(); - data.Movies.AddRange( - new Movie("A New Hope") - { - Id = 10, - Actors = new List - { - new("Alec Guinness") { Id = 1 }, - new("Mark Hamill") { Id = 2 } - } - } - ); + data.Movies.AddRange(new Movie("A New Hope") { Id = 10, Actors = [new("Alec Guinness") { Id = 1 }, new("Mark Hamill") { Id = 2 }] }); data.SaveChanges(); var res = schema.ExecuteRequest(gql, serviceProvider, null); @@ -78,7 +68,7 @@ public void TestGenericMethodToUpdateType() entityName } } - }" + }", }; var serviceCollection = new ServiceCollection(); @@ -86,17 +76,7 @@ public void TestGenericMethodToUpdateType() var data = factory.CreateContext(); serviceCollection.AddSingleton(data); var serviceProvider = serviceCollection.BuildServiceProvider(); - data.Movies.AddRange( - new Movie("A New Hope") - { - Id = 10, - Actors = new List - { - new Actor("Alec Guinness") { Id = 1 }, - new Actor("Mark Hamill") { Id = 2 } - } - } - ); + data.Movies.AddRange(new Movie("A New Hope") { Id = 10, Actors = [new Actor("Alec Guinness") { Id = 1 }, new Actor("Mark Hamill") { Id = 2 }] }); data.SaveChanges(); var res = schema.ExecuteRequest(gql, serviceProvider, null); @@ -114,10 +94,10 @@ public void TestServiceBackToDbManyToOne() schema.UpdateType(config => { // connect back to the main query types - config.AddField("movie", "Get movie").ResolveWithService((c, db) => db.Movies.Where(m => m.Id == c.Id).FirstOrDefault()); + config.AddField("movie", "Get movie").Resolve((c, db) => db.Movies.Where(m => m.Id == c.Id).FirstOrDefault()); }); - schema.Query().AddField("configs", "Get configs").ResolveWithService((p, srv) => srv.GetList(3, 100)).IsNullable(false); + schema.Query().AddField("configs", "Get configs").Resolve((p, srv) => srv.GetList(3, 100)).IsNullable(false); var gql = new QueryRequest { Query = @@ -128,7 +108,7 @@ public void TestServiceBackToDbManyToOne() director { name } } } - }" + }", }; var serviceCollection = new ServiceCollection(); @@ -176,7 +156,7 @@ public void TestFilterWithServiceReferenceNotSelected() data.SaveChanges(); schema.Query().ReplaceField("actors", ctx => ctx.Actors, "Return list of people").UseFilter(); - schema.Type().AddField("age", "Persons age").ResolveWithService((person, ager) => ager.GetAge(person.Birthday)); + schema.Type().AddField("age", "Persons age").Resolve((person, ager) => ager.GetAge(person.Birthday)); var gql = new QueryRequest { Query = diff --git a/src/tests/EntityGraphQL.Tests.Util/AssertExpression.cs b/src/tests/EntityGraphQL.Tests.Util/AssertExpression.cs index b3e5d724..af37fa1f 100644 --- a/src/tests/EntityGraphQL.Tests.Util/AssertExpression.cs +++ b/src/tests/EntityGraphQL.Tests.Util/AssertExpression.cs @@ -9,7 +9,7 @@ public enum AssertExpressionType MemberInit, Any, MemberBinding, - Constant + Constant, } public class AssertExpression(AssertExpressionType type, params object?[] arguments) diff --git a/src/tests/EntityGraphQL.Tests.Util/EntityGraphQL.Tests.Util.csproj b/src/tests/EntityGraphQL.Tests.Util/EntityGraphQL.Tests.Util.csproj index 6b0073d0..f5649fb5 100644 --- a/src/tests/EntityGraphQL.Tests.Util/EntityGraphQL.Tests.Util.csproj +++ b/src/tests/EntityGraphQL.Tests.Util/EntityGraphQL.Tests.Util.csproj @@ -1,10 +1,11 @@  - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 enable enable - 12.0 + false + 13.0 diff --git a/src/tests/EntityGraphQL.Tests/ConnectionPaging/ConnectionPagingTests.cs b/src/tests/EntityGraphQL.Tests/ConnectionPaging/ConnectionPagingTests.cs index 84bd9f37..4799b777 100644 --- a/src/tests/EntityGraphQL.Tests/ConnectionPaging/ConnectionPagingTests.cs +++ b/src/tests/EntityGraphQL.Tests/ConnectionPaging/ConnectionPagingTests.cs @@ -327,8 +327,8 @@ name id dynamic lastPeople = result.Data!["people"]!; // cursors MQ, Mg, Mw, NA, NQ - // first Mg, Mw - // last Mg, Mw + // first Mg, Mw + // last Mg, Mw Assert.Equal(firstPeople.pageInfo.startCursor, lastPeople.pageInfo.startCursor); Assert.Equal(Enumerable.First(firstPeople.edges).cursor, Enumerable.First(lastPeople.edges).cursor); @@ -784,14 +784,14 @@ private static void FillProjectData(TestDataContext data) new Task { Id = 2, Name = "Task 3" }, new Task { Id = 3, Name = "Task 4" }, new Task { Id = 4, Name = "Task 5" }, - ] - } + ], + }, ]; } private static void FillData(TestDataContext data) { - data.People = new() { MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Crow"), MakePerson("Jill", "Castle"), MakePerson("Jack", "Snider"), }; + data.People = [MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Crow"), MakePerson("Jill", "Castle"), MakePerson("Jack", "Snider")]; } private static Person MakePerson(string fname, string lname) @@ -800,20 +800,20 @@ private static Person MakePerson(string fname, string lname) { Id = peopleCnt++, Name = fname, - LastName = lname + LastName = lname, }; } private class TestDataContext2 : TestDataContext { [UseConnectionPaging] - public override List People { get; set; } = new List(); + public override List People { get; set; } = []; } [Fact] public void IdPropertyStillGenerated() { var schema = SchemaBuilder.FromObject(); - Assert.NotEmpty(schema.Query().GetFields().Where(x => x.Name == "person")); + Assert.Contains(schema.Query().GetFields(), x => x.Name == "person"); } } diff --git a/src/tests/EntityGraphQL.Tests/ConnectionPaging/CursorSerializationTests.cs b/src/tests/EntityGraphQL.Tests/ConnectionPaging/CursorSerializationTests.cs index 7f05291b..7de3a570 100644 --- a/src/tests/EntityGraphQL.Tests/ConnectionPaging/CursorSerializationTests.cs +++ b/src/tests/EntityGraphQL.Tests/ConnectionPaging/CursorSerializationTests.cs @@ -1,30 +1,29 @@ using EntityGraphQL.Schema.FieldExtensions; using Xunit; -namespace EntityGraphQL.Tests.ConnectionPaging +namespace EntityGraphQL.Tests.ConnectionPaging; + +public class CursorSerializationTests { - public class CursorSerializationTests + [Fact] + public void TestSerializeAndDeserialize() { - [Fact] - public void TestSerializeAndDeserialize() - { - const int val = 0; - var cursor = ConnectionHelper.SerializeCursor(val); - var valBack = ConnectionHelper.DeserializeCursor(cursor); + const int val = 0; + var cursor = ConnectionHelper.SerializeCursor(val); + var valBack = ConnectionHelper.DeserializeCursor(cursor); - Assert.NotNull(valBack); - Assert.Equal(val, valBack); - } + Assert.NotNull(valBack); + Assert.Equal(val, valBack); + } - [Fact] - public void TestSerializeAndDeserializeLarge() - { - const int val = int.MaxValue; - var cursor = ConnectionHelper.SerializeCursor(val); - var valBack = ConnectionHelper.DeserializeCursor(cursor); + [Fact] + public void TestSerializeAndDeserializeLarge() + { + const int val = int.MaxValue; + var cursor = ConnectionHelper.SerializeCursor(val); + var valBack = ConnectionHelper.DeserializeCursor(cursor); - Assert.NotNull(valBack); - Assert.Equal(val, valBack); - } + Assert.NotNull(valBack); + Assert.Equal(val, valBack); } } diff --git a/src/tests/EntityGraphQL.Tests/ConnectionPaging/GetCursorTests.cs b/src/tests/EntityGraphQL.Tests/ConnectionPaging/GetCursorTests.cs index c17ea1bc..8d81dd90 100644 --- a/src/tests/EntityGraphQL.Tests/ConnectionPaging/GetCursorTests.cs +++ b/src/tests/EntityGraphQL.Tests/ConnectionPaging/GetCursorTests.cs @@ -1,101 +1,100 @@ using EntityGraphQL.Schema.FieldExtensions; using Xunit; -namespace EntityGraphQL.Tests.ConnectionPaging +namespace EntityGraphQL.Tests.ConnectionPaging; + +public class GetCursorTests { - public class GetCursorTests + [Fact] + public void TestAllNull() { - [Fact] - public void TestAllNull() - { - var args = new ConnectionArgs(); - var index = 4; - var cursor = ConnectionHelper.GetCursor(args, index); - // non zero based index - Assert.Equal(ConnectionHelper.SerializeCursor(index + 1), cursor); - } + var args = new ConnectionArgs(); + var index = 4; + var cursor = ConnectionHelper.GetCursor(args, index); + // non zero based index + Assert.Equal(ConnectionHelper.SerializeCursor(index + 1), cursor); + } - [Fact] - public void TestOnlyFirst() - { - var args = new ConnectionArgs { First = 4, }; - var index = 4; - var cursor = ConnectionHelper.GetCursor(args, index); - // non zero based index - Assert.Equal(ConnectionHelper.SerializeCursor(index + 1), cursor); - } + [Fact] + public void TestOnlyFirst() + { + var args = new ConnectionArgs { First = 4 }; + var index = 4; + var cursor = ConnectionHelper.GetCursor(args, index); + // non zero based index + Assert.Equal(ConnectionHelper.SerializeCursor(index + 1), cursor); + } - [Fact] - public void TestFirstAndAfter() - { - var args = new ConnectionArgs { First = 4, AfterNum = 3 }; - var index = 3; // index of item is 0 based so this is the 4th item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(7), cursor); - } + [Fact] + public void TestFirstAndAfter() + { + var args = new ConnectionArgs { First = 4, AfterNum = 3 }; + var index = 3; // index of item is 0 based so this is the 4th item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(7), cursor); + } - [Fact] - public void TestOnlyLast() - { - var args = new ConnectionArgs { Last = 4, TotalCount = 10 }; - var index = 3; // 4th item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(10), cursor); - } + [Fact] + public void TestOnlyLast() + { + var args = new ConnectionArgs { Last = 4, TotalCount = 10 }; + var index = 3; // 4th item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(10), cursor); + } - [Fact] - public void TestLastAndBefore() + [Fact] + public void TestLastAndBefore() + { + var args = new ConnectionArgs { - var args = new ConnectionArgs - { - Last = 4, - BeforeNum = 6, - TotalCount = 10 - }; - var index = 1; // 2nd item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(3), cursor); - } + Last = 4, + BeforeNum = 6, + TotalCount = 10, + }; + var index = 1; // 2nd item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(3), cursor); + } - [Fact] - public void TestLastAndBefore2() + [Fact] + public void TestLastAndBefore2() + { + var args = new ConnectionArgs { - var args = new ConnectionArgs - { - Last = 3, - BeforeNum = 4, - TotalCount = 10 - }; - var index = 0; // 1st item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(1), cursor); - } + Last = 3, + BeforeNum = 4, + TotalCount = 10, + }; + var index = 0; // 1st item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(1), cursor); + } - [Fact] - public void TestOnlyBefore() - { - var args = new ConnectionArgs { BeforeNum = 4, TotalCount = 10 }; - var index = 0; // 1st item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(1), cursor); - } + [Fact] + public void TestOnlyBefore() + { + var args = new ConnectionArgs { BeforeNum = 4, TotalCount = 10 }; + var index = 0; // 1st item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(1), cursor); + } - [Fact] - public void TestOnlyBefore2() - { - var args = new ConnectionArgs { BeforeNum = 8, TotalCount = 10 }; - var index = 3; // 4th item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(4), cursor); - } + [Fact] + public void TestOnlyBefore2() + { + var args = new ConnectionArgs { BeforeNum = 8, TotalCount = 10 }; + var index = 3; // 4th item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(4), cursor); + } - [Fact] - public void TestOnlyAfter() - { - var args = new ConnectionArgs { AfterNum = 7, }; - var index = 1; // 2nd item - var cursor = ConnectionHelper.GetCursor(args, index); - Assert.Equal(ConnectionHelper.SerializeCursor(9), cursor); - } + [Fact] + public void TestOnlyAfter() + { + var args = new ConnectionArgs { AfterNum = 7 }; + var index = 1; // 2nd item + var cursor = ConnectionHelper.GetCursor(args, index); + Assert.Equal(ConnectionHelper.SerializeCursor(9), cursor); } } diff --git a/src/tests/EntityGraphQL.Tests/ConnectionPaging/PageInfoTests.cs b/src/tests/EntityGraphQL.Tests/ConnectionPaging/PageInfoTests.cs index b731fb66..88b6ca46 100644 --- a/src/tests/EntityGraphQL.Tests/ConnectionPaging/PageInfoTests.cs +++ b/src/tests/EntityGraphQL.Tests/ConnectionPaging/PageInfoTests.cs @@ -1,116 +1,115 @@ using EntityGraphQL.Schema.FieldExtensions; using Xunit; -namespace EntityGraphQL.Tests.ConnectionPaging +namespace EntityGraphQL.Tests.ConnectionPaging; + +public class PageInfoTests { - public class PageInfoTests + [Fact] + public void TestAllNull() + { + var args = new ConnectionArgs(); + var info = new ConnectionPageInfo(10, args); + + Assert.False(info.HasNextPage); + Assert.False(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); + } + + [Fact] + public void TestOnlyFirst() + { + var args = new ConnectionArgs { First = 4 }; + var info = new ConnectionPageInfo(10, args); + + Assert.True(info.HasNextPage); + Assert.False(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(4), info.EndCursor); + } + + [Fact] + public void TestFirstAndAfter() + { + var args = new ConnectionArgs { First = 4, AfterNum = 3 }; + var info = new ConnectionPageInfo(10, args); + + Assert.True(info.HasNextPage); + Assert.True(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(4), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(7), info.EndCursor); + } + + [Fact] + public void TestOnlyLast() + { + var args = new ConnectionArgs { Last = 3 }; + var info = new ConnectionPageInfo(10, args); + + Assert.False(info.HasNextPage); + Assert.True(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(8), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); + } + + [Fact] + public void TestLastAndBefore() { - [Fact] - public void TestAllNull() - { - var args = new ConnectionArgs(); - var info = new ConnectionPageInfo(10, args); - - Assert.False(info.HasNextPage); - Assert.False(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); - } - - [Fact] - public void TestOnlyFirst() - { - var args = new ConnectionArgs { First = 4 }; - var info = new ConnectionPageInfo(10, args); - - Assert.True(info.HasNextPage); - Assert.False(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(4), info.EndCursor); - } - - [Fact] - public void TestFirstAndAfter() - { - var args = new ConnectionArgs { First = 4, AfterNum = 3 }; - var info = new ConnectionPageInfo(10, args); - - Assert.True(info.HasNextPage); - Assert.True(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(4), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(7), info.EndCursor); - } - - [Fact] - public void TestOnlyLast() - { - var args = new ConnectionArgs { Last = 3 }; - var info = new ConnectionPageInfo(10, args); - - Assert.False(info.HasNextPage); - Assert.True(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(8), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); - } - - [Fact] - public void TestLastAndBefore() - { - var args = new ConnectionArgs { Last = 3, BeforeNum = 6 }; - var info = new ConnectionPageInfo(10, args); - - Assert.True(info.HasNextPage); - Assert.True(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(3), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(5), info.EndCursor); - } - - [Fact] - public void TestOnlyBefore() - { - var args = new ConnectionArgs { BeforeNum = 6 }; - var info = new ConnectionPageInfo(10, args); - - Assert.True(info.HasNextPage); - Assert.False(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(5), info.EndCursor); - } - - [Fact] - public void TestOnlyAfter() - { - var args = new ConnectionArgs { AfterNum = 6 }; - var info = new ConnectionPageInfo(10, args); - - Assert.False(info.HasNextPage); - Assert.True(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(7), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); - } - - [Fact] - public void TestOverTotal() - { - var args = new ConnectionArgs { First = 5, AfterNum = 7 }; - var info = new ConnectionPageInfo(10, args); - - Assert.False(info.HasNextPage); - Assert.True(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(8), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); - } - - [Fact] - public void TestBeforeStart() - { - var args = new ConnectionArgs { Last = 5, BeforeNum = 3 }; - var info = new ConnectionPageInfo(10, args); - - Assert.True(info.HasNextPage); - Assert.False(info.HasPreviousPage); - Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); - Assert.Equal(ConnectionHelper.SerializeCursor(2), info.EndCursor); - } + var args = new ConnectionArgs { Last = 3, BeforeNum = 6 }; + var info = new ConnectionPageInfo(10, args); + + Assert.True(info.HasNextPage); + Assert.True(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(3), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(5), info.EndCursor); + } + + [Fact] + public void TestOnlyBefore() + { + var args = new ConnectionArgs { BeforeNum = 6 }; + var info = new ConnectionPageInfo(10, args); + + Assert.True(info.HasNextPage); + Assert.False(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(5), info.EndCursor); + } + + [Fact] + public void TestOnlyAfter() + { + var args = new ConnectionArgs { AfterNum = 6 }; + var info = new ConnectionPageInfo(10, args); + + Assert.False(info.HasNextPage); + Assert.True(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(7), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); + } + + [Fact] + public void TestOverTotal() + { + var args = new ConnectionArgs { First = 5, AfterNum = 7 }; + var info = new ConnectionPageInfo(10, args); + + Assert.False(info.HasNextPage); + Assert.True(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(8), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(10), info.EndCursor); + } + + [Fact] + public void TestBeforeStart() + { + var args = new ConnectionArgs { Last = 5, BeforeNum = 3 }; + var info = new ConnectionPageInfo(10, args); + + Assert.True(info.HasNextPage); + Assert.False(info.HasPreviousPage); + Assert.Equal(ConnectionHelper.SerializeCursor(1), info.StartCursor); + Assert.Equal(ConnectionHelper.SerializeCursor(2), info.EndCursor); } } diff --git a/src/tests/EntityGraphQL.Tests/ConnectionPaging/SkipTakeTests.cs b/src/tests/EntityGraphQL.Tests/ConnectionPaging/SkipTakeTests.cs index f7c38960..3992443b 100644 --- a/src/tests/EntityGraphQL.Tests/ConnectionPaging/SkipTakeTests.cs +++ b/src/tests/EntityGraphQL.Tests/ConnectionPaging/SkipTakeTests.cs @@ -1,100 +1,98 @@ using EntityGraphQL.Schema.FieldExtensions; using Xunit; -namespace EntityGraphQL.Tests.ConnectionPaging +namespace EntityGraphQL.Tests.ConnectionPaging; + +public class SkipTakeTests { - public class SkipTakeTests + [Fact] + public void TestAllNull() + { + var args = new ConnectionArgs(); + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Null(take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(0, skip); + } + + [Fact] + public void TestOnlyFirst() + { + var args = new ConnectionArgs { First = 3 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Equal(3, take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(0, skip); + } + + [Fact] + public void TestFirstAndAfter() + { + var args = new ConnectionArgs { First = 3, AfterNum = 2 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Equal(3, take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(2, skip); + } + + [Fact] + public void TestOnlyLast() + { + var args = new ConnectionArgs { Last = 3, TotalCount = 10 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Equal(3, take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(7, skip); + } + + [Fact] + public void TestLastAndBefore() + { + var args = new ConnectionArgs { Last = 4, BeforeNum = 7 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Equal(4, take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(2, skip); + } + + [Fact] + public void TestLastAndBefore_WhenLastGreaterThanBeforeNum() + { + var args = new ConnectionArgs { Last = 4, BeforeNum = 2 }; + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(0, skip); + + var offset = ConnectionHelper.GetSkipNumber(args, false); + + var take = ConnectionHelper.GetTakeNumber(args, offset); + Assert.Equal(1, take); + } + + [Fact] + public void TestOnlyBefore() + { + var args = new ConnectionArgs { BeforeNum = 7 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Equal(6, take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(0, skip); + } + + [Fact] + public void TestOnlyAfter() { - [Fact] - public void TestAllNull() - { - var args = new ConnectionArgs(); - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Null(take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(0, skip); - } - - [Fact] - public void TestOnlyFirst() - { - var args = new ConnectionArgs { First = 3 }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Equal(3, take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(0, skip); - } - - [Fact] - public void TestFirstAndAfter() - { - var args = new ConnectionArgs { First = 3, AfterNum = 2 }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Equal(3, take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(2, skip); - } - - [Fact] - public void TestOnlyLast() - { - var args = new ConnectionArgs { Last = 3, TotalCount = 10 }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Equal(3, take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(7, skip); - } - - [Fact] - public void TestLastAndBefore() - { - var args = new ConnectionArgs { Last = 4, BeforeNum = 7, }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Equal(4, take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(2, skip); - } - - [Fact] - public void TestLastAndBefore_WhenLastGreaterThanBeforeNum() - { - var args = new ConnectionArgs { Last = 4, BeforeNum = 2, }; - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(0, skip); - - var offset = ConnectionHelper.GetSkipNumber(args, false); - - var take = ConnectionHelper.GetTakeNumber(args, offset); - Assert.Equal(1, take); - - } - - [Fact] - public void TestOnlyBefore() - { - var args = new ConnectionArgs { BeforeNum = 7, }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Equal(6, take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(0, skip); - } - - [Fact] - public void TestOnlyAfter() - { - var args = new ConnectionArgs { AfterNum = 5, }; - var take = ConnectionHelper.GetTakeNumber(args); - Assert.Null(take); - - var skip = ConnectionHelper.GetSkipNumber(args); - Assert.Equal(5, skip); - } + var args = new ConnectionArgs { AfterNum = 5 }; + var take = ConnectionHelper.GetTakeNumber(args); + Assert.Null(take); + + var skip = ConnectionHelper.GetSkipNumber(args); + Assert.Equal(5, skip); } } diff --git a/src/tests/EntityGraphQL.Tests/EntityGraphQL.Tests.csproj b/src/tests/EntityGraphQL.Tests/EntityGraphQL.Tests.csproj index aa9824f1..4bde308f 100755 --- a/src/tests/EntityGraphQL.Tests/EntityGraphQL.Tests.csproj +++ b/src/tests/EntityGraphQL.Tests/EntityGraphQL.Tests.csproj @@ -2,10 +2,9 @@ Tests for EntityGraphQL - net8.0 + net9.0 true false - 12.0 enable @@ -15,10 +14,10 @@ - - - - + + + + diff --git a/src/tests/EntityGraphQL.Tests/EntityQuery/DefaultMethodProviderTests.cs b/src/tests/EntityGraphQL.Tests/EntityQuery/DefaultMethodProviderTests.cs index 71787112..5e23036e 100644 --- a/src/tests/EntityGraphQL.Tests/EntityQuery/DefaultMethodProviderTests.cs +++ b/src/tests/EntityGraphQL.Tests/EntityQuery/DefaultMethodProviderTests.cs @@ -202,35 +202,29 @@ public void SupportUseFilterIsAnyMethodOnNullable() // This would be your Entity/Object graph you use with EntityFramework private class TestSchema { - public IEnumerable People - { - get - { - return - [ - new Person - { - Id = 9, - Name = "Bob", - Guid = Guid.NewGuid() - }, - new Person(), - new Person - { - Id = 9, - Name = "Boba", - Guid = Guid.NewGuid() - }, - new Person - { - Id = 9, - Name = "Robin", - Guid = Guid.NewGuid(), - Age = 44 - }, - ]; - } - } + public IEnumerable People => + [ + new Person + { + Id = 9, + Name = "Bob", + Guid = Guid.NewGuid(), + }, + new Person(), + new Person + { + Id = 9, + Name = "Boba", + Guid = Guid.NewGuid(), + }, + new Person + { + Id = 9, + Name = "Robin", + Guid = Guid.NewGuid(), + Age = 44, + }, + ]; } private class Person diff --git a/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerTests.cs b/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerTests.cs index b10922dc..042ca35d 100644 --- a/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerTests.cs +++ b/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerTests.cs @@ -43,6 +43,20 @@ public void CompilesNullConstant() Assert.Null(exp.Execute()); } + [Fact] + public void CompilesTrueConstant() + { + var exp = EntityQueryCompiler.Compile("true", executionOptions); + Assert.True((bool)exp.Execute()!); + } + + [Fact] + public void CompilesFalseConstant() + { + var exp = EntityQueryCompiler.Compile("false", executionOptions); + Assert.False((bool)exp.Execute()!); + } + [Fact] public void CompilesStringConstant() { @@ -71,6 +85,14 @@ public void CompilesIdentityCall() Assert.Equal("returned value", exp.Execute(new TestSchema())); } + [Fact] + public void CompilesIdentityCallWithKeyWord() + { + // null is the keyword + var exp = EntityQueryCompiler.Compile("nullableInt", SchemaBuilder.FromObject(), executionOptions); + Assert.Equal(55, exp.Execute(new TestSchema())); + } + [Fact] public void FailsIdentityNotThere() { @@ -208,7 +230,7 @@ public void TestEntityQueryWorks() { new TestEntity("bob") { Relation = new Person { Id = 1 } }, new TestEntity("mary") { Relation = new Person { Id = 2 } }, - new TestEntity("Jake") { Relation = new Person { Id = 5 } } + new TestEntity("Jake") { Relation = new Person { Id = 5 } }, }; Assert.Equal(3, list.Count); var results = list.Where((Func)compiledResult.LambdaExpression.Compile()); @@ -231,7 +253,7 @@ public void TestEntityQueryWorksWithDates(string dateValue) { new("First") { When = new DateTime(2020, 08, 10) }, new("Second") { When = new DateTime(2020, 08, 11) }, - new("Third") { When = new DateTime(2020, 08, 12) } + new("Third") { When = new DateTime(2020, 08, 12) }, }; Assert.Equal(3, list.Count()); var results = list.Where((Func)compiledResult.LambdaExpression.Compile()); @@ -251,7 +273,7 @@ public void TestEntityQueryFailsOnInvalidDate(string dateValue) var schemaProvider = SchemaBuilder.FromObject(); schemaProvider.AddType("DateTime"); //<-- Tried with and without var compiledResult = EntityQueryCompiler.Compile($"when >= {dateValue}", schemaProvider, executionOptions); - var list = new List { new Entry("First") { When = new DateTime(2020, 08, 10) }, }; + var list = new List { new Entry("First") { When = new DateTime(2020, 08, 10) } }; Assert.Single(list); var results = list.Where((Func)compiledResult.LambdaExpression.Compile()); @@ -294,7 +316,7 @@ public void TestEntityQueryWorksWithDateTimeOffsets(string dateValue, int count) { new("First") { WhenOffset = new DateTimeOffset(2020, 08, 10, 0, 0, 0, TimeSpan.FromTicks(0)) }, new("Second") { WhenOffset = new DateTimeOffset(2020, 08, 11, 13, 21, 11, TimeSpan.FromTicks(0)) }, - new("Third") { WhenOffset = new DateTimeOffset(2020, 08, 12, 13, 22, 11, TimeSpan.FromTicks(0)) } + new("Third") { WhenOffset = new DateTimeOffset(2020, 08, 12, 13, 22, 11, TimeSpan.FromTicks(0)) }, }; Assert.Equal(3, list.Count); var filter = (Func)compiledResult.LambdaExpression.Compile(); @@ -344,8 +366,8 @@ public void CompilesEnum2() People = new List { new() { Gender = Gender.Female }, - new() { Gender = Gender.Other } - } + new() { Gender = Gender.Other }, + }, } ); Assert.NotNull(res); @@ -365,8 +387,8 @@ public void CompilesEnum3() People = new List { new Person { Gender = Gender.Female }, - new Person { Gender = Gender.Other } - } + new Person { Gender = Gender.Other }, + }, } ); Assert.NotNull(res); @@ -408,14 +430,14 @@ public enum Gender { Female, Male, - Other + Other, } public enum Size { Small, Large, - Other + Other, } private class Entry @@ -433,19 +455,11 @@ public Entry(string message) // This would be your Entity/Object graph you use with EntityFramework private class TestSchema { - public string Hello - { - get { return "returned value"; } - } - public int Num - { - get { return 33; } - } + public string Hello => "returned value"; + public int Num => 33; + public int? NullableInt => 55; - public TestEntity SomeRelation - { - get { return new TestEntity("bob"); } - } + public TestEntity SomeRelation => new TestEntity("bob"); public IEnumerable People { get; set; } = new List(); } @@ -457,22 +471,10 @@ public TestEntity(string name) Relation = new Person(); } - public int Id - { - get { return 100; } - } - public int Field1 - { - get { return 2; } - } - public uint UnisgnedInt - { - get { return 2; } - } - public int? NullableInt - { - get { return 8; } - } + public int Id => 100; + public int Field1 => 2; + public uint UnisgnedInt => 2; + public int? NullableInt => 8; public string Name { get; set; } public Person Relation { get; set; } } @@ -485,10 +487,7 @@ public Person() } public int Id { get; set; } - public string Name - { - get { return "Luke"; } - } + public string Name => "Luke"; public Gender Gender { get; set; } } } diff --git a/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerWithMappedSchemaTests.cs b/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerWithMappedSchemaTests.cs index 86141384..183a1336 100644 --- a/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerWithMappedSchemaTests.cs +++ b/src/tests/EntityGraphQL.Tests/EntityQuery/EntityQueryCompilerWithMappedSchemaTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using EntityGraphQL.Schema; using EntityGraphQL.Tests; @@ -71,16 +70,9 @@ private TestDataContext GetDataContext() { var db = new TestDataContext { - Projects = new List - { - new Project { Id = 90, Type = 2 }, - new Project { Id = 91, Type = 1 } - }, - People = new List - { - new Person { Id = 4, Guid = new Guid("6492f5fe-0869-4279-88df-7f82f8e87a67") } - }, - Locations = new List { new Location { Id = 10 } } + Projects = [new Project { Id = 90, Type = 2 }, new Project { Id = 91, Type = 1 }], + People = [new Person { Id = 4, Guid = new Guid("6492f5fe-0869-4279-88df-7f82f8e87a67") }], + Locations = [new Location { Id = 10 }], }; return db; } diff --git a/src/tests/EntityGraphQL.Tests/EntityQuery/FilterExtensionTests.cs b/src/tests/EntityGraphQL.Tests/EntityQuery/FilterExtensionTests.cs index a8ec86ce..ef06dbb5 100644 --- a/src/tests/EntityGraphQL.Tests/EntityQuery/FilterExtensionTests.cs +++ b/src/tests/EntityGraphQL.Tests/EntityQuery/FilterExtensionTests.cs @@ -84,7 +84,7 @@ public void SupportEntityQueryArgument() @"query Query($filter: String!) { users(filter: $filter) { field2 } }", - Variables = new QueryVariables { { "filter", "field2 == \"2\"" } } + Variables = new QueryVariables { { "filter", "field2 == \"2\"" } }, }; var context = new TestDataContext().FillWithTestData(); context.Users.Add(new User { Field2 = "99" }); @@ -108,7 +108,7 @@ public void FilterExpressionWithNoValue() users(filter: $filter) { field2 } }", // do not pass any values. p.filter.HasValue will be false (and work) - Variables = new QueryVariables() + Variables = [], }; var context = new TestDataContext().FillWithTestData(); context.Users.Add(new User { Field2 = "99" }); @@ -134,7 +134,7 @@ public void FilterExpressionWithNoValueNoDocVar() users { field2 } }", // do not pass any values. p.filter.HasValue will be false (and work) - Variables = new QueryVariables() + Variables = [], }; var context = new TestDataContext().FillWithTestData(); context.Users.Add(new User { Field2 = "99" }); @@ -159,7 +159,7 @@ public void SupportUseFilter() @"query Query($filter: String!) { users(filter: $filter) { field2 } }", - Variables = new QueryVariables { { "filter", "field2 == \"2\"" } } + Variables = new QueryVariables { { "filter", "field2 == \"2\"" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -180,7 +180,7 @@ public void SupportUseFilterWithOrStatement() @"query Query($filter: String!) { users(filter: $filter) { field2 } }", - Variables = new QueryVariables { { "filter", "field2 == \"2\" or field2 == \"3\"" } } + Variables = new QueryVariables { { "filter", "field2 == \"2\" or field2 == \"3\"" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -201,7 +201,7 @@ public void SupportUseFilterWithAndStatement() @"query Query($filter: String!) { users(filter: $filter) { field2 } }", - Variables = new QueryVariables { { "filter", "field2 == \"2\" and field2 == \"2\"" } } + Variables = new QueryVariables { { "filter", "field2 == \"2\" and field2 == \"2\"" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -222,7 +222,7 @@ public void SupportUseFilterWithnotEqualStatement() @"query Query($filter: String!) { users(filter: $filter) { field2 } }", - Variables = new QueryVariables { { "filter", "field2 != \"3\"" } } + Variables = new QueryVariables { { "filter", "field2 != \"3\"" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -232,6 +232,54 @@ public void SupportUseFilterWithnotEqualStatement() Assert.Equal("2", user.field2); } + [Fact] + public void SupportUseFilterWithIsAnyStatementInts() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.Query().ReplaceField("users", new { filter = EntityQuery() }, (ctx, p) => ctx.Users.WhereWhen(p.filter, p.filter.HasValue), "Return filtered users"); + var gql = new QueryRequest + { + Query = + @"query { + users(filter: ""id.isAny([1,5])"") { id field2 } +}", + }; + var context = new TestDataContext().FillWithTestData(); + context.Users.Add(new User { Id = 1 }); + context.Users.Add(new User { Id = 5 }); + context.Users.Add(new User { Id = 10 }); + var tree = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(tree.Errors); + dynamic users = ((IDictionary)tree.Data!)["users"]; + Assert.Equal(2, Enumerable.Count(users)); + var user = Enumerable.First(users); + Assert.Equal(1, user.id); + } + + [Fact] + public void SupportUseFilterWithIsAnyStatementNullableInts() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.Query().ReplaceField("users", (ctx) => ctx.Users, "Return filtered users").UseFilter(); + var gql = new QueryRequest + { + Query = + @"query { + users(filter: ""relationId.isAny([1,5])"") { id field2 relationId } +}", + }; + var context = new TestDataContext().FillWithTestData(); + context.Users.Add(new User { Id = 1 }); + context.Users.Add(new User { Id = 5, RelationId = 5 }); + context.Users.Add(new User { Id = 10 }); + var tree = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(tree.Errors); + dynamic users = ((IDictionary)tree.Data!)["users"]; + Assert.Equal(1, Enumerable.Count(users)); + var user = Enumerable.First(users); + Assert.Equal(5, user.id); + } + [Fact] public void SupportUseFilterOnNonRoot() { @@ -245,7 +293,7 @@ public void SupportUseFilterOnNonRoot() tasks(filter: $filter) { id } } }", - Variables = new QueryVariables { { "filter", "(id == 2) || (id == 4)" } } + Variables = new QueryVariables { { "filter", "(id == 2) || (id == 4)" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -270,7 +318,7 @@ public void SupportUseFilterOnNonRootOrTest() tasks(filter: $filter) { id name } } }", - Variables = new QueryVariables { { "filter", "(name == \"task 2\") || (name == \"task not there\")" } } + Variables = new QueryVariables { { "filter", "(name == \"task 2\") || (name == \"task not there\")" } }, }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -292,7 +340,7 @@ public void TestAttribute() @"query Query($filter: String!) { people(filter: $filter) { name } }", - Variables = new QueryVariables { { "filter", "name == \"Luke\"" } } + Variables = new QueryVariables { { "filter", "name == \"Luke\"" } }, }; var tree = schema.ExecuteRequestWithContext(gql, (TestDataContext2)new TestDataContext2().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -314,7 +362,11 @@ public void TestTrueConstant() @"query Query($filter: String!) { tasks(filter: $filter) { name } }", - Variables = new QueryVariables { { "filter", "isActive == true)" } } // extra ) bracket + Variables = new QueryVariables + { + { "filter", "isActive == true)" }, + } // extra ) bracket + , }; var tree = schema.ExecuteRequestWithContext(gql, new TestDataContext2().FillWithTestData(), null, null); Assert.Null(tree.Errors); @@ -330,7 +382,7 @@ public void SupportUseFilterAnyMethod() @"query Query($filter: String!) { people(filter: $filter) { id name } }", - Variables = new QueryVariables { { "filter", "projects.any(name == \"Home\")" } } + Variables = new QueryVariables { { "filter", "projects.any(name == \"Home\")" } }, }; var data = new TestDataContext2().FillWithTestData(); data.People.First().Name = "Lisa"; @@ -353,7 +405,7 @@ public void SupportUseFilterCountMethod() @"query Query($filter: String!) { people(filter: $filter) { id name } }", - Variables = new QueryVariables { { "filter", "projects.count() == 2" } } + Variables = new QueryVariables { { "filter", "projects.count() == 2" } }, }; var data = new TestDataContext2().FillWithTestData(); data.People.First().Name = "Lisa"; @@ -376,7 +428,7 @@ public void SupportUseFilterCountMethodWithFilter() @"query Query($filter: String!) { people(filter: $filter) { id name } }", - Variables = new QueryVariables { { "filter", "projects.count(name.startsWith(\"Plan\")) > 0" } } + Variables = new QueryVariables { { "filter", "projects.count(name.startsWith(\"Plan\")) > 0" } }, }; var data = new TestDataContext2().FillWithTestData(); data.People.Add(DataFiller.MakePerson(33, null, null)); @@ -400,7 +452,7 @@ public void SupportUseFilterDecimalWithInt() @"query Query($filter: String!) { people(filter: $filter) { id name height } }", - Variables = new QueryVariables { { "filter", "height > 170" } } + Variables = new QueryVariables { { "filter", "height > 170" } }, }; var data = new TestDataContext2(); var person1 = DataFiller.MakePerson(33, null, null); @@ -428,7 +480,7 @@ public void SupportUseFilterUnicode() @"query Query($filter: String!) { people(filter: $filter) { id name height } }", - Variables = new QueryVariables { { "filter", "name == \"Кирил\"" } } + Variables = new QueryVariables { { "filter", "name == \"Кирил\"" } }, }; var data = new TestDataContext2(); var person1 = DataFiller.MakePerson(33, null, null); @@ -464,15 +516,15 @@ public void TestFilterMethodsChained() tasks { id } } }", - Variables = new QueryVariables { { "filter", "tasks.orderByDesc(hoursEstimated).first().hoursEstimated > 1" } } + Variables = new QueryVariables { { "filter", "tasks.orderByDesc(hoursEstimated).first().hoursEstimated > 1" } }, }; var data = new TestDataContext { Projects = [ new Project { Name = "Project 1", Tasks = [new Task { Id = 1, HoursEstimated = 0 }, new Task { Id = 2, HoursEstimated = 1 }] }, - new Project { Name = "Project 2", Tasks = [new Task { Id = 1, HoursEstimated = 0 }, new Task { Id = 2, HoursEstimated = 2 }] } - ] + new Project { Name = "Project 2", Tasks = [new Task { Id = 1, HoursEstimated = 0 }, new Task { Id = 2, HoursEstimated = 2 }] }, + ], }; var tree = schema.ExecuteRequestWithContext(gql, data, null, null); @@ -496,7 +548,7 @@ public void SupportUseFilterEnumWithString() @"query Query($filter: String!) { people(filter: $filter) { id name gender } }", - Variables = new QueryVariables { { "filter", "gender == 'Male'" } } + Variables = new QueryVariables { { "filter", "gender == 'Male'" } }, }; var data = new TestDataContext2(); var person1 = DataFiller.MakePerson(33, null, null); @@ -524,7 +576,7 @@ public void SupportNullableDateTime() @"query Query($filter: String!) { people(filter: $filter) { id name gender } }", - Variables = new QueryVariables { { "filter", "birthday > \"2024-09-08T07:00:00.000Z\"" } } + Variables = new QueryVariables { { "filter", "birthday > \"2024-09-08T07:00:00.000Z\"" } }, }; var data = new TestDataContext2(); var person1 = DataFiller.MakePerson(33, null, null); @@ -545,7 +597,7 @@ public void SupportNullableDateTime() private class TestDataContext2 : TestDataContext { [UseFilter] - public override List People { get; set; } = new List(); + public override List People { get; set; } = []; [UseFilter] public override IEnumerable Tasks { get; set; } = new List(); diff --git a/src/tests/EntityGraphQL.Tests/ErrorTests.cs b/src/tests/EntityGraphQL.Tests/ErrorTests.cs index fd0ad1b1..c8036e32 100644 --- a/src/tests/EntityGraphQL.Tests/ErrorTests.cs +++ b/src/tests/EntityGraphQL.Tests/ErrorTests.cs @@ -2,339 +2,338 @@ using EntityGraphQL.Schema; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class ErrorTests { - public class ErrorTests + [Fact] + public void MutationReportsError() { - [Fact] - public void MutationReportsError() + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson($name: String) { + Query = + @"mutation AddPerson($name: String) { addPersonError(name: $name) }", - Variables = new QueryVariables { { "name", "Bill" } } - }; - - var testSchema = new TestDataContext(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - // error from execution that prevented a valid response, the data entry in the response should be null - Assert.Null(results.Data); - Assert.Equal("Field 'addPersonError' - Name can not be null (Parameter 'name')", results.Errors[0].Message); - } - - [Fact] - public void QueryReportsError() + Variables = new QueryVariables { { "name", "Bill" } }, + }; + + var testSchema = new TestDataContext(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + // error from execution that prevented a valid response, the data entry in the response should be null + Assert.Null(results.Data); + Assert.Equal("Field 'addPersonError' - Name can not be null (Parameter 'name')", results.Errors[0].Message); + } + + [Fact] + public void QueryReportsError() + { + var schemaProvider = SchemaBuilder.FromObject(); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - Field failed to execute", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - Field failed to execute", results.Errors[0].Message); + } - [Fact] - public void TestErrorFieldNotIncludedInResponseWhenNoErrors() + [Fact] + public void TestErrorFieldNotIncludedInResponseWhenNoErrors() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ locations { id } - }" - }; - - var testSchema = new TestDataContext(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.False(results.HasErrors()); - var result = System.Text.Json.JsonSerializer.Serialize(results); - Assert.DoesNotContain("errors", result); - Assert.Contains("data", result); - } - - [Fact] - public void TestExtensionException() + }", + }; + + var testSchema = new TestDataContext(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.False(results.HasErrors()); + var result = System.Text.Json.JsonSerializer.Serialize(results); + Assert.DoesNotContain("errors", result); + Assert.Contains("data", result); + } + + [Fact] + public void TestExtensionException() + { + var schemaProvider = SchemaBuilder.FromObject(); + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error } - }" - }; - - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.True(results.HasErrors()); - Assert.NotNull(results.Errors); - var error = results.Errors[0]; - Assert.NotNull(error.Extensions); - Assert.Equal(1, error.Extensions["code"]); - var result = System.Text.Json.JsonSerializer.Serialize(results); - Assert.Contains("errors", result); - Assert.DoesNotContain("data", result); - } - - [Fact] - public void MutationReportsError_UnexposedException() + }", + }; + + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.True(results.HasErrors()); + Assert.NotNull(results.Errors); + var error = results.Errors[0]; + Assert.NotNull(error.Extensions); + Assert.Equal(1, error.Extensions["code"]); + var result = System.Text.Json.JsonSerializer.Serialize(results); + Assert.Contains("errors", result); + Assert.DoesNotContain("data", result); + } + + [Fact] + public void MutationReportsError_UnexposedException() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson($name: String) { + Query = + @"mutation AddPerson($name: String) { addPersonErrorUnexposedException(name: $name) } ", - Variables = new QueryVariables { { "name", "Bill" } } - }; - - var testSchema = new TestDataContext(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - // error from execution that prevented a valid response, the data entry in the response should be null - Assert.Null(results.Data); - Assert.Equal("Field 'addPersonErrorUnexposedException' - Error occurred", results.Errors[0].Message); - } - - [Fact] - public void MutationReportsError_UnexposedException_Development() + Variables = new QueryVariables { { "name", "Bill" } }, + }; + + var testSchema = new TestDataContext(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + // error from execution that prevented a valid response, the data entry in the response should be null + Assert.Null(results.Data); + Assert.Equal("Field 'addPersonErrorUnexposedException' - Error occurred", results.Errors[0].Message); + } + + [Fact] + public void MutationReportsError_UnexposedException_Development() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(new SchemaBuilderOptions() { AutoCreateInputTypes = true }); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson($name: String) { + Query = + @"mutation AddPerson($name: String) { addPersonErrorUnexposedException(name: $name) } ", - Variables = new QueryVariables { { "name", "Bill" } } - }; - - var testSchema = new TestDataContext(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - // error from execution that prevented a valid response, the data entry in the response should be null - Assert.Null(results.Data); - Assert.Equal("Field 'addPersonErrorUnexposedException' - You should not see this message outside of Development", results.Errors[0].Message); - } - - [Fact] - public void QueryReportsError_UnexposedException() + Variables = new QueryVariables { { "name", "Bill" } }, + }; + + var testSchema = new TestDataContext(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + // error from execution that prevented a valid response, the data entry in the response should be null + Assert.Null(results.Data); + Assert.Equal("Field 'addPersonErrorUnexposedException' - You should not see this message outside of Development", results.Errors[0].Message); + } + + [Fact] + public void QueryReportsError_UnexposedException() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_Development() + [Fact] + public void QueryReportsError_UnexposedException_Development() + { + var schemaProvider = SchemaBuilder.FromObject(); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_WithWhitelist() + [Fact] + public void QueryReportsError_UnexposedException_WithWhitelist() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception))); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception))); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_WithWhitelist_Development() + [Fact] + public void QueryReportsError_UnexposedException_WithWhitelist_Development() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception))); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception))); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_Exact_WithWhitelist() + [Fact] + public void QueryReportsError_UnexposedException_Exact_WithWhitelist() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(ArgumentException), true)); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(ArgumentException), true)); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_WithWhitelist_Exact_Development() + [Fact] + public void QueryReportsError_UnexposedException_WithWhitelist_Exact_Development() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(ArgumentException), true)); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(ArgumentException), true)); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_Exact_Mismatch_WithWhitelist() + [Fact] + public void QueryReportsError_UnexposedException_Exact_Mismatch_WithWhitelist() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception), true)); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception), true)); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_UnexposedException_WithWhitelist_Exact_Mismatch_Development() + [Fact] + public void QueryReportsError_UnexposedException_WithWhitelist_Exact_Mismatch_Development() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception), true)); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AllowedExceptions.Add(new AllowedException(typeof(Exception), true)); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_UnexposedArgumentException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - You should not see this message outside of Development", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_DistinctErrors() + [Fact] + public void QueryReportsError_DistinctErrors() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_AggregateException } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Single(results.Errors); - Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Single(results.Errors); + Assert.Equal("Field 'people' - Error occurred", results.Errors[0].Message); + } - [Fact] - public void QueryReportsError_AllowedExceptionAttribute() + [Fact] + public void QueryReportsError_AllowedExceptionAttribute() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderSchemaOptions { IsDevelopment = false }); - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { error_Allowed } }", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(results.Errors); - Assert.Equal("Field 'people' - This error is allowed", results.Errors[0].Message); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(results.Errors); + Assert.Equal("Field 'people' - This error is allowed", results.Errors[0].Message); } } diff --git a/src/tests/EntityGraphQL.Tests/ExecutionOptionsTests.cs b/src/tests/EntityGraphQL.Tests/ExecutionOptionsTests.cs index df8d947a..a8aa5075 100644 --- a/src/tests/EntityGraphQL.Tests/ExecutionOptionsTests.cs +++ b/src/tests/EntityGraphQL.Tests/ExecutionOptionsTests.cs @@ -68,7 +68,7 @@ public void TestBeforeExpressionBuild(string query, string fieldName, int expect var data = new TestDataContext(); FillProjectData(data); - var gql = new QueryRequest { Query = query, }; + var gql = new QueryRequest { Query = query }; var calledInExp = 0; var beforeExpressionBuildCalled = 0; @@ -85,7 +85,7 @@ public void TestBeforeExpressionBuild(string query, string fieldName, int expect beforeExpressionBuildCalled++; Action onCalled = () => calledInExp++; return Expression.Call(typeof(TestTagWith), nameof(TestTagWith.TagWith), [e.Type], e, Expression.Constant(onCalled)); - } + }, } ); Assert.Null(result.Errors); @@ -124,7 +124,7 @@ public void TestBeforeExpressionBuildOffsetPaging() => AssertExpression.Any() ) ) - ) + ), ] ) ), @@ -169,7 +169,7 @@ public void TestBeforeExpressionBuildConnectionPaging() => AssertExpression.Any() ) ) - ) + ), ] ) ), @@ -185,7 +185,7 @@ private void TestBeforeExpressionBuildExpression(string query, AssertExpression var data = new TestDataContext(); FillProjectData(data); - var gql = new QueryRequest { Query = query, }; + var gql = new QueryRequest { Query = query }; var calledInExp = 0; var beforeExpressionBuildCalled = 0; @@ -208,7 +208,7 @@ private void TestBeforeExpressionBuildExpression(string query, AssertExpression { AssertExpression.Matches(expectedExpression, e); return e; - } + }, } ); Assert.Null(result.Errors); @@ -221,7 +221,7 @@ public void TestBeforeExpressionBuildInvalid() var data = new TestDataContext(); FillProjectData(data); - var gql = new QueryRequest { Query = @"{ projects { name tasks { name id } } }", }; + var gql = new QueryRequest { Query = @"{ projects { name tasks { name id } } }" }; var result = schema.ExecuteRequestWithContext( gql, @@ -234,7 +234,7 @@ public void TestBeforeExpressionBuildInvalid() { // we changed the return type return Expression.Constant(7); - } + }, } ); Assert.NotNull(result.Errors); @@ -256,8 +256,8 @@ private static void FillProjectData(TestDataContext data) new Task { Id = 2, Name = "Task 3" }, new Task { Id = 3, Name = "Task 4" }, new Task { Id = 4, Name = "Task 5" }, - ] - } + ], + }, ]; } } diff --git a/src/tests/EntityGraphQL.Tests/FieldExtensionTests.cs b/src/tests/EntityGraphQL.Tests/FieldExtensionTests.cs index 976ad900..93284c8d 100644 --- a/src/tests/EntityGraphQL.Tests/FieldExtensionTests.cs +++ b/src/tests/EntityGraphQL.Tests/FieldExtensionTests.cs @@ -250,7 +250,7 @@ name id private static void FillData(TestDataContext data) { - data.People = new() { MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Frank"), MakePerson("Jill", "Frank"), MakePerson("Jack", "Snider"), }; + data.People = [MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Frank"), MakePerson("Jill", "Frank"), MakePerson("Jack", "Snider")]; } private static Person MakePerson(string fname, string lname) @@ -259,7 +259,7 @@ private static Person MakePerson(string fname, string lname) { Id = peopleCnt++, Name = fname, - LastName = lname + LastName = lname, }; } } diff --git a/src/tests/EntityGraphQL.Tests/IntrospectionTests/IntrospectionTests.cs b/src/tests/EntityGraphQL.Tests/IntrospectionTests/IntrospectionTests.cs index 38d5c0fd..d01573bd 100644 --- a/src/tests/EntityGraphQL.Tests/IntrospectionTests/IntrospectionTests.cs +++ b/src/tests/EntityGraphQL.Tests/IntrospectionTests/IntrospectionTests.cs @@ -113,10 +113,10 @@ fragment TypeRef on __Type { } } } - }" + }", }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); Assert.Null(res.Errors); @@ -149,10 +149,10 @@ fragment InputValue on __InputValue { fragment TypeRef on __Type { name - }" + }", }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); Assert.Null(res.Errors); @@ -180,10 +180,10 @@ query IntrospectionQuery { deprecationReason } } - }" + }", }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); Assert.Null(res.Errors); @@ -212,10 +212,10 @@ query IntrospectionQuery { deprecationReason } } - }" + }", }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); Assert.Null(res.Errors); @@ -254,7 +254,7 @@ public void TestScalarDescription() name description } - }" + }", }; var context = new TestDataContext(); diff --git a/src/tests/EntityGraphQL.Tests/IntrospectionTests/MetaDataTests.cs b/src/tests/EntityGraphQL.Tests/IntrospectionTests/MetaDataTests.cs index e7c292fc..e08e30ae 100644 --- a/src/tests/EntityGraphQL.Tests/IntrospectionTests/MetaDataTests.cs +++ b/src/tests/EntityGraphQL.Tests/IntrospectionTests/MetaDataTests.cs @@ -49,7 +49,7 @@ public void TestServiceFieldTypeWithTypename() __typename } } - }" + }", }; var context = new TestDataContext().FillWithTestData(); @@ -90,7 +90,7 @@ public void TestTypenameOnAllTypes() project(id: 1) { __typename } - }" + }", }; var context = new TestDataContext().FillWithTestData(); diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/IMutations.cs b/src/tests/EntityGraphQL.Tests/MutationTests/IMutations.cs index 38bd5a72..7adf6af0 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/IMutations.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/IMutations.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace EntityGraphQL.Tests; -namespace EntityGraphQL.Tests -{ - public class IMutations { } -} +public class IMutations { } diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/MutationArgsTests.cs b/src/tests/EntityGraphQL.Tests/MutationTests/MutationArgsTests.cs index 2f52e699..2239785a 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/MutationArgsTests.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/MutationArgsTests.cs @@ -3,193 +3,192 @@ using Xunit; using static EntityGraphQL.Tests.ServiceFieldTests; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class MutationArgsTests { - public class MutationArgsTests + [Fact] + public void SupportsGenericClassArg() { - [Fact] - public void SupportsGenericClassArg() + var schema = SchemaBuilder.FromObject(); + schema.AddInputType("HumanInput").AddAllFields(); + schema.Mutation().Add("addPerson", ([GraphQLArguments] Partial args) => 65); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("addPerson(others: HumanInput, name: String): Int!", sdl); + + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - schema.AddInputType("HumanInput").AddAllFields(); - schema.Mutation().Add("addPerson", ([GraphQLArguments] Partial args) => 65); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("addPerson(others: HumanInput, name: String): Int!", sdl); - - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson { + Query = + @"mutation AddPerson { addPerson(name: ""Herb"" others: { age: 43 }) }", - }; - var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); - Assert.Equal(65, res.Data!["addPerson"]!); - } - - [Fact] - public void SupportsGenericClassArgAsInputType() + }; + var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); + Assert.Equal(65, res.Data!["addPerson"]!); + } + + [Fact] + public void SupportsGenericClassArgAsInputType() + { + var schema = SchemaBuilder.FromObject(); + schema.Mutation().Add("addPerson", ([GraphQLInputType] Partial args) => 65, new SchemaBuilderOptions { AutoCreateInputTypes = true }); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("addPerson(args: PartialHuman!): Int!", sdl); + Assert.Contains("input PartialHuman {", sdl); + + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - schema.Mutation().Add("addPerson", ([GraphQLInputType] Partial args) => 65, new SchemaBuilderOptions { AutoCreateInputTypes = true, }); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("addPerson(args: PartialHuman!): Int!", sdl); - Assert.Contains("input PartialHuman {", sdl); - - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson { + Query = + @"mutation AddPerson { addPerson(args: { name: ""Herb"", others: { age: 43 } }) }", - }; - var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); - Assert.Equal(65, res.Data!["addPerson"]!); - } - - [Fact] - public void TestMethodWithScalarComplexAndServiceArgs() + }; + var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); + Assert.Equal(65, res.Data!["addPerson"]!); + } + + [Fact] + public void TestMethodWithScalarComplexAndServiceArgs() + { + var schema = SchemaBuilder.FromObject(); + schema + .Mutation() + .Add( + "addPerson", + (string token, [GraphQLArguments] InputArgs args, ConfigService service) => + { + Assert.Equal("123", token); + Assert.Equal("Herb", args.Name); + Assert.NotNull(service); + return args.Age; + }, + new SchemaBuilderOptions { AutoCreateInputTypes = true } + ); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("addPerson(token: String!, name: String!, age: Int!): Int!", sdl); + + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - schema - .Mutation() - .Add( - "addPerson", - (string token, [GraphQLArguments] InputArgs args, ConfigService service) => - { - Assert.Equal("123", token); - Assert.Equal("Herb", args.Name); - Assert.NotNull(service); - return args.Age; - }, - new SchemaBuilderOptions { AutoCreateInputTypes = true, } - ); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("addPerson(token: String!, name: String!, age: Int!): Int!", sdl); - - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson { + Query = + @"mutation AddPerson { addPerson( name: ""Herb"", age: 43, token: ""123"") }", - }; - var serviceCollection = new ServiceCollection(); - var service = new ConfigService(); - serviceCollection.AddSingleton(service); - - var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); - Assert.Null(res.Errors); - Assert.Equal(43, res.Data!["addPerson"]!); - } - - [Fact] - public void TestMethodWithScalarComplexInlineAndServiceArgs() + }; + var serviceCollection = new ServiceCollection(); + var service = new ConfigService(); + serviceCollection.AddSingleton(service); + + var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(43, res.Data!["addPerson"]!); + } + + [Fact] + public void TestMethodWithScalarComplexInlineAndServiceArgs() + { + var schema = SchemaBuilder.FromObject(); + // Note the Argument not Argument_s_ + schema + .Mutation() + .Add( + "addPerson", + (string token, [GraphQLInputType] InputArgs args, ConfigService service) => + { + Assert.Equal("123", token); + Assert.Equal("Herb", args.Name); + Assert.NotNull(service); + return args.Age; + }, + new SchemaBuilderOptions { AutoCreateInputTypes = true } + ); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("addPerson(token: String!, args: InputArgs!): Int!", sdl); + Assert.Contains("input InputArgs {", sdl); + + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - // Note the Argument not Argument_s_ - schema - .Mutation() - .Add( - "addPerson", - (string token, [GraphQLInputType] InputArgs args, ConfigService service) => - { - Assert.Equal("123", token); - Assert.Equal("Herb", args.Name); - Assert.NotNull(service); - return args.Age; - }, - new SchemaBuilderOptions { AutoCreateInputTypes = true, } - ); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("addPerson(token: String!, args: InputArgs!): Int!", sdl); - Assert.Contains("input InputArgs {", sdl); - - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson { + Query = + @"mutation AddPerson { addPerson( args: { name: ""Herb"", age: 43 }, token: ""123"") }", - }; - var serviceCollection = new ServiceCollection(); - var service = new ConfigService(); - serviceCollection.AddSingleton(service); - - var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); - Assert.Null(res.Errors); - Assert.Equal(43, res.Data!["addPerson"]!); - } - - [Fact] - public void TestMethodWithMultipleFlattenArguments() + }; + var serviceCollection = new ServiceCollection(); + var service = new ConfigService(); + serviceCollection.AddSingleton(service); + + var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(43, res.Data!["addPerson"]!); + } + + [Fact] + public void TestMethodWithMultipleFlattenArguments() + { + var schema = SchemaBuilder.FromObject(); + schema + .Mutation() + .Add( + "addPerson", + ([GraphQLArguments] InputArgs args, [GraphQLArguments] InputExtraArgs extra, ConfigService service) => + { + Assert.Equal("123", extra.Token); + Assert.Equal("Herb", args.Name); + Assert.NotNull(service); + return args.Age; + }, + new SchemaBuilderOptions { AutoCreateInputTypes = true } + ); + + var sdl = schema.ToGraphQLSchemaString(); + Assert.Contains("addPerson(name: String!, age: Int!, token: String): Int!", sdl); + + // Add a argument field with a require parameter + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - schema - .Mutation() - .Add( - "addPerson", - ([GraphQLArguments] InputArgs args, [GraphQLArguments] InputExtraArgs extra, ConfigService service) => - { - Assert.Equal("123", extra.Token); - Assert.Equal("Herb", args.Name); - Assert.NotNull(service); - return args.Age; - }, - new SchemaBuilderOptions { AutoCreateInputTypes = true, } - ); - - var sdl = schema.ToGraphQLSchemaString(); - Assert.Contains("addPerson(name: String!, age: Int!, token: String): Int!", sdl); - - // Add a argument field with a require parameter - var gql = new QueryRequest - { - Query = - @"mutation AddPerson { + Query = + @"mutation AddPerson { addPerson( name: ""Herb"", age: 43, token: ""123"") }", - }; - var serviceCollection = new ServiceCollection(); - var service = new ConfigService(); - serviceCollection.AddSingleton(service); - - var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); - Assert.Null(res.Errors); - Assert.Equal(43, res.Data!["addPerson"]!); - } + }; + var serviceCollection = new ServiceCollection(); + var service = new ConfigService(); + serviceCollection.AddSingleton(service); + + var res = schema.ExecuteRequestWithContext(gql, new TestDataContext(), serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(43, res.Data!["addPerson"]!); } +} - internal class InputArgs - { - [GraphQLNotNull] - public string Name { get; set; } = string.Empty; - public int Age { get; set; } - } +internal class InputArgs +{ + [GraphQLNotNull] + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} - internal class InputExtraArgs - { - public string? Token { get; set; } - } +internal class InputExtraArgs +{ + public string? Token { get; set; } +} - internal class Human - { - public int Age { get; set; } - } +internal class Human +{ + public int Age { get; set; } +} - internal class Partial - { - public T? Others { get; set; } - public string? Name { get; set; } - } +internal class Partial +{ + public T? Others { get; set; } + public string? Name { get; set; } } diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/MutationMethodParameterTests.cs b/src/tests/EntityGraphQL.Tests/MutationTests/MutationMethodParameterTests.cs index e3457398..ba97b850 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/MutationMethodParameterTests.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/MutationMethodParameterTests.cs @@ -27,8 +27,8 @@ public void TestSeparateArguments_PrimitivesOnly() { "name", "Frank" }, { "birthday", DateTime.Today }, { "weight", 45.5 }, - { "gender", Gender.Male } - } + { "gender", Gender.Male }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -52,8 +52,8 @@ public void TestSeparateArguments_PrimitivesOnly_WithInlineDefaults() { { "birthday", DateTime.Today }, { "weight", 45.5 }, - { "gender", Gender.Male } - } + { "gender", Gender.Male }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -77,8 +77,8 @@ public void TestSeparateArguments() { "name", "Frank" }, { "names", new[] { "Frank" } }, { "nameInput", null }, - { "gender", Gender.Female } - } + { "gender", Gender.Female }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -103,7 +103,7 @@ public void TestSingleArgument() "nameInput", new InputObject() { Name = "Frank" } }, - } + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -128,7 +128,7 @@ public void TestSingleArgument_DifferentVariableName() "differentName", new InputObject() { Name = "Frank" } }, - } + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -153,7 +153,7 @@ public void TestSingleArgument_AutoAddInputTypes() "nameInput", new InputObject() { Name = "Frank" } }, - } + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -187,8 +187,8 @@ public void TestSeparateArguments_AutoAddInputTypes() { "name", "Frank" }, { "names", new[] { "Frank" } }, { "nameInput", null }, - { "gender", Gender.Female } - } + { "gender", Gender.Female }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -206,7 +206,7 @@ public void TestChildArraysDontGetArguments() schemaProvider.AddMutationsFrom(new SchemaBuilderOptions { AutoCreateInputTypes = true }); - Assert.Empty(schemaProvider.GetSchemaType(nameof(ListOfObjectsWithIds), null).GetFields().Where(x => x.Arguments.Any())); + Assert.DoesNotContain(schemaProvider.GetSchemaType(nameof(ListOfObjectsWithIds), null).GetFields(), x => x.Arguments.Any()); } [Fact] @@ -227,7 +227,7 @@ public void TestSingleArgument_AutoAddInputTypes_NestedType() "nameInput", new NestedInputObject() { Name = "Frank" } }, - } + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -251,8 +251,8 @@ public void TestSeparateArguments_DefaultSchemaBuilder_AutoAddInputTypes() { "name", "Frank" }, { "names", new[] { "Frank" } }, { "nameInput", null }, - { "gender", Gender.Female } - } + { "gender", Gender.Female }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -276,8 +276,8 @@ public void TestAutoAddReturnTypeOption() { "name", "Frank" }, { "names", new[] { "Frank" } }, { "nameInput", null }, - { "gender", Gender.Female } - } + { "gender", Gender.Female }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/MutationTests.cs b/src/tests/EntityGraphQL.Tests/MutationTests/MutationTests.cs index 076e11cc..c502ae0c 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/MutationTests.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/MutationTests.cs @@ -22,7 +22,7 @@ public void MissingRequiredVar() @"mutation AddPerson($name: String!) { addPerson(name: $name) { id name } }", - Variables = new QueryVariables { { "na", "Frank" } } + Variables = new QueryVariables { { "na", "Frank" } }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.NotNull(res.Errors); @@ -45,7 +45,7 @@ mutation AddPerson($name: String) { id name } }", - Variables = new QueryVariables { } + Variables = [], }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(res.Errors); @@ -73,7 +73,7 @@ public void SupportsMutationArrayArg() id name lastName } }", - Variables = new QueryVariables { { "names", new[] { "Bill", "Frank" } } } + Variables = new QueryVariables { { "names", new[] { "Bill", "Frank" } } }, }; var testSchema = new TestDataContext(); var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); @@ -103,7 +103,7 @@ public void SupportsMutationExpressionWithTask() id name lastName } }", - Variables = new QueryVariables { { "names", new[] { "Bill", "Frank" } } } + Variables = new QueryVariables { { "names", new[] { "Bill", "Frank" } } }, }; var testSchema = new TestDataContext(); var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); @@ -153,7 +153,7 @@ id name lastName new QueryRequest { Query = query, - Variables = new QueryVariables { { "names", new[] { "Mary", "Joe" } } } + Variables = new QueryVariables { { "names", new[] { "Mary", "Joe" } } }, }, testSchema, null, @@ -187,7 +187,7 @@ id name lastName } }", // Object does not match the var definition in the AddPerson operation - Variables = new QueryVariables { { "names", new { name = "Lisa", lastName = "Simpson" } } } + Variables = new QueryVariables { { "names", new { name = "Lisa", lastName = "Simpson" } } }, }; var result = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.NotNull(result.Errors); @@ -211,7 +211,7 @@ id name lastName } }", // variable matches the var definition but does not match the field expected type - Variables = new QueryVariables { { "names", new[] { "Lisa", "Simpson" } } } + Variables = new QueryVariables { { "names", new[] { "Lisa", "Simpson" } } }, }; var result = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.NotNull(result.Errors); @@ -235,7 +235,7 @@ id name lastName } }", // object will come through as json in the request - Variables = new QueryVariables { { "names", new { name = "Lisa", lastName = "Simpson" } } } + Variables = new QueryVariables { { "names", new { name = "Lisa", lastName = "Simpson" } } }, }; var result = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -271,8 +271,8 @@ id name lastName { "names", new InputObject { Name = "Lisa", LastName = "Simpson" } - } - } + }, + }, }; var result = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -308,8 +308,8 @@ id name lastName { "names", new Dictionary { { "name", "Lisa" }, { "lastName", "Simpson" } } - } - } + }, + }, }; var result = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -338,7 +338,7 @@ public void SupportsSelectionFromConstant() id name projects { id } } }", - Variables = new QueryVariables { { "name", "Bill" } } + Variables = new QueryVariables { { "name", "Bill" } }, }; var testSchema = new TestDataContext(); var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); @@ -366,7 +366,7 @@ public void SupportsSelectionFromConstantList() id name projects { id } } }", - Variables = new QueryVariables { { "name", "Bill" } } + Variables = new QueryVariables { { "name", "Bill" } }, }; var testSchema = new TestDataContext(); var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); @@ -402,7 +402,7 @@ public void SupportsSelectionWithServiceFieldInFragment() fragment frag on Person { id age }", - Variables = new QueryVariables { { "name", "Bill" } } + Variables = new QueryVariables { { "name", "Bill" } }, }; var serviceCollection = new ServiceCollection(); var service = new AgeService(); @@ -438,7 +438,7 @@ public void MutationReturnsCollectionExpressionWithService() id age } }", - Variables = new QueryVariables { { "name", "Bill" } } + Variables = new QueryVariables { { "name", "Bill" } }, }; var serviceCollection = new ServiceCollection(); var service = new AgeService(); @@ -473,7 +473,7 @@ public void MutationReturnsCollectionConst() } } ", - Variables = new QueryVariables { { "name", "Bill" } } + Variables = new QueryVariables { { "name", "Bill" } }, }; var testSchema = new TestDataContext().FillWithTestData(); @@ -595,7 +595,7 @@ public void TestMethodDefaultValue() } } } - }" + }", }; results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); @@ -724,7 +724,7 @@ public void TestListArgInputTypeUsingVariables() @"mutation Mutate($var: [InputObject!]) { taskWithList(inputs: $var) }", - Variables = new QueryVariables { { "var", "[{name: \"Bill\"}, {name: \"Bob\"}]" } } + Variables = new QueryVariables { { "var", "[{name: \"Bill\"}, {name: \"Bob\"}]" } }, }; var testSchema = new TestDataContext(); @@ -982,7 +982,7 @@ public void TestListGuidTypeUsingVariables() @"mutation Mutate($ids: [ID]) { listOfGuidArgs(ids: $ids) }", - Variables = new QueryVariables { { "ids", new string[] { "cc3e20f9-9dbb-4ded-8072-6ab3cf0c94da" } } } + Variables = new QueryVariables { { "ids", new string[] { "cc3e20f9-9dbb-4ded-8072-6ab3cf0c94da" } } }, }; var testSchema = new TestDataContext(); @@ -1004,7 +1004,7 @@ public void TestListGuidTypeUsingVariablesRequired() @"mutation Mutate($ids: [ID!]!) { listOfGuidArgs(ids: $ids) }", - Variables = new QueryVariables { { "ids", new string[] { "cc3e20f9-9dbb-4ded-8072-6ab3cf0c94da" } } } + Variables = new QueryVariables { { "ids", new string[] { "cc3e20f9-9dbb-4ded-8072-6ab3cf0c94da" } } }, }; var testSchema = new TestDataContext(); @@ -1025,7 +1025,7 @@ public void TestDescriptionAttribute() @"mutation Mutate($x: Int!) { descriptionArgs(x: $x) }", - Variables = new QueryVariables { { "x", 3 } } + Variables = new QueryVariables { { "x", 3 } }, }; var testSchema = new TestDataContext(); @@ -1087,7 +1087,7 @@ public Person AddPerson(PeopleMutationsArgs args) { Name = string.IsNullOrEmpty(args.Name) ? "Default" : args.Name, Id = 555, - Projects = new List() + Projects = [], }; } } @@ -1138,7 +1138,7 @@ public void TestRightValueReturnedFromActivatorCreateMutationClass() Query = @"mutation getValue() { getValue() - }" + }", }; var testSchema = new TestDataContext(); diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/OneOfInputTypeTests.cs b/src/tests/EntityGraphQL.Tests/MutationTests/OneOfInputTypeTests.cs index a458b936..232838d5 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/OneOfInputTypeTests.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/OneOfInputTypeTests.cs @@ -2,175 +2,172 @@ using EntityGraphQL.Schema.Directives; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class OneOfInputTypeTests { - public class OneOfInputTypeTests + private class NotOneOfInputType { - private class NotOneOfInputType - { - public int One { get; set; } - public int Two { get; set; } - } + public int One { get; set; } + public int Two { get; set; } + } - [Fact] - public void TestNotOneOfAttribute() - { - var schemaProvider = SchemaBuilder.Create(); - schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); + [Fact] + public void TestNotOneOfAttribute() + { + var schemaProvider = SchemaBuilder.Create(); + schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); - var schema = schemaProvider.ToGraphQLSchemaString(); + var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.Contains("input InputObject {", schema); - Assert.Contains("one: Int!", schema); - Assert.Contains("two: Int!", schema); + Assert.Contains("input InputObject {", schema); + Assert.Contains("one: Int!", schema); + Assert.Contains("two: Int!", schema); - var gql = new QueryRequest - { - Query = - @" + var gql = new QueryRequest + { + Query = + @" query IntrospectionQuery { __type(name: ""InputObject"") { name kind oneField } - }" - }; + }", + }; - var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); + var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); - Assert.Equal("InputObject", ((dynamic)res.Data!["__type"]!).name); - Assert.Equal("INPUT_OBJECT", ((dynamic)res.Data!["__type"]!).kind); - Assert.False(((dynamic)res.Data!["__type"]!).oneField); - } + Assert.Equal("InputObject", ((dynamic)res.Data!["__type"]!).name); + Assert.Equal("INPUT_OBJECT", ((dynamic)res.Data!["__type"]!).kind); + Assert.False(((dynamic)res.Data!["__type"]!).oneField); + } - [GraphQLOneOf] - private class OneOfInputType - { - public int? One { get; set; } - public int? Two { get; set; } - } + [GraphQLOneOf] + private class OneOfInputType + { + public int? One { get; set; } + public int? Two { get; set; } + } - [Fact] - public void TestOneOfAttribute() - { - var schemaProvider = SchemaBuilder.Create(); - schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); + [Fact] + public void TestOneOfAttribute() + { + var schemaProvider = SchemaBuilder.Create(); + schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); - var schema = schemaProvider.ToGraphQLSchemaString(); + var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.Contains("input InputObject @oneOf {", schema); - Assert.Contains("one: Int", schema); - Assert.Contains("two: Int", schema); + Assert.Contains("input InputObject @oneOf {", schema); + Assert.Contains("one: Int", schema); + Assert.Contains("two: Int", schema); - var gql = new QueryRequest - { - Query = - @" + var gql = new QueryRequest + { + Query = + @" query IntrospectionQuery { __type(name: ""InputObject"") { name kind oneField } - }" - }; + }", + }; - var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); + var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); - Assert.Equal("InputObject", ((dynamic)res.Data!["__type"]!).name); - Assert.Equal("INPUT_OBJECT", ((dynamic)res.Data!["__type"]!).kind); - Assert.True(((dynamic)res.Data!["__type"]!).oneField); - } + Assert.Equal("InputObject", ((dynamic)res.Data!["__type"]!).name); + Assert.Equal("INPUT_OBJECT", ((dynamic)res.Data!["__type"]!).kind); + Assert.True(((dynamic)res.Data!["__type"]!).oneField); + } - [Fact] - public void TestOneOfAttributeCanNotBeUsedOnNonInputTypes() - { - var schemaProvider = SchemaBuilder.Create(); - var ex = Assert.Throws(() => schemaProvider.AddType("InputObject", "Using an object in the arguments")); - Assert.Equal($"OneOfInputType marked with OneOfDirective directive which is not valid on a {nameof(GqlTypes.QueryObject)}", ex.Message); - } + [Fact] + public void TestOneOfAttributeCanNotBeUsedOnNonInputTypes() + { + var schemaProvider = SchemaBuilder.Create(); + var ex = Assert.Throws(() => schemaProvider.AddType("InputObject", "Using an object in the arguments")); + Assert.Equal($"OneOfInputType marked with OneOfDirective directive which is not valid on a {nameof(GqlTypes.QueryObject)}", ex.Message); + } - [GraphQLOneOf] - private class InvalidOneOfInputType - { - public int One { get; set; } - public int? Two { get; set; } - } + [GraphQLOneOf] + private class InvalidOneOfInputType + { + public int One { get; set; } + public int? Two { get; set; } + } - [Fact] - public void TestOneOfAttributeChecksFieldsAreNullable() - { - var schemaProvider = SchemaBuilder.Create(); - var ex = Assert.Throws(() => schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields()); - Assert.Equal("InvalidOneOfInputType is a OneOf type but all its fields are not nullable. OneOf input types require all the field to be nullable.", ex.Message); - } + [Fact] + public void TestOneOfAttributeChecksFieldsAreNullable() + { + var schemaProvider = SchemaBuilder.Create(); + var ex = Assert.Throws(() => schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields()); + Assert.Equal("InvalidOneOfInputType is a OneOf type but all its fields are not nullable. OneOf input types require all the field to be nullable.", ex.Message); + } - [Fact] - public void TestOneOfErrorsIfMoreThanOneOfSupplied() + [Fact] + public void TestOneOfErrorsIfMoreThanOneOfSupplied() + { + var schemaProvider = SchemaBuilder.Create(); + schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); + schemaProvider.Mutation().Add("createOneOfInputType", (OneOfInputType input) => true); + + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.Create(); - schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); - schemaProvider.Mutation().Add("createOneOfInputType", (OneOfInputType input) => true); - - var gql = new QueryRequest - { - Query = - @" + Query = + @" mutation Test { createOneOfInputType(input: { one: 1, two: 2 }) - }" - }; + }", + }; + + var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.NotNull(res.Errors); + Assert.Equal("Exactly one field must be specified for argument of type InputObject.", res.Errors[0].Message); + } - var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.NotNull(res.Errors); - Assert.Equal("Exactly one field must be specified for argument of type InputObject.", res.Errors[0].Message); - } + [Fact] + public void TestOneOfWorksWithInlineParameters() + { + var schemaProvider = SchemaBuilder.Create(); + schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); + schemaProvider.Mutation().Add("createOneOfInputType", (OneOfInputType input) => input.One == 1 && input.Two == null); - [Fact] - public void TestOneOfWorksWithInlineParameters() + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.Create(); - schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); - schemaProvider.Mutation().Add("createOneOfInputType", (OneOfInputType input) => input.One == 1 && input.Two == null); - - var gql = new QueryRequest - { - Query = - @" + Query = + @" mutation Test { createOneOfInputType(input: { one: 1 }) - }" - }; + }", + }; - var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); - Assert.True((bool)res.Data!["createOneOfInputType"]!); - } + var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); + Assert.True((bool)res.Data!["createOneOfInputType"]!); + } + + [Fact] + public void TestOneOfWorksWithRequired() + { + var schemaProvider = SchemaBuilder.Create(); + schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); + schemaProvider.Query().AddField("createOneOfInputType", new { input = ArgumentHelper.Required() }, (ctx, args) => args.input.Value!.One ?? args.input.Value.Two, "Descrption"); - [Fact] - public void TestOneOfWorksWithRequired() + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.Create(); - schemaProvider.AddInputType("InputObject", "Using an object in the arguments").AddAllFields(); - schemaProvider - .Query() - .AddField("createOneOfInputType", new { input = ArgumentHelper.Required() }, (ctx, args) => args.input.Value!.One ?? args.input.Value.Two, "Descrption"); - - var gql = new QueryRequest - { - Query = - @" + Query = + @" query Test { createOneOfInputType(input: { one: 1 }) - }" - }; + }", + }; - var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); - Assert.Null(res.Errors); - Assert.Equal(1, (int)res.Data!["createOneOfInputType"]!); - } + var res = schemaProvider.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); + Assert.Null(res.Errors); + Assert.Equal(1, (int)res.Data!["createOneOfInputType"]!); } } diff --git a/src/tests/EntityGraphQL.Tests/MutationTests/PeopleMutations.cs b/src/tests/EntityGraphQL.Tests/MutationTests/PeopleMutations.cs index 966de4a7..854f7a4f 100644 --- a/src/tests/EntityGraphQL.Tests/MutationTests/PeopleMutations.cs +++ b/src/tests/EntityGraphQL.Tests/MutationTests/PeopleMutations.cs @@ -7,425 +7,424 @@ using System.Threading.Tasks; using EntityGraphQL.Schema; -namespace EntityGraphQL.Tests -{ - public class PeopleMutations : IMutations - { - private static int idCount = 0; - - [GraphQLMutation] - public Person AddPerson(PeopleMutationsArgs args) - { - return new Person - { - Name = string.IsNullOrEmpty(args.Name) ? "Default" : args.Name, - Id = 555, - Projects = new List() - }; - } - - [GraphQLMutation] - public int DefaultValueTest(int valueWithDefault = 8) - { - return valueWithDefault; - } +namespace EntityGraphQL.Tests; - [GraphQLMutation] - public Person AddPersonSeparateArguments(string? name, List? names, InputObject? nameInput, Gender? gender) - { - return new Person - { - Name = string.IsNullOrEmpty(name) ? "Default" : name, - Id = 555, - Projects = new List() - }; - } - - [GraphQLMutation] - public Person AddPersonPrimitive(int id, string name, DateTime birthday, decimal weight, Gender? gender) - { - return new Person - { - Name = string.IsNullOrEmpty(name) ? "Default" : name, - Id = 555, - Projects = new List() - }; - } - - [GraphQLMutation] - public Person AddPersonSingleArgument([GraphQLInputType] InputObject nameInput) - { - return new Person - { - Name = string.IsNullOrEmpty(nameInput.Name) ? "Default" : nameInput.Name, - Id = 555, - Projects = new List() - }; - } - - [GraphQLMutation] - public Person? AddInputWithChildWithId(ListOfObjectsWithIds nameInput) - { - return null; - } +public class PeopleMutations : IMutations +{ + private static int idCount = 0; -#nullable enable - [GraphQLMutation] - public Person AddPersonNullableNestedType([GraphQLInputType] NestedInputObject required, [GraphQLInputType] NestedInputObject? optional) + [GraphQLMutation] + public Person AddPerson(PeopleMutationsArgs args) + { + return new Person { - return new Person - { - Name = string.IsNullOrEmpty(required.Name) ? "Default" : required.Name, - Id = 555, - Projects = new List() - }; - } - -#nullable restore + Name = string.IsNullOrEmpty(args.Name) ? "Default" : args.Name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public Person AddPersonSingleArgumentNestedType(NestedInputObject nameInput) - { - return new Person - { - Name = string.IsNullOrEmpty(nameInput.Name) ? "Default" : nameInput.Name, - Id = 555, - Projects = new List() - }; - } - - [GraphQLMutation] - public Expression> AddPersonNames(TestDataContext db, PeopleMutationsArgs args) - { - var id = 11; - var newPerson = new Person - { - Id = id, - Name = args.Names![0], - LastName = args.Names[1] - }; - db.People.Add(newPerson); - return ctx => ctx.People.First(p => p.Id == id); - } - - [GraphQLMutation] - public async Task>> AddPersonNamesAsync(TestDataContext db, PeopleMutationsArgs args) - { - var id = 11; - var newPerson = new Person - { - Id = id, - Name = args.Names![0], - LastName = args.Names[1] - }; - db.People.Add(newPerson); - await System.Threading.Tasks.Task.Delay(1); - return ctx => ctx.People.First(p => p.Id == id); - } - - [GraphQLMutation] - public Expression> AddPersonNamesExpression(TestDataContext db, PeopleMutationsArgs args) - { - var newPerson = new Person - { - Id = idCount++, - Name = args.Names![0], - LastName = args.Names[1] - }; - db.People.Add(newPerson); - return ctx => ctx.People.First(p => p.Id == newPerson.Id); - } - - [GraphQLMutation] - public Person AddPersonInput(PeopleMutationsArgs args) - { - return new Person { Name = args.NameInput!.Name!, LastName = args.NameInput.LastName! }; - } + [GraphQLMutation] + public int DefaultValueTest(int valueWithDefault = 8) + { + return valueWithDefault; + } - [GraphQLMutation] - public float AddFloat(FloatInput args) + [GraphQLMutation] + public Person AddPersonSeparateArguments(string? name, List? names, InputObject? nameInput, Gender? gender) + { + return new Person { - return args.Float; - } + Name = string.IsNullOrEmpty(name) ? "Default" : name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public double AddDouble(DoubleInput args) + [GraphQLMutation] + public Person AddPersonPrimitive(int id, string name, DateTime birthday, decimal weight, Gender? gender) + { + return new Person { - return args.Double; - } + Name = string.IsNullOrEmpty(name) ? "Default" : name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public decimal AddDecimal(DecimalInput args) + [GraphQLMutation] + public Person AddPersonSingleArgument([GraphQLInputType] InputObject nameInput) + { + return new Person { - return args.Decimal; - } + Name = string.IsNullOrEmpty(nameInput.Name) ? "Default" : nameInput.Name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public Expression> AddPersonAdv(PeopleMutationsArgs args) - { - // test returning a constant in the expression which allows GraphQL selection over the schema (assuming the constant is a type in the schema) - // Ie. in the mutation query you can select any valid fields in the schema from Person - var person = new Person - { - Name = args.Name!, - Tasks = new List { new Task { Name = "A" } }, - Projects = new List { new Project { Id = 123 } } - }; - return ctx => person; - } - - [GraphQLMutation] - public Expression>> AddPersonAdvList(PeopleMutationsArgs args) - { - // test returning a constant in the expression which allows GraphQL selection over the schema (assuming the constant is a type in the schema) - // Ie. in the mutation query you can select any valid fields in the schema from Person - var person = new Person - { - Name = args.Name!, - Tasks = new List { new Task { Name = "A" } }, - Projects = new List { new Project { Id = 123 } } - }; - return ctx => new List { person }; - } - - [GraphQLMutation] - public Expression>> AddPersonReturnAll(TestDataContext db, PeopleMutationsArgs args) - { - db.People.Add(new Person { Id = 11, Name = args.Name! }); - return ctx => ctx.People; - } + [GraphQLMutation] + public Person? AddInputWithChildWithId(ListOfObjectsWithIds nameInput) + { + return null; + } - [GraphQLMutation] - public IEnumerable AddPersonReturnAllConst(TestDataContext db, PeopleMutationsArgs args) +#nullable enable + [GraphQLMutation] + public Person AddPersonNullableNestedType([GraphQLInputType] NestedInputObject required, [GraphQLInputType] NestedInputObject? optional) + { + return new Person { - db.People.Add(new Person { Id = 11, Name = args.Name! }); - return db.People.ToList(); - } + Name = string.IsNullOrEmpty(required.Name) ? "Default" : required.Name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public int AddPersonError(PeopleMutationsArgs args) - { - throw new EntityGraphQLArgumentException("name", "Name can not be null"); - } +#nullable restore - [GraphQLMutation] - public int AddPersonErrorUnexposedException(PeopleMutationsArgs args) + [GraphQLMutation] + public Person AddPersonSingleArgumentNestedType(NestedInputObject nameInput) + { + return new Person { - throw new Exception("You should not see this message outside of Development"); - } + Name = string.IsNullOrEmpty(nameInput.Name) ? "Default" : nameInput.Name, + Id = 555, + Projects = [], + }; + } - [GraphQLMutation] - public async Task DoGreatThing() + [GraphQLMutation] + public Expression> AddPersonNames(TestDataContext db, PeopleMutationsArgs args) + { + var id = 11; + var newPerson = new Person { - return await Task.Run(() => - { - return true; - }); - } - - [GraphQLMutation] - public Task DoGreatThingWithoutAsyncKeyword() => DoGreatThing(); + Id = id, + Name = args.Names![0], + LastName = args.Names[1], + }; + db.People.Add(newPerson); + return ctx => ctx.People.First(p => p.Id == id); + } - [GraphQLMutation] - public static async Task DoGreatThingStaticly() - { - return await Task.Run(() => - { - return true; - }); - } - - [GraphQLMutation] - public async Task NeedsGuid(GuidArgs args) - { - return await Task.Run(() => - { - return true; - }); - } - - [GraphQLMutation] - public async Task NeedsGuidNonNull(GuidNonNullArgs args) - { - return await Task.Run(() => - { - return true; - }); - } - - [GraphQLMutation] - public bool TaskWithList(ListArgs args) + [GraphQLMutation] + public async Task>> AddPersonNamesAsync(TestDataContext db, PeopleMutationsArgs args) + { + var id = 11; + var newPerson = new Person { - if (args?.Inputs == null) - throw new Exception("Inputs can not be null"); - return true; - } - - [GraphQLMutation] - public bool TaskWithListSeparateArg(List inputs) => true; + Id = id, + Name = args.Names![0], + LastName = args.Names[1], + }; + db.People.Add(newPerson); + await System.Threading.Tasks.Task.Delay(1); + return ctx => ctx.People.First(p => p.Id == id); + } - [GraphQLMutation] - public bool TaskWithListInt(ListIntArgs args) + [GraphQLMutation] + public Expression> AddPersonNamesExpression(TestDataContext db, PeopleMutationsArgs args) + { + var newPerson = new Person { - return true; - } + Id = idCount++, + Name = args.Names![0], + LastName = args.Names[1], + }; + db.People.Add(newPerson); + return ctx => ctx.People.First(p => p.Id == newPerson.Id); + } - [GraphQLMutation] - public async Task regexValidation(RegexArgs args) - { - return await Task.Run(() => - { - return true; - }); - } - - [GraphQLMutation] - public static bool NullableGuidArgs(NullableGuidArgs args) - { - if (args.Id != null) - throw new ArgumentException("Guid can not be a value"); - if (args.Int != null) - throw new ArgumentException("Int can not be a value"); - if (args.Float != null) - throw new ArgumentException("Float can not be a value"); - if (args.Double != null) - throw new ArgumentException("Double can not be a value"); - if (args.Bool != null) - throw new ArgumentException("Bool can not be a value"); - if (args.Enum != null) - throw new ArgumentException("Enum can not be a value"); - return true; - } + [GraphQLMutation] + public Person AddPersonInput(PeopleMutationsArgs args) + { + return new Person { Name = args.NameInput!.Name!, LastName = args.NameInput.LastName! }; + } - [GraphQLMutation] - public static string[] ListOfGuidArgs(ListOfGuidArgs args) - { - if (args.Ids == null) - throw new ArgumentException("Ids can not be null"); - if (args.Ids.Any(i => i == Guid.Empty)) - throw new ArgumentException("Ids can not be empty GUID values"); - return args.Ids.Select(g => g.ToString()).ToArray(); - } - - [GraphQLMutation] - public static int DescriptionArgs(DescriptionArgs args) - { - return args.X; - } + [GraphQLMutation] + public float AddFloat(FloatInput args) + { + return args.Float; } - public class NestedInputObject + [GraphQLMutation] + public double AddDouble(DoubleInput args) { - public string Name { get; set; } = string.Empty; - public string LastName { get; set; } = string.Empty; - public DateTime? Birthday { get; set; } + return args.Double; } - [GraphQLArguments] - public class ListArgs + [GraphQLMutation] + public decimal AddDecimal(DecimalInput args) { - public List Inputs { get; set; } = []; + return args.Decimal; } - [GraphQLArguments] - public class ListIntArgs + [GraphQLMutation] + public Expression> AddPersonAdv(PeopleMutationsArgs args) { - public List Inputs { get; set; } = []; + // test returning a constant in the expression which allows GraphQL selection over the schema (assuming the constant is a type in the schema) + // Ie. in the mutation query you can select any valid fields in the schema from Person + var person = new Person + { + Name = args.Name!, + Tasks = [new Task { Name = "A" }], + Projects = [new Project { Id = 123 }], + }; + return ctx => person; } - [GraphQLArguments] - public class PeopleMutationsArgs + [GraphQLMutation] + public Expression>> AddPersonAdvList(PeopleMutationsArgs args) { - public string? Name { get; set; } - public List? Names { get; set; } + // test returning a constant in the expression which allows GraphQL selection over the schema (assuming the constant is a type in the schema) + // Ie. in the mutation query you can select any valid fields in the schema from Person + var person = new Person + { + Name = args.Name!, + Tasks = [new Task { Name = "A" }], + Projects = [new Project { Id = 123 }], + }; + return ctx => new List { person }; + } - public InputObject? NameInput { get; set; } - public Gender? Gender { get; set; } + [GraphQLMutation] + public Expression>> AddPersonReturnAll(TestDataContext db, PeopleMutationsArgs args) + { + db.People.Add(new Person { Id = 11, Name = args.Name! }); + return ctx => ctx.People; } - [GraphQLArguments] - public class NullableGuidArgs + [GraphQLMutation] + public IEnumerable AddPersonReturnAllConst(TestDataContext db, PeopleMutationsArgs args) { - public Guid? Id { get; set; } - public int? Int { get; set; } - public float? Float { get; set; } - public double? Double { get; set; } - public bool? Bool { get; set; } - public Gender? Enum { get; set; } + db.People.Add(new Person { Id = 11, Name = args.Name! }); + return db.People.ToList(); } - [GraphQLArguments] - public class GuidArgs + [GraphQLMutation] + public int AddPersonError(PeopleMutationsArgs args) { - [Required] - public Guid Id { get; set; } + throw new EntityGraphQLArgumentException("name", "Name can not be null"); } - [GraphQLArguments] - public class GuidNonNullArgs + [GraphQLMutation] + public int AddPersonErrorUnexposedException(PeopleMutationsArgs args) { - public Guid Id { get; set; } + throw new Exception("You should not see this message outside of Development"); } - [GraphQLArguments] - public class RegexArgs + [GraphQLMutation] + public async Task DoGreatThing() { - [RegularExpression("[^a]+", ErrorMessage = "Title does not match required format")] - public string Title { get; set; } = string.Empty; + return await Task.Run(() => + { + return true; + }); + } + + [GraphQLMutation] + public Task DoGreatThingWithoutAsyncKeyword() => DoGreatThing(); - [RegularExpression("steve", ErrorMessage = "Author does not match required format")] - public string Author { get; set; } = string.Empty; + [GraphQLMutation] + public static async Task DoGreatThingStaticly() + { + return await Task.Run(() => + { + return true; + }); } - public class InputObject + [GraphQLMutation] + public async Task NeedsGuid(GuidArgs args) { - public string? Name { get; set; } - public string? LastName { get; set; } - public DateTime? Birthday { get; set; } + return await Task.Run(() => + { + return true; + }); } - public class ListOfObjectsWithIds + [GraphQLMutation] + public async Task NeedsGuidNonNull(GuidNonNullArgs args) { - public IList? InputObjects { get; set; } + return await Task.Run(() => + { + return true; + }); } - public class InputObjectId + [GraphQLMutation] + public bool TaskWithList(ListArgs args) { - public int Id { get; set; } - public long IdLong { get; set; } + if (args?.Inputs == null) + throw new Exception("Inputs can not be null"); + return true; } - [GraphQLArguments] - public class FloatInput + [GraphQLMutation] + public bool TaskWithListSeparateArg(List inputs) => true; + + [GraphQLMutation] + public bool TaskWithListInt(ListIntArgs args) { - public float Float { get; set; } - public float? Float2 { get; set; } + return true; } - [GraphQLArguments] - public class DoubleInput + [GraphQLMutation] + public async Task regexValidation(RegexArgs args) { - public double Double { get; set; } - public double? Double2 { get; set; } + return await Task.Run(() => + { + return true; + }); } - [GraphQLArguments] - public class DecimalInput + [GraphQLMutation] + public static bool NullableGuidArgs(NullableGuidArgs args) { - public decimal Decimal { get; set; } - public decimal? Decimal2 { get; set; } + if (args.Id != null) + throw new ArgumentException("Guid can not be a value"); + if (args.Int != null) + throw new ArgumentException("Int can not be a value"); + if (args.Float != null) + throw new ArgumentException("Float can not be a value"); + if (args.Double != null) + throw new ArgumentException("Double can not be a value"); + if (args.Bool != null) + throw new ArgumentException("Bool can not be a value"); + if (args.Enum != null) + throw new ArgumentException("Enum can not be a value"); + return true; } - [GraphQLArguments] - public class ListOfGuidArgs + [GraphQLMutation] + public static string[] ListOfGuidArgs(ListOfGuidArgs args) { - public List? Ids { get; set; } + if (args.Ids == null) + throw new ArgumentException("Ids can not be null"); + if (args.Ids.Any(i => i == Guid.Empty)) + throw new ArgumentException("Ids can not be empty GUID values"); + return args.Ids.Select(g => g.ToString()).ToArray(); } - [GraphQLArguments] - public class DescriptionArgs + [GraphQLMutation] + public static int DescriptionArgs(DescriptionArgs args) { - [Description("The x parameter")] - public int X { get; set; } + return args.X; } } + +public class NestedInputObject +{ + public string Name { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public DateTime? Birthday { get; set; } +} + +[GraphQLArguments] +public class ListArgs +{ + public List Inputs { get; set; } = []; +} + +[GraphQLArguments] +public class ListIntArgs +{ + public List Inputs { get; set; } = []; +} + +[GraphQLArguments] +public class PeopleMutationsArgs +{ + public string? Name { get; set; } + public List? Names { get; set; } + + public InputObject? NameInput { get; set; } + public Gender? Gender { get; set; } +} + +[GraphQLArguments] +public class NullableGuidArgs +{ + public Guid? Id { get; set; } + public int? Int { get; set; } + public float? Float { get; set; } + public double? Double { get; set; } + public bool? Bool { get; set; } + public Gender? Enum { get; set; } +} + +[GraphQLArguments] +public class GuidArgs +{ + [Required] + public Guid Id { get; set; } +} + +[GraphQLArguments] +public class GuidNonNullArgs +{ + public Guid Id { get; set; } +} + +[GraphQLArguments] +public class RegexArgs +{ + [RegularExpression("[^a]+", ErrorMessage = "Title does not match required format")] + public string Title { get; set; } = string.Empty; + + [RegularExpression("steve", ErrorMessage = "Author does not match required format")] + public string Author { get; set; } = string.Empty; +} + +public class InputObject +{ + public string? Name { get; set; } + public string? LastName { get; set; } + public DateTime? Birthday { get; set; } +} + +public class ListOfObjectsWithIds +{ + public IList? InputObjects { get; set; } +} + +public class InputObjectId +{ + public int Id { get; set; } + public long IdLong { get; set; } +} + +[GraphQLArguments] +public class FloatInput +{ + public float Float { get; set; } + public float? Float2 { get; set; } +} + +[GraphQLArguments] +public class DoubleInput +{ + public double Double { get; set; } + public double? Double2 { get; set; } +} + +[GraphQLArguments] +public class DecimalInput +{ + public decimal Decimal { get; set; } + public decimal? Decimal2 { get; set; } +} + +[GraphQLArguments] +public class ListOfGuidArgs +{ + public List? Ids { get; set; } +} + +[GraphQLArguments] +public class DescriptionArgs +{ + [Description("The x parameter")] + public int X { get; set; } +} diff --git a/src/tests/EntityGraphQL.Tests/OffsetPaging/OffsetPagingTests.cs b/src/tests/EntityGraphQL.Tests/OffsetPaging/OffsetPagingTests.cs index 641d438d..9f869c78 100644 --- a/src/tests/EntityGraphQL.Tests/OffsetPaging/OffsetPagingTests.cs +++ b/src/tests/EntityGraphQL.Tests/OffsetPaging/OffsetPagingTests.cs @@ -483,7 +483,7 @@ private class TestDataContext2 : TestDataContext private static void FillData(TestDataContext data) { - data.People = new() { MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Crow"), MakePerson("Jill", "Castle"), MakePerson("Jack", "Snider"), }; + data.People = [MakePerson("Bill", "Murray"), MakePerson("John", "Frank"), MakePerson("Cheryl", "Crow"), MakePerson("Jill", "Castle"), MakePerson("Jack", "Snider")]; } private static void FillProjectData(TestDataContext data) @@ -501,8 +501,8 @@ private static void FillProjectData(TestDataContext data) new Task { Id = 2, Name = "Task 3" }, new Task { Id = 3, Name = "Task 4" }, new Task { Id = 4, Name = "Task 5" }, - ] - } + ], + }, ]; } @@ -512,7 +512,7 @@ private static Person MakePerson(string fname, string lname) { Id = peopleCnt++, Name = fname, - LastName = lname + LastName = lname, }; } @@ -520,6 +520,6 @@ private static Person MakePerson(string fname, string lname) public void IdPropertyStillGenerated() { var schema = SchemaBuilder.FromObject(); - Assert.NotEmpty(schema.Query().GetFields().Where(x => x.Name == "person")); + Assert.Contains(schema.Query().GetFields(), x => x.Name == "person"); } } diff --git a/src/tests/EntityGraphQL.Tests/PersistedQueriesTests.cs b/src/tests/EntityGraphQL.Tests/PersistedQueriesTests.cs index de42b7d0..60e0d5f0 100644 --- a/src/tests/EntityGraphQL.Tests/PersistedQueriesTests.cs +++ b/src/tests/EntityGraphQL.Tests/PersistedQueriesTests.cs @@ -33,8 +33,8 @@ name id { "persistedQuery", new PersistedQueryExtension { Sha256Hash = hash } - } - } + }, + }, }; var result = schema.ExecuteRequestWithContext(gql, data, null, null, new ExecutionOptions { EnablePersistedQueries = true }); @@ -80,8 +80,8 @@ name id { "persistedQuery", new PersistedQueryExtension { Sha256Hash = hash } - } - } + }, + }, }; var result = schema.ExecuteRequestWithContext(gql, data, null, null, new ExecutionOptions { EnablePersistedQueries = true }); @@ -117,8 +117,8 @@ name id { "persistedQuery", new PersistedQueryExtension { Sha256Hash = hash } - } - } + }, + }, }; var result = schema.ExecuteRequestWithContext(gql, data, null, null, new ExecutionOptions { EnablePersistedQueries = false }); @@ -154,8 +154,8 @@ name id { "persistedQuery", new PersistedQueryExtension { Sha256Hash = hash, Version = 2 } - } - } + }, + }, }; var result = schema.ExecuteRequestWithContext(gql, data, null, null, new ExecutionOptions { EnablePersistedQueries = true }); @@ -167,8 +167,8 @@ name id private static void FillProjectData(TestDataContext data) { - data.Projects = new List - { + data.Projects = + [ new Project { Id = 99, @@ -180,8 +180,8 @@ private static void FillProjectData(TestDataContext data) new Task { Id = 2, Name = "Task 3" }, new Task { Id = 3, Name = "Task 4" }, new Task { Id = 4, Name = "Task 5" }, - } - } - }; + }, + }, + ]; } } diff --git a/src/tests/EntityGraphQL.Tests/QueryCacheTests.cs b/src/tests/EntityGraphQL.Tests/QueryCacheTests.cs index ca5d47ac..b101e036 100644 --- a/src/tests/EntityGraphQL.Tests/QueryCacheTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryCacheTests.cs @@ -44,7 +44,7 @@ name id var gql = new QueryRequest { Query = query, - Variables = new QueryVariables { { "project", 99 } } + Variables = new QueryVariables { { "project", 99 } }, }; // cache the query @@ -111,7 +111,7 @@ name id var gql = new QueryRequest { Query = query, - Variables = new QueryVariables { { "project", 99 } } + Variables = new QueryVariables { { "project", 99 } }, }; var total = 1000; @@ -174,20 +174,20 @@ private static void FillProjectData(TestDataContext data) new Task { Id = 3, Name = "Task 4" }, new Task { Id = 4, Name = "Task 5" }, }; - data.Projects = new List - { + data.Projects = + [ new Project { Id = 99, Name = "Project 1", - Tasks = tasks + Tasks = tasks, }, new Project { Id = 1, Name = "Project 1", - Tasks = tasks - } - }; + Tasks = tasks, + }, + ]; } } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/ArgumentTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/ArgumentTests.cs index 41501105..c29d02ce 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/ArgumentTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/ArgumentTests.cs @@ -78,7 +78,7 @@ public void ThrowsOnMissingRequiredArgument() Query = @"query { user { id } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.NotNull(result.Errors); @@ -104,7 +104,7 @@ public void ThrowsOnMissingRequiredArguments() Query = @"query { user { id } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.NotNull(result.Errors); @@ -192,7 +192,7 @@ public void SupportsArgumentsInNonRootAndEnumAsVar() @"query People($unitType: HeightUnit) { people { height(unit: $unitType) } }", - Variables = new QueryVariables { { "unitType", "Meter" } } + Variables = new QueryVariables { { "unitType", "Meter" } }, }; var tree = new GraphQLCompiler(schema).Compile(gql).ExecuteQuery(new TestDataContext().FillWithTestData(), null, gql.Variables, null); @@ -236,7 +236,7 @@ public void SupportsArgumentsGuidAsVar() @"query Guid($id: ID!) { person(id: $id) { id projects { id name } } }", - Variables = new QueryVariables { { "id", "cccccccc-bbbb-4444-1111-ccddeeff0033" } } + Variables = new QueryVariables { { "id", "cccccccc-bbbb-4444-1111-ccddeeff0033" } }, }; var tree = new GraphQLCompiler(schema).Compile(gql).ExecuteQuery(new TestDataContext().FillWithTestData(), null, gql.Variables); @@ -292,7 +292,7 @@ query MyQuery($limit: Int = 10) { public void FloatArg() { var schema = SchemaBuilder.FromObject(); - schema.Query().ReplaceField("users", new { f = (float?)null, }, (db, p) => db.Users, "Testing float"); + schema.Query().ReplaceField("users", new { f = (float?)null }, (db, p) => db.Users, "Testing float"); var gql = new GraphQLCompiler(schema).Compile( @" @@ -311,7 +311,7 @@ public void FloatArg() public void StringArg() { var schema = SchemaBuilder.FromObject(); - schema.Query().ReplaceField("users", new { str = (string?)null, }, (db, p) => db.Users.WhereWhen(u => u.Field2.Contains(p.str!), !string.IsNullOrEmpty(p.str)), "Testing string"); + schema.Query().ReplaceField("users", new { str = (string?)null }, (db, p) => db.Users.WhereWhen(u => u.Field2.Contains(p.str!), !string.IsNullOrEmpty(p.str)), "Testing string"); var gql = new GraphQLCompiler(schema).Compile( @" @@ -399,7 +399,7 @@ public void EnumerableArg() new { // EntityGraphQL will automatically use a List - names = (IEnumerable?)null + names = (IEnumerable?)null, }, (db, p) => db.People.WhereWhen(per => p.names!.Any(a => a == per.Name), p.names != null), "Testing list" @@ -483,16 +483,8 @@ public void TestSameNameArgsOnDifferentFields() { Id = 99, Name = "jill", - Projects = new List - { - new Project { Id = 1, Name = "Project 1" }, - new Project { Id = 2, Name = "Project 2" } - }, - Tasks = new List - { - new Task { Id = 1, Name = "Task 1" }, - new Task { Id = 2, Name = "Task 2" } - } + Projects = [new Project { Id = 1, Name = "Project 1" }, new Project { Id = 2, Name = "Project 2" },], + Tasks = [new Task { Id = 1, Name = "Task 1" }, new Task { Id = 2, Name = "Task 2" },], } ); var qr = gql.ExecuteQuery(context, null, null); @@ -521,16 +513,12 @@ public void TestSameNameArgsOnDifferentFieldsRoot() ); var context = new TestDataContext { - Projects = new List - { - new Project { Id = 1, Name = "Project 1" }, - new Project { Id = 2, Name = "Project 2" } - }, + Projects = [new Project { Id = 1, Name = "Project 1" }, new Project { Id = 2, Name = "Project 2" },], Tasks = new List { new Task { Id = 1, Name = "Task 1" }, - new Task { Id = 2, Name = "Task 2" } - } + new Task { Id = 2, Name = "Task 2" }, + }, }; var qr = gql.ExecuteQuery(context, null, null); Assert.Null(qr.Errors); @@ -564,11 +552,7 @@ public void TestSameNameArgsOnSameFieldWithAlias() { Id = 99, Name = "jill", - Tasks = new List - { - new Task { Id = 1, Name = "Task 1" }, - new Task { Id = 2, Name = "Task 2" } - } + Tasks = [new Task { Id = 1, Name = "Task 1" }, new Task { Id = 2, Name = "Task 2" },], } ); var qr = gql.ExecuteQuery(context, null, null); diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/AsyncTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/AsyncTests.cs index 69467d28..091670b5 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/AsyncTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/AsyncTests.cs @@ -23,7 +23,7 @@ public void TestAsyncServiceField() people { age } - }" + }", }; var context = new TestDataContext(); diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveOnVisitTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveOnVisitTests.cs index 3411896a..56361743 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveOnVisitTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveOnVisitTests.cs @@ -22,7 +22,7 @@ public void TestOnVisitListFieldRoot() people @myDirective { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -41,7 +41,7 @@ public void TestOnVisitObjectFieldRoot() person(id: 1) @myDirective { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -58,7 +58,7 @@ public void TestOnVisitScalarFieldRoot() Query = @"query { totalPeople @myDirective - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -80,7 +80,7 @@ projects @myDirective { id } } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -102,7 +102,7 @@ manager @myDirective { id } } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -122,7 +122,7 @@ public void TestOnVisitScalarField() id name @myDirective } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -144,7 +144,7 @@ public void TestOnVisitFragmentDef() } fragment myFragment on Person @myDirective { id - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FRAGMENT_DEFINITION, directive.WasVisited); @@ -166,7 +166,7 @@ ...myFragment @myDirective } fragment myFragment on Person { id - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FRAGMENT_SPREAD, directive.WasVisited); @@ -227,7 +227,7 @@ public void TestOnVisitMutationField() doStuff @myDirective { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -255,7 +255,7 @@ public void TestOnVisitMutationInnerField() doStuff { id @myDirective } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -283,7 +283,7 @@ public void TestOnVisitMutationStatement() doStuff { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.MUTATION, directive.WasVisited); @@ -300,7 +300,7 @@ public void TestOnVisitQueryStatement() Query = @"query @myDirective { people { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.QUERY, directive.WasVisited); @@ -319,7 +319,7 @@ public void TestOnVisitSubscriptionStatement() Query = @"subscription @myDirective { onMessage { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.SUBSCRIPTION, directive.WasVisited); @@ -338,7 +338,7 @@ public void TestOnVisitSubscriptionField() Query = @"subscription { onMessage @myDirective { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FIELD, directive.WasVisited); @@ -355,7 +355,7 @@ public void TestOnVisitVariableDef() Query = @"query Q($id: Int @myDirective) { people { id } - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.VARIABLE_DEFINITION, directive.WasVisited); @@ -378,7 +378,7 @@ ...myFragment @myDirective } fragment myFragment on Person { id - }" + }", }; schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Equal(ExecutableDirectiveLocation.FRAGMENT_SPREAD, directive.WasVisited); @@ -390,15 +390,9 @@ internal class MyDirecitive : DirectiveProcessor { private readonly ExecutableDirectiveLocation location; - public override string Name - { - get => "myDirective"; - } - public override string Description - { - get => "My directive"; - } - public override List Location => new() { location }; + public override string Name => "myDirective"; + public override string Description => "My directive"; + public override List Location => [location]; public ExecutableDirectiveLocation? WasVisited { get; private set; } public int Calls { get; private set; } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveTests.cs index 65413171..0a01562e 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/DirectiveTests.cs @@ -25,7 +25,7 @@ public void TestIncludeIfTrueConstant() id name @include(if: true) } - }" + }", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); dynamic person = ((dynamic)result.Data!["people"]!)[0]; @@ -45,7 +45,7 @@ public void TestIncludeIfFalseConstant() id name @include(if: false) } - }" + }", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Null(result.Errors); @@ -66,7 +66,7 @@ public void TestIncludeIfFalseConstantOnIdField() id name } - }" + }", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Null(result.Errors); @@ -86,7 +86,7 @@ people @include(if: false) { id name } - }" + }", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Null(result.Errors); @@ -107,7 +107,7 @@ public void TestIncludeIfTrueVariable() name @include(if: $include) } }", - Variables = new QueryVariables { { "include", true } } + Variables = new QueryVariables { { "include", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); dynamic person = ((dynamic)result.Data!["people"]!)[0]; @@ -127,7 +127,7 @@ public void TestSkipIfTrueConstant() id name @skip(if: true) } -}" +}", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); dynamic person = ((dynamic)result.Data!["people"]!)[0]; @@ -147,7 +147,7 @@ public void TestSkipIfFalseConstant() id name @skip(if: false) } -}" +}", }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); dynamic person = ((dynamic)result.Data!["people"]!)[0]; @@ -168,7 +168,7 @@ public void TestSkipIfFalseVariable() name @skip(if: $skip) } }", - Variables = new QueryVariables { { "skip", true } } + Variables = new QueryVariables { { "skip", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); dynamic person = ((dynamic)result.Data!["people"]!)[0]; @@ -189,7 +189,7 @@ people @skip(if: $skip) { name } }", - Variables = new QueryVariables { { "skip", true } } + Variables = new QueryVariables { { "skip", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.Null(result.Errors); @@ -213,7 +213,7 @@ projects @skip(if: $skip) { } } }", - Variables = new QueryVariables { { "skip", true } } + Variables = new QueryVariables { { "skip", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); var person = ((dynamic)result.Data!["people"]!)[0]; @@ -238,7 +238,7 @@ manager @skip(if: $skip) { } } }", - Variables = new QueryVariables { { "skip", true } } + Variables = new QueryVariables { { "skip", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); var person = ((dynamic)result.Data!["people"]!)[0]; @@ -263,7 +263,7 @@ public void TestDirectiveOnMutation() { Name = string.IsNullOrEmpty(args.Name) ? "Default" : args.Name, Id = 555, - Projects = new List() + Projects = [], }; }, new SchemaBuilderOptions { AutoCreateInputTypes = true } @@ -277,7 +277,7 @@ public void TestDirectiveOnMutation() name } }", - Variables = new QueryVariables { { "skip", true } } + Variables = new QueryVariables { { "skip", true } }, }; var result = schema.ExecuteRequestWithContext(query, new TestDataContext().FillWithTestData(), null, null, null); Assert.False(mutationCalled); @@ -339,14 +339,14 @@ internal class ExampleDirective : DirectiveProcessor public override string Description => "Actually does nothing"; - public override List Location => new() { ExecutableDirectiveLocation.FIELD }; + public override List Location => [ExecutableDirectiveLocation.FIELD]; } internal class FormatDirective : DirectiveProcessor { public override string Name => "format"; public override string Description => "Formats DateTime scalar values"; - public override List Location => new() { ExecutableDirectiveLocation.FIELD }; + public override List Location => [ExecutableDirectiveLocation.FIELD]; public override IGraphQLNode VisitNode(ExecutableDirectiveLocation location, IGraphQLNode? node, object? arguments) { diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/FilteredFieldTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/FilteredFieldTests.cs index 5c936184..c737c175 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/FilteredFieldTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/FilteredFieldTests.cs @@ -17,7 +17,7 @@ public void TestUseConstFilter() var schema = SchemaBuilder.FromObject(); schema.Query().ReplaceField("projects", new { search = (string?)null }, (ctx, args) => ctx.Projects.OrderBy(p => p.Id), "List of projects"); - Func TaskFilter = t => t.IsActive == true; + Func TaskFilter = t => t.IsActive; schema.Type().ReplaceField("tasks", p => p.Tasks.Where(TaskFilter), "Active tasks"); var gql = new QueryRequest @@ -27,10 +27,10 @@ public void TestUseConstFilter() projects { tasks { id } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Tasks = new List { new Task() }, } }, }; + var context = new TestDataContext { Projects = [new Project { Tasks = new List { new Task() } }] }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); Assert.Null(res.Errors); @@ -61,13 +61,13 @@ public void TestWhereWhenOnNonRootField() projects { tasks(like: ""h"") { name } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Tasks = new List @@ -75,9 +75,9 @@ public void TestWhereWhenOnNonRootField() new Task { Name = "hello" }, new Task { Name = "world" }, }, - Description = "Hello" - } - }, + Description = "Hello", + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, null, null); @@ -99,7 +99,7 @@ public void TestOffsetPagingWithOthersAndServices() Id = 1, Name = "Jill", LastName = "Frank", - Birthday = DateTime.Now.AddYears(22) + Birthday = DateTime.Now.AddYears(22), } ); data.People.Add( @@ -108,7 +108,7 @@ public void TestOffsetPagingWithOthersAndServices() Id = 2, Name = "Cheryl", LastName = "Frank", - Birthday = DateTime.Now.AddYears(10) + Birthday = DateTime.Now.AddYears(10), } ); @@ -151,7 +151,7 @@ public void TestFilterWithServiceReference(bool separateServices) Id = 1, Name = "Jill", LastName = "Frank", - Birthday = DateTime.Now.AddYears(-22) + Birthday = DateTime.Now.AddYears(-22), } ); data.People.Add( @@ -160,12 +160,12 @@ public void TestFilterWithServiceReference(bool separateServices) Id = 2, Name = "Cheryl", LastName = "Frank", - Birthday = DateTime.Now.AddYears(-10) + Birthday = DateTime.Now.AddYears(-10), } ); schema.Query().ReplaceField("people", ctx => ctx.People, "Return list of people").UseFilter(); - schema.Type().AddField("age", "Persons age").ResolveWithService((person, ager) => ager.GetAge(person.Birthday)); + schema.Type().AddField("age", "Persons age").Resolve((person, ager) => ager.GetAge(person.Birthday)); var gql = new QueryRequest { Query = diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/FragmentTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/FragmentTests.cs index d526a644..e5e37610 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/FragmentTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/FragmentTests.cs @@ -4,17 +4,17 @@ using EntityGraphQL.Schema; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class FragmentTests { - public class FragmentTests + [Fact] + public void SupportsFragmentSelectionSyntax() { - [Fact] - public void SupportsFragmentSelectionSyntax() - { - var schemaProvider = SchemaBuilder.FromObject(); - // Add a argument field with a require parameter - var tree = new GraphQLCompiler(schemaProvider).Compile( - @" + var schemaProvider = SchemaBuilder.FromObject(); + // Add a argument field with a require parameter + var tree = new GraphQLCompiler(schemaProvider).Compile( + @" query { people { ...info projects { id name } } } @@ -22,25 +22,25 @@ fragment info on Person { id name } " - ); - - Assert.Single(tree.Operations.First().QueryFields); - var qr = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - dynamic person = Enumerable.First((dynamic)qr.Data!["people"]!); - // we only have the fields requested - Assert.Equal(3, person.GetType().GetFields().Length); - Assert.NotNull(person.GetType().GetField("id")); - Assert.NotNull(person.GetType().GetField("name")); - Assert.NotNull(person.GetType().GetField("projects")); - } - - [Fact] - public void SupportsFragmentWithDirective() - { - var schemaProvider = SchemaBuilder.FromObject(); - // Add a argument field with a require parameter - var tree = new GraphQLCompiler(schemaProvider).Compile( - @" + ); + + Assert.Single(tree.Operations.First().QueryFields); + var qr = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + dynamic person = Enumerable.First((dynamic)qr.Data!["people"]!); + // we only have the fields requested + Assert.Equal(3, person.GetType().GetFields().Length); + Assert.NotNull(person.GetType().GetField("id")); + Assert.NotNull(person.GetType().GetField("name")); + Assert.NotNull(person.GetType().GetField("projects")); + } + + [Fact] + public void SupportsFragmentWithDirective() + { + var schemaProvider = SchemaBuilder.FromObject(); + // Add a argument field with a require parameter + var tree = new GraphQLCompiler(schemaProvider).Compile( + @" query { people { ...info @skip(if: true) @@ -50,42 +50,42 @@ fragment info on Person { id name } " - ); - - Assert.Single(tree.Operations.First().QueryFields); - var qr = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - dynamic person = Enumerable.First((dynamic)qr.Data!["people"]!); - // we only have the fields requested - Assert.Equal(1, person.GetType().GetFields().Length); - Assert.NotNull(person.GetType().GetField("projects")); - } - - [Fact] - public void TestReuseFragment() + ); + + Assert.Single(tree.Operations.First().QueryFields); + var qr = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + dynamic person = Enumerable.First((dynamic)qr.Data!["people"]!); + // we only have the fields requested + Assert.Equal(1, person.GetType().GetFields().Length); + Assert.NotNull(person.GetType().GetField("projects")); + } + + [Fact] + public void TestReuseFragment() + { + var schema = SchemaBuilder.FromObject(); + + schema + .Query() + .AddField( + "activeProjects", + ctx => ctx.Projects, // pretent you id some filtering here + "Active projects" + ) + .IsNullable(false); + schema + .Query() + .AddField( + "oldProjects", + ctx => ctx.Projects, // pretent you id some filtering here + "Old projects" + ) + .IsNullable(false); + + var gql = new QueryRequest { - var schema = SchemaBuilder.FromObject(); - - schema - .Query() - .AddField( - "activeProjects", - ctx => ctx.Projects, // pretent you id some filtering here - "Active projects" - ) - .IsNullable(false); - schema - .Query() - .AddField( - "oldProjects", - ctx => ctx.Projects, // pretent you id some filtering here - "Old projects" - ) - .IsNullable(false); - - var gql = new QueryRequest - { - Query = - @"query { + Query = + @"query { activeProjects { ...frag } @@ -96,28 +96,28 @@ public void TestReuseFragment() fragment frag on Project { id - }" - }; + }", + }; - var context = new TestDataContext().FillWithTestData(); + var context = new TestDataContext().FillWithTestData(); - var res = schema.ExecuteRequestWithContext(gql, context, null, null); - Assert.Null(res.Errors); - } + var res = schema.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(res.Errors); + } - [Fact] - public void TestFragmentWithFieldThatSkipsARelation() + [Fact] + public void TestFragmentWithFieldThatSkipsARelation() + { + var schema = SchemaBuilder.FromObject(); + schema.UpdateType(projectType => { - var schema = SchemaBuilder.FromObject(); - schema.UpdateType(projectType => - { - projectType.AddField("manager", p => p.Owner!.Manager, "The manager of the owner"); - }); + projectType.AddField("manager", p => p.Owner!.Manager, "The manager of the owner"); + }); - var gql = new QueryRequest - { - Query = - @"query { + var gql = new QueryRequest + { + Query = + @"query { projects { ...frag } @@ -127,47 +127,47 @@ fragment frag on Project { manager { name } - }" - }; + }", + }; - var context = new TestDataContext - { - Projects = new List + var context = new TestDataContext + { + Projects = + [ + new Project { - new Project + Id = 9, + Owner = new Person { - Id = 9, - Owner = new Person - { - Name = "Bill", - Manager = new Person { Name = "Jill" } - }, - Tasks = new List { new Task() } + Name = "Bill", + Manager = new Person { Name = "Jill" }, }, + Tasks = new List { new Task() }, }, - }; - - var res = schema.ExecuteRequestWithContext(gql, context, null, null); - Assert.Null(res.Errors); - } + ], + }; - [Fact] - public void TestIntrospectionDoubleFragment() - { - var schema = new SchemaProvider(); - schema.AddType( - "Person", - "Person details", - type => - { - type.AddField("id", p => p.Id, "ID"); - } - ); + var res = schema.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(res.Errors); + } - var gql = new QueryRequest + [Fact] + public void TestIntrospectionDoubleFragment() + { + var schema = new SchemaProvider(); + schema.AddType( + "Person", + "Person details", + type => { - Query = - @"query IntrospectionQuery { + type.AddField("id", p => p.Id, "ID"); + } + ); + + var gql = new QueryRequest + { + Query = + @"query IntrospectionQuery { __type(name: ""Person"") { ...FullType } @@ -190,21 +190,20 @@ fragment TypeRef on __Type { name kind } - }" - }; - - var context = new TestDataContext(); - - var res = schema.ExecuteRequestWithContext(gql, context, null, null); - Assert.Null(res.Errors); - dynamic typeData = res.Data!["__type"]!; - Assert.Equal("Person", typeData.name); - Assert.Single(typeData.fields); - var field = typeData.fields[0]; - Assert.Equal("id", field.name); - Assert.Equal("NON_NULL", field.type.kind); - Assert.Equal("Int", field.type.ofType.name); - Assert.Equal("SCALAR", field.type.ofType.kind); - } + }", + }; + + var context = new TestDataContext(); + + var res = schema.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(res.Errors); + dynamic typeData = res.Data!["__type"]!; + Assert.Equal("Person", typeData.name); + Assert.Single(typeData.fields); + var field = typeData.fields[0]; + Assert.Equal("id", field.name); + Assert.Equal("NON_NULL", field.type.kind); + Assert.Equal("Int", field.type.ofType.name); + Assert.Equal("SCALAR", field.type.ofType.kind); } } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/GraphQLFieldAttributeTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/GraphQLFieldAttributeTests.cs index 10cd5284..1dadacc5 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/GraphQLFieldAttributeTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/GraphQLFieldAttributeTests.cs @@ -30,7 +30,7 @@ public void TestGraphQLFieldAttribute() fields { methodField } - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -63,7 +63,7 @@ public void TestGraphQLFieldAttributeWithArgs() methodFieldWithArgs(value: $value) } }", - Variables = new QueryVariables { { "value", 13 } } + Variables = new QueryVariables { { "value", 13 } }, }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -95,7 +95,7 @@ public void TestGraphQLFieldAttributeWithOptionalArgs() fields { methodFieldWithOptionalArgs } - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -128,7 +128,7 @@ public void TestGraphQLFieldAttributeWithTwoArgs() methodFieldWithTwoArgs(value: $value, value2: $value2) } }", - Variables = new QueryVariables { { "value", 6 }, { "value2", 7 }, } + Variables = new QueryVariables { { "value", 6 }, { "value2", 7 } }, }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -160,7 +160,7 @@ public void TestGraphQLFieldAttributeWithDefaultArgs() fields { methodFieldWithDefaultArgs } - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -194,7 +194,7 @@ public void TestGraphQLFieldAttributeRename() renamedMethod(value: $value) } }", - Variables = new QueryVariables { { "value", 33 }, } + Variables = new QueryVariables { { "value", 33 } }, }; var context = new ContextFieldWithMethod { Fields = [new TypeWithMethod()] }; @@ -221,7 +221,7 @@ public void TestGraphQLFieldAttributeOnContext() Query = @"query TypeWithMethod { testMethod - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -251,7 +251,7 @@ public void TestGraphQLFieldAttributeStatic() fields { staticMethodField(value: 88) } - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List() { new TypeWithMethod() } }; @@ -278,7 +278,7 @@ public void TestGraphQLFieldAttributeWithService() Query = @"query TypeWithMethod { methodFieldWithService(value: 88) - }" + }", }; var context = new ContextFieldWithMethodService(); @@ -307,7 +307,7 @@ public void TestGraphQLFieldAttributeWithServiceStatic() Query = @"query TypeWithMethod { methodFieldWithServiceStatic(value: 88) - }" + }", }; var context = new ContextFieldWithMethodService(); @@ -336,7 +336,7 @@ public void TestGraphQLFieldAttributeWithArgumentsClass() complex { methodFieldWithArgs(name: ""Superman"") } - }" + }", }; var context = new ContextFieldWithMethod2 { Complex = new List { new TypeWithMethodArgs() } }; @@ -363,7 +363,7 @@ public void TestGraphQLFieldAttributeWithInputClass() complex { methodFieldWithInput(props: { name: ""Superman"" }) } - }" + }", }; var context = new ContextFieldWithMethod3 { Complex = new List { new TypeWithMethodInput() } }; @@ -392,7 +392,7 @@ public void TestGraphQLFieldReturnSchemaType() getMyTask(id: 8) { id } - }" + }", }; var context = new ContextFieldWithMethod(); @@ -424,7 +424,7 @@ public void TestGraphQLFieldReturnSchemaTypeRelation() id name } } - }" + }", }; var context = new ContextFieldWithMethod @@ -438,10 +438,10 @@ id name Tasks = new List { new Task { Id = 1, Name = "abba" }, - new Task { Id = 2, Name = "b" } - } - } - } + new Task { Id = 2, Name = "b" }, + }, + }, + }, }; var res = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); @@ -470,7 +470,7 @@ public void TestGraphQLFieldAttributeWithServiceNotRoot() id fieldWithService(value: 88) } - }" + }", }; var context = new ContextFieldWithMethod { Fields = new List { new TypeWithMethod() } }; diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceAdvancedTest.cs b/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceAdvancedTest.cs index 789c0886..2b0d474d 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceAdvancedTest.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceAdvancedTest.cs @@ -1,125 +1,124 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using System.Text.Json; using EntityGraphQL.Schema; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class InheritanceAdvancedTest { - public class InheritanceAdvancedTest + public abstract class Entity { - public abstract class Entity - { - public int Id { get; set; } - - [GraphQLIgnore] - public int TenantId { get; set; } - } + public int Id { get; set; } - public class Order : Entity - { - public string Name { get; set; } = string.Empty; - public Status? Status { get; set; } - public ICollection OrderItems { get; set; } = []; - } + [GraphQLIgnore] + public int TenantId { get; set; } + } - public abstract class OrderItem : Entity - { - public Status? Status { get; set; } - } + public class Order : Entity + { + public string Name { get; set; } = string.Empty; + public Status? Status { get; set; } + public ICollection OrderItems { get; set; } = []; + } - public class BookOrderItem : OrderItem - { - public Book? Book { get; set; } - } + public abstract class OrderItem : Entity + { + public Status? Status { get; set; } + } - public class TShirtOrderItem : OrderItem - { - public int Size { get; set; } - public int Colour { get; set; } - public TShirt? TShirt { get; set; } + public class BookOrderItem : OrderItem + { + public Book? Book { get; set; } + } - public static string FormatTShirtPropertiesAsString(int size, int colour) - { - return $"{size} {colour}"; - } - } + public class TShirtOrderItem : OrderItem + { + public int Size { get; set; } + public int Colour { get; set; } + public TShirt? TShirt { get; set; } - public class Status + public static string FormatTShirtPropertiesAsString(int size, int colour) { - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public bool IsDeleted { get; set; } + return $"{size} {colour}"; } + } - public abstract class Product : Entity - { - public string Name { get; set; } = string.Empty; - } + public class Status + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public bool IsDeleted { get; set; } + } - public class Book : Product - { - public int Pages { get; set; } - public string Author { get; set; } = string.Empty; - } + public abstract class Product : Entity + { + public string Name { get; set; } = string.Empty; + } - public class TShirt : Product { } + public class Book : Product + { + public int Pages { get; set; } + public string Author { get; set; } = string.Empty; + } - public class TestContext - { - public IList Orders { get; set; } = []; - public IList Products { get; set; } = []; - public IList Statuses { get; set; } = []; - } + public class TShirt : Product { } + + public class TestContext + { + public IList Orders { get; set; } = []; + public IList Products { get; set; } = []; + public IList Statuses { get; set; } = []; + } - [Fact] - public void BookStoreInheritanceTest() + [Fact] + public void BookStoreInheritanceTest() + { + var context = new TestContext() { - var context = new TestContext() - { - Orders = new List() + Orders = + [ + new Order() { - new Order() - { - Id = 1, - Name = "Barney", - Status = new Status() { Id = 0, Name = "Pending" }, - OrderItems = new List() + Id = 1, + Name = "Barney", + Status = new Status() { Id = 0, Name = "Pending" }, + OrderItems = + [ + new TShirtOrderItem() { - new TShirtOrderItem() + Colour = 1, + Size = 7, + Status = new Status() { Id = 2, Name = "BackOrder" }, + TShirt = new TShirt() { Id = 3, Name = "SpiderMan" }, + }, + new BookOrderItem() + { + Status = new Status() { Id = 4, Name = "Shipped" }, + Book = new Book() { - Colour = 1, - Size = 7, - Status = new Status() { Id = 2, Name = "BackOrder" }, - TShirt = new TShirt() { Id = 3, Name = "SpiderMan" } + Author = "Ben Riley", + Name = "My Life", + Pages = 300, }, - new BookOrderItem() - { - Status = new Status() { Id = 4, Name = "Shipped" }, - Book = new Book() - { - Author = "Ben Riley", - Name = "My Life", - Pages = 300 - } - } - } - } - } - }; - - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); - // book and tshirt added with AddAllFields above - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - - // Simulate a JSON request with System.Text.Json - var q = @"{ + }, + ], + }, + ], + }; + + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); + // book and tshirt added with AddAllFields above + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + + // Simulate a JSON request with System.Text.Json + var q = @"{ ""query"": ""query Order($orderId: Int) { order(id: $orderId) { id @@ -149,82 +148,82 @@ ... on TShirtOrderItem { ""orderId"": ""1"" } }".Replace('\r', ' ').Replace('\n', ' '); - var gql = System.Text.Json.JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); - Assert.False(results.HasErrors()); - var order = (dynamic)results.Data!["order"]!; - - Assert.Equal(4, order.GetType().GetFields().Length); - Assert.Equal(1, order.id); - Assert.Equal("Barney", order.name); - Assert.Equal("Pending", order.status.name); - Assert.Equal(2, order.orderItems.Count); - - Assert.Equal(6, order.orderItems[0].GetType().GetFields().Length); - Assert.Equal(nameof(TShirtOrderItem), order.orderItems[0].__typename); - Assert.Equal(1, order.orderItems[0].colour); - Assert.Equal(7, order.orderItems[0].size); - Assert.Equal("BackOrder", order.orderItems[0].status.name); - - Assert.Equal(1, order.orderItems[0].tShirt.GetType().GetFields().Length); - Assert.Equal("SpiderMan", order.orderItems[0].tShirt.name); - Assert.Null(order.orderItems[0].tShirt.GetType().GetField("Tenant")); - - Assert.Equal(4, order.orderItems[1].GetType().GetFields().Length); - Assert.Equal(nameof(BookOrderItem), order.orderItems[1].__typename); - Assert.Equal("Shipped", order.orderItems[1].status.name); - - Assert.Equal(2, order.orderItems[1].book.GetType().GetFields().Length); - Assert.Null(order.orderItems[1].book.GetType().GetField("Author")); - Assert.Equal("My Life", order.orderItems[1].book.name); - Assert.Equal(300, order.orderItems[1].book.pages); - } + var gql = System.Text.Json.JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); + Assert.False(results.HasErrors()); + var order = (dynamic)results.Data!["order"]!; + + Assert.Equal(4, order.GetType().GetFields().Length); + Assert.Equal(1, order.id); + Assert.Equal("Barney", order.name); + Assert.Equal("Pending", order.status.name); + Assert.Equal(2, order.orderItems.Count); + + Assert.Equal(6, order.orderItems[0].GetType().GetFields().Length); + Assert.Equal(nameof(TShirtOrderItem), order.orderItems[0].__typename); + Assert.Equal(1, order.orderItems[0].colour); + Assert.Equal(7, order.orderItems[0].size); + Assert.Equal("BackOrder", order.orderItems[0].status.name); + + Assert.Equal(1, order.orderItems[0].tShirt.GetType().GetFields().Length); + Assert.Equal("SpiderMan", order.orderItems[0].tShirt.name); + Assert.Null(order.orderItems[0].tShirt.GetType().GetField("Tenant")); + + Assert.Equal(4, order.orderItems[1].GetType().GetFields().Length); + Assert.Equal(nameof(BookOrderItem), order.orderItems[1].__typename); + Assert.Equal("Shipped", order.orderItems[1].status.name); + + Assert.Equal(2, order.orderItems[1].book.GetType().GetFields().Length); + Assert.Null(order.orderItems[1].book.GetType().GetField("Author")); + Assert.Equal("My Life", order.orderItems[1].book.name); + Assert.Equal(300, order.orderItems[1].book.pages); + } - [Fact] - public void BookStoreInheritanceTestWithFragments() + [Fact] + public void BookStoreInheritanceTestWithFragments() + { + var context = new TestContext() { - var context = new TestContext() - { - Orders = - [ - new Order() - { - Id = 1, - Name = "Barney", - Status = new Status() { Id = 0, Name = "Pending" }, - OrderItems = - [ - new TShirtOrderItem() + Orders = + [ + new Order() + { + Id = 1, + Name = "Barney", + Status = new Status() { Id = 0, Name = "Pending" }, + OrderItems = + [ + new TShirtOrderItem() + { + Colour = 1, + Size = 7, + Status = new Status() { Id = 2, Name = "BackOrder" }, + TShirt = new TShirt() { Id = 3, Name = "SpiderMan" }, + }, + new BookOrderItem() + { + Status = new Status() { Id = 4, Name = "Shipped" }, + Book = new Book() { - Colour = 1, - Size = 7, - Status = new Status() { Id = 2, Name = "BackOrder" }, - TShirt = new TShirt() { Id = 3, Name = "SpiderMan" } + Author = "Ben Riley", + Name = "My Life", + Pages = 300, }, - new BookOrderItem() - { - Status = new Status() { Id = 4, Name = "Shipped" }, - Book = new Book() - { - Author = "Ben Riley", - Name = "My Life", - Pages = 300 - } - } - ] - } - ] - }; - - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); - // book and tshirt added with AddAllFields above - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - - // Simulate a JSON request with System.Text.Json - var q = @"{ + }, + ], + }, + ], + }; + + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); + // book and tshirt added with AddAllFields above + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + + // Simulate a JSON request with System.Text.Json + var q = @"{ ""query"": ""query Order($orderId: Int) { order(id: $orderId) { ...order @@ -262,87 +261,87 @@ ... on TShirtOrderItem { ""orderId"": ""1"" } }".Replace('\r', ' ').Replace('\n', ' '); - var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); - Assert.False(results.HasErrors()); - var order = (dynamic)results.Data!["order"]!; - - Assert.Equal(4, order.GetType().GetFields().Length); - Assert.Equal(1, order.id); - Assert.Equal("Barney", order.name); - Assert.Equal("Pending", order.status.name); - Assert.Equal(2, order.orderItems.Count); - - Assert.Equal(6, order.orderItems[0].GetType().GetFields().Length); - Assert.Equal(nameof(TShirtOrderItem), order.orderItems[0].__typename); - Assert.Equal(1, order.orderItems[0].colour); - Assert.Equal(7, order.orderItems[0].size); - Assert.Equal("BackOrder", order.orderItems[0].status.name); - - Assert.Equal(1, order.orderItems[0].tShirt.GetType().GetFields().Length); - Assert.Equal("SpiderMan", order.orderItems[0].tShirt.name); - Assert.Null(order.orderItems[0].tShirt.GetType().GetField("Tenant")); - - Assert.Equal(4, order.orderItems[1].GetType().GetFields().Length); - Assert.Equal(nameof(BookOrderItem), order.orderItems[1].__typename); - Assert.Equal("Shipped", order.orderItems[1].status.name); - - Assert.Equal(2, order.orderItems[1].book.GetType().GetFields().Length); - Assert.Null(order.orderItems[1].book.GetType().GetField("Author")); - Assert.Equal("My Life", order.orderItems[1].book.name); - Assert.Equal(300, order.orderItems[1].book.pages); - } + var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); + Assert.False(results.HasErrors()); + var order = (dynamic)results.Data!["order"]!; + + Assert.Equal(4, order.GetType().GetFields().Length); + Assert.Equal(1, order.id); + Assert.Equal("Barney", order.name); + Assert.Equal("Pending", order.status.name); + Assert.Equal(2, order.orderItems.Count); + + Assert.Equal(6, order.orderItems[0].GetType().GetFields().Length); + Assert.Equal(nameof(TShirtOrderItem), order.orderItems[0].__typename); + Assert.Equal(1, order.orderItems[0].colour); + Assert.Equal(7, order.orderItems[0].size); + Assert.Equal("BackOrder", order.orderItems[0].status.name); + + Assert.Equal(1, order.orderItems[0].tShirt.GetType().GetFields().Length); + Assert.Equal("SpiderMan", order.orderItems[0].tShirt.name); + Assert.Null(order.orderItems[0].tShirt.GetType().GetField("Tenant")); + + Assert.Equal(4, order.orderItems[1].GetType().GetFields().Length); + Assert.Equal(nameof(BookOrderItem), order.orderItems[1].__typename); + Assert.Equal("Shipped", order.orderItems[1].status.name); + + Assert.Equal(2, order.orderItems[1].book.GetType().GetFields().Length); + Assert.Null(order.orderItems[1].book.GetType().GetField("Author")); + Assert.Equal("My Life", order.orderItems[1].book.name); + Assert.Equal(300, order.orderItems[1].book.pages); + } - [Fact] - public void InheritanceTestUsingMethod() + [Fact] + public void InheritanceTestUsingMethod() + { + var context = new TestContext() { - var context = new TestContext() - { - Orders = new List() + Orders = + [ + new Order() { - new Order() - { - Id = 1, - Name = "Barney", - Status = new Status() { Id = 0, Name = "Pending" }, - OrderItems = new List() + Id = 1, + Name = "Barney", + Status = new Status() { Id = 0, Name = "Pending" }, + OrderItems = + [ + new TShirtOrderItem() + { + Colour = 1, + Size = 7, + Status = new Status() { Id = 2, Name = "BackOrder" }, + TShirt = new TShirt() { Id = 3, Name = "SpiderMan" }, + }, + new BookOrderItem() { - new TShirtOrderItem() + Status = new Status() { Id = 4, Name = "Shipped" }, + Book = new Book() { - Colour = 1, - Size = 7, - Status = new Status() { Id = 2, Name = "BackOrder" }, - TShirt = new TShirt() { Id = 3, Name = "SpiderMan" } + Author = "Ben Riley", + Name = "My Life", + Pages = 300, }, - new BookOrderItem() - { - Status = new Status() { Id = 4, Name = "Shipped" }, - Book = new Book() - { - Author = "Ben Riley", - Name = "My Life", - Pages = 300 - } - } - } - } - } - }; - - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); - // book and tshirt added with AddAllFields above - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - - schemaProvider.UpdateType(Order => - { - Order.AddField("statusAsString", (o) => TShirtOrderItem.FormatTShirtPropertiesAsString(o.Size, o.Colour), "Get the order status as a string"); - }); - - // Simulate a JSON request with System.Text.Json - var q = @"{ + }, + ], + }, + ], + }; + + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); + // book and tshirt added with AddAllFields above + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + + schemaProvider.UpdateType(Order => + { + Order.AddField("statusAsString", (o) => TShirtOrderItem.FormatTShirtPropertiesAsString(o.Size, o.Colour), "Get the order status as a string"); + }); + + // Simulate a JSON request with System.Text.Json + var q = @"{ ""query"": ""query Order($orderId: Int) { order(id: $orderId) { ...order @@ -366,82 +365,82 @@ ... on TShirtOrderItem { } }".Replace('\r', ' ').Replace('\n', ' '); - var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); + var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + var results = schemaProvider.ExecuteRequestWithContext(gql, context, null, null); - if (results.HasErrors()) - { - throw new Exception(results.Errors!.First().Message); - } - - //Uncomment these guys to have the test fail properly - Assert.False(results.HasErrors()); - var order = (dynamic)results.Data!["order"]!; + if (results.HasErrors()) + { + throw new Exception(results.Errors!.First().Message); } - public class TestService + //Uncomment these guys to have the test fail properly + Assert.False(results.HasErrors()); + var order = (dynamic)results.Data!["order"]!; + } + + public class TestService + { + public string FormatTShirtPropertiesAsString(int colour, int size) { - public string FormatTShirtPropertiesAsString(int colour, int size) - { - return $"colour: {colour} - size: {size}"; - } + return $"colour: {colour} - size: {size}"; + } - public string FormatTShirtPropertiesAsString(int colour, int size, int length) - { - return $"colour: {colour} - size: {size} - length: {length}"; - } + public string FormatTShirtPropertiesAsString(int colour, int size, int length) + { + return $"colour: {colour} - size: {size} - length: {length}"; } + } - [Fact] - public void InheritanceTestUsingResolveWithService() + [Fact] + public void InheritanceTestUsingResolve() + { + var context = new TestContext() { - var context = new TestContext() - { - Orders = new List() + Orders = + [ + new Order() { - new Order() - { - Id = 1, - Name = "Barney", - Status = new Status() { Id = 0, Name = "Pending" }, - OrderItems = new List() + Id = 1, + Name = "Barney", + Status = new Status() { Id = 0, Name = "Pending" }, + OrderItems = + [ + new TShirtOrderItem() { - new TShirtOrderItem() + Colour = 1, + Size = 7, + Status = new Status() { Id = 2, Name = "BackOrder" }, + TShirt = new TShirt() { Id = 3, Name = "SpiderMan" }, + }, + new BookOrderItem() + { + Status = new Status() { Id = 4, Name = "Shipped" }, + Book = new Book() { - Colour = 1, - Size = 7, - Status = new Status() { Id = 2, Name = "BackOrder" }, - TShirt = new TShirt() { Id = 3, Name = "SpiderMan" } + Author = "Ben Riley", + Name = "My Life", + Pages = 300, }, - new BookOrderItem() - { - Status = new Status() { Id = 4, Name = "Shipped" }, - Book = new Book() - { - Author = "Ben Riley", - Name = "My Life", - Pages = 300 - } - } - } - } - } - }; - - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); - // book and tshirt added with AddAllFields above - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - - schemaProvider.UpdateType(Order => - { - Order.AddField("statusAsString", "Get the order status as a string").ResolveWithService((o, srv) => srv.FormatTShirtPropertiesAsString(o.Colour, o.Size)); - }); - - // Simulate a JSON request with System.Text.Json - var q = @"{ + }, + ], + }, + ], + }; + + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); + // book and tshirt added with AddAllFields above + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + + schemaProvider.UpdateType(Order => + { + Order.AddField("statusAsString", "Get the order status as a string").Resolve((o, srv) => srv.FormatTShirtPropertiesAsString(o.Colour, o.Size)); + }); + + // Simulate a JSON request with System.Text.Json + var q = @"{ ""query"": ""query Order($orderId: Int) { order(id: $orderId) { ...order @@ -465,76 +464,76 @@ ... on TShirtOrderItem { } }".Replace('\r', ' ').Replace('\n', ' '); - var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - var testService = new TestService(); - var sc = new ServiceCollection(); - sc.AddSingleton(testService); - var results = schemaProvider.ExecuteRequestWithContext(gql, context, sc.BuildServiceProvider(), null); + var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + var testService = new TestService(); + var sc = new ServiceCollection(); + sc.AddSingleton(testService); + var results = schemaProvider.ExecuteRequestWithContext(gql, context, sc.BuildServiceProvider(), null); - if (results.HasErrors()) - { - throw new Exception(results.Errors!.First().Message); - } + if (results.HasErrors()) + { + throw new Exception(results.Errors!.First().Message); + } - //Uncomment these guys to have the test fail properly - Assert.False(results.HasErrors()); - var order = (dynamic)results.Data!["order"]!; + //Uncomment these guys to have the test fail properly + Assert.False(results.HasErrors()); + var order = (dynamic)results.Data!["order"]!; - Assert.Equal("colour: 1 - size: 7", order.orderItems[0].statusAsString); - } + Assert.Equal("colour: 1 - size: 7", order.orderItems[0].statusAsString); + } - [Fact] - public void InheritanceTestUsingResolveWithServiceUsingArgs() + [Fact] + public void InheritanceTestUsingResolveUsingArgs() + { + var context = new TestContext() { - var context = new TestContext() - { - Orders = - [ - new Order() - { - Id = 1, - Name = "Barney", - Status = new Status() { Id = 0, Name = "Pending" }, - OrderItems = - [ - new TShirtOrderItem() + Orders = + [ + new Order() + { + Id = 1, + Name = "Barney", + Status = new Status() { Id = 0, Name = "Pending" }, + OrderItems = + [ + new TShirtOrderItem() + { + Colour = 1, + Size = 7, + Status = new Status() { Id = 2, Name = "BackOrder" }, + TShirt = new TShirt() { Id = 3, Name = "SpiderMan" }, + }, + new BookOrderItem() + { + Status = new Status() { Id = 4, Name = "Shipped" }, + Book = new Book() { - Colour = 1, - Size = 7, - Status = new Status() { Id = 2, Name = "BackOrder" }, - TShirt = new TShirt() { Id = 3, Name = "SpiderMan" } + Author = "Ben Riley", + Name = "My Life", + Pages = 300, }, - new BookOrderItem() - { - Status = new Status() { Id = 4, Name = "Shipped" }, - Book = new Book() - { - Author = "Ben Riley", - Name = "My Life", - Pages = 300 - } - } - ] - } - ] - }; - - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); - // book and tshirt added with AddAllFields above - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); - - schemaProvider.UpdateType(Order => - { - Order - .AddField("statusAsString", new { Length = 0 }, "Get the order status as a string") - .ResolveWithService((o, args, srv) => srv.FormatTShirtPropertiesAsString(o.Colour, o.Size, args.Length)); - }); - - // Simulate a JSON request with System.Text.Json - var q = @"{ + }, + ], + }, + ], + }; + + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddType("BookOrderItem").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("TShirtOrderItem").ImplementAllBaseTypes().AddAllFields(); + // book and tshirt added with AddAllFields above + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + schemaProvider.UpdateType(type => type.ImplementAllBaseTypes()); + + schemaProvider.UpdateType(Order => + { + Order + .AddField("statusAsString", new { Length = 0 }, "Get the order status as a string") + .Resolve((o, args, srv) => srv.FormatTShirtPropertiesAsString(o.Colour, o.Size, args.Length)); + }); + + // Simulate a JSON request with System.Text.Json + var q = @"{ ""query"": ""query Order($orderId: Int) { order(id: $orderId) { ...order @@ -558,21 +557,20 @@ ... on TShirtOrderItem { } }".Replace('\r', ' ').Replace('\n', ' '); - var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - var sc = new ServiceCollection(); - sc.AddSingleton(new TestService()); - var results = schemaProvider.ExecuteRequestWithContext(gql, context, sc.BuildServiceProvider(), null); + var gql = JsonSerializer.Deserialize(q, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + var sc = new ServiceCollection(); + sc.AddSingleton(new TestService()); + var results = schemaProvider.ExecuteRequestWithContext(gql, context, sc.BuildServiceProvider(), null); - if (results.HasErrors()) - { - throw new Exception(results.Errors!.First().Message); - } + if (results.HasErrors()) + { + throw new Exception(results.Errors!.First().Message); + } - //Uncomment these guys to have the test fail properly - Assert.False(results.HasErrors()); - var order = (dynamic)results.Data!["order"]!; + //Uncomment these guys to have the test fail properly + Assert.False(results.HasErrors()); + var order = (dynamic)results.Data!["order"]!; - Assert.Equal("colour: 1 - size: 7 - length: 2", order.orderItems[0].statusAsString); - } + Assert.Equal("colour: 1 - size: 7 - length: 2", order.orderItems[0].statusAsString); } } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceTests.cs index ff017a43..83bdbb6c 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/InheritanceTests.cs @@ -115,7 +115,7 @@ ...on Dog { { Id = 1, Name = "steve", - HasBone = true + HasBone = true, } ); context.Animals.Add( @@ -123,7 +123,7 @@ ...on Dog { { Id = 2, Name = "george", - Lives = 9 + Lives = 9, } ); @@ -165,7 +165,7 @@ ...on Dog { { Id = 9, Name = "steve", - HasBone = true + HasBone = true, } ); context.Animals.Add(new Cat() { Name = "george", Lives = 9 }); @@ -206,7 +206,7 @@ ...on Dog { { Id = 9, Name = "steve", - HasBone = true + HasBone = true, } ); context.Animals.Add( @@ -214,7 +214,7 @@ ...on Dog { { Id = 2, Name = "george", - Lives = 9 + Lives = 9, } ); @@ -258,7 +258,7 @@ ...on Dog { { Id = 9, Name = "steve", - HasBone = true + HasBone = true, } ); context.Animals.Add( @@ -266,7 +266,7 @@ ...on Dog { { Id = 2, Name = "george", - Lives = 9 + Lives = 9, } ); @@ -305,7 +305,7 @@ ... on Dog { { Id = 1, Name = "steve", - HasBone = true + HasBone = true, } ); context.Animals.Add(new Cat() { Name = "george", Lives = 9 }); @@ -345,7 +345,7 @@ fragment animalFragment on Animal { { Id = 9, Name = "steve", - HasBone = true + HasBone = true, } ); @@ -380,7 +380,7 @@ fragment dogFragment on Dog { { Id = 9, Name = "steve", - HasBone = true + HasBone = true, } ); @@ -424,9 +424,9 @@ fragment frag on Dog { { Id = 9, Name = "steve", - HasBone = true - } - } + HasBone = true, + }, + }, } ); var serviceCollection = new ServiceCollection(); diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/InputTypeTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/InputTypeTests.cs index 4f14d9c0..fb2a70b8 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/InputTypeTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/InputTypeTests.cs @@ -24,7 +24,7 @@ public void SupportsEnumInInputType_Introspection() name } } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -49,7 +49,7 @@ public void SupportsEnumInInputTypeAsList_Introspection() name } } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -83,7 +83,7 @@ public void SupportsEnumInInputTypeAsListInMutation_Introspection() name } } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -117,7 +117,7 @@ public void SupportsEnumInInputTypeAsListInMutationArgs_Introspection() name } } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -221,7 +221,7 @@ public void SupportsQueryTypeAsInputTypeIntrospection() type { name ofType { kind name } } } } - }" + }", }; var result = schema.ExecuteRequestWithContext(gql, new TestDataContext(), null, null); Assert.Null(result.Errors); @@ -270,7 +270,7 @@ public void SupportsQueryTypeAsInputTypeUsingReturns() [GraphQLArguments] internal class TestMutationArgs { - public List People { get; set; } = new(); + public List People { get; set; } = []; } internal class PeopleArgs diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/OptionsTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/OptionsTests.cs index a726f249..86d7b6e1 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/OptionsTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/OptionsTests.cs @@ -21,7 +21,7 @@ public void Test_ExecuteServiceFieldsSeparately_True_WithListNavigation() id } } - }" + }", }; var contextData = new OptionsContext().AddCustomerWithOrder("Lisa", 4); var res = schema.ExecuteRequestWithContext(gql, contextData, null, null); @@ -48,7 +48,7 @@ public void Test_ExecuteServiceFieldsSeparately_False_WithListNavigation() id } } - }" + }", }; var contextData = new OptionsContext().AddCustomerWithOrder("Lisa", 4); @@ -67,11 +67,11 @@ public void Test_ExecuteServiceFieldsSeparately_False_WithListNavigation() internal class OptionsContext { - public IList Customers { get; set; } = new List(); + public IList Customers { get; set; } = []; internal OptionsContext AddCustomerWithOrder(string customerName, int orderId) { - var customer = new Customer { Name = customerName, }; + var customer = new Customer { Name = customerName }; customer.Orders.Add(new Order { Id = orderId }); Customers.Add(customer); return this; @@ -81,7 +81,7 @@ internal OptionsContext AddCustomerWithOrder(string customerName, int orderId) internal class Customer { public string Name { get; set; } = string.Empty; - public List Orders { get; set; } = new List(); + public List Orders { get; set; } = []; } internal class Order diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/QueryTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/QueryTests.cs index a6ca9c28..9b530082 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/QueryTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/QueryTests.cs @@ -4,170 +4,170 @@ using EntityGraphQL.Schema; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +/// +/// Tests the extended (non-GraphQL - came first) LINQ style querying functionality +/// +public class QueryTests { - /// - /// Tests the extended (non-GraphQL - came first) LINQ style querying functionality - /// - public class QueryTests + [Fact] + public void CanParseSimpleQuery() { - [Fact] - public void CanParseSimpleQuery() - { - var objectSchemaProvider = SchemaBuilder.FromObject(); - var tree = new GraphQLCompiler(objectSchemaProvider).Compile( - @" + var objectSchemaProvider = SchemaBuilder.FromObject(); + var tree = new GraphQLCompiler(objectSchemaProvider).Compile( + @" { people { id name } }" - ); - Assert.Single(tree.Operations); - Assert.Single(tree.Operations.First().QueryFields); - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.NotNull(result.Data); - Assert.Single(result.Data); - var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); - // we only have the fields requested - Assert.Equal(2, person.GetType().GetFields().Length); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); - } + ); + Assert.Single(tree.Operations); + Assert.Single(tree.Operations.First().QueryFields); + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.NotNull(result.Data); + Assert.Single(result.Data); + var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); + // we only have the fields requested + Assert.Equal(2, person.GetType().GetFields().Length); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); + } - [Fact] - public void CanQueryAsyncField() - { - var schemaProvider = SchemaBuilder.FromObject(); - var result = schemaProvider.ExecuteRequestWithContext( - new QueryRequest - { - Query = - @"{ + [Fact] + public void CanQueryAsyncField() + { + var schemaProvider = SchemaBuilder.FromObject(); + var result = schemaProvider.ExecuteRequestWithContext( + new QueryRequest + { + Query = + @"{ firstUserId - }" - }, - new TestDataContext().FillWithTestData(), - null, - null - ); - Assert.Equal(100, (int?)result.Data!["firstUserId"]); - } + }", + }, + new TestDataContext().FillWithTestData(), + null, + null + ); + Assert.Equal(100, (int?)result.Data!["firstUserId"]); + } - [Fact] - public void CanQueryExtendedFields() - { - var objectSchemaProvider = SchemaBuilder.FromObject(); - objectSchemaProvider.Type().AddField("thing", p => p.Id + " - " + p.Name, "A weird field I want"); - var tree = new GraphQLCompiler(objectSchemaProvider).Compile( - @" + [Fact] + public void CanQueryExtendedFields() + { + var objectSchemaProvider = SchemaBuilder.FromObject(); + objectSchemaProvider.Type().AddField("thing", p => p.Id + " - " + p.Name, "A weird field I want"); + var tree = new GraphQLCompiler(objectSchemaProvider).Compile( + @" { people { id thing } }" - ); - Assert.Single(tree.Operations); - Assert.Single(tree.Operations.First().QueryFields); - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.NotNull(result.Data); - Assert.Single(result.Data); - object person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); - // we only have the fields requested - Assert.Equal(2, person.GetType().GetFields().Length); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "thing"); - } + ); + Assert.Single(tree.Operations); + Assert.Single(tree.Operations.First().QueryFields); + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.NotNull(result.Data); + Assert.Single(result.Data); + object person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); + // we only have the fields requested + Assert.Equal(2, person.GetType().GetFields().Length); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "thing"); + } - [Fact] - public void CanRemoveFields() + [Fact] + public void CanRemoveFields() + { + var schema = SchemaBuilder.FromObject(); + schema.Type().RemoveField(p => p.Id); + var ex = Assert.Throws(() => { - var schema = SchemaBuilder.FromObject(); - schema.Type().RemoveField(p => p.Id); - var ex = Assert.Throws(() => - { - var tree = new GraphQLCompiler(schema).Compile( - @" + var tree = new GraphQLCompiler(schema).Compile( + @" { people { id } }" - ); - }); - Assert.Equal("Field 'id' not found on type 'Person'", ex.Message); - } + ); + }); + Assert.Equal("Field 'id' not found on type 'Person'", ex.Message); + } - [Fact] - public void WildcardQueriesHonorRemovedFieldsOnObject() - { - // empty schema - var schema = SchemaBuilder.Create(); - schema.AddType("Person").AddField("name", p => p.Name, "Person's name"); - schema.Query().AddField("person", new { id = ArgumentHelper.Required() }, (p, args) => p.People.FirstOrDefault(p => p.Id == args.id), "Person"); - var ex = Assert.Throws( - () => - new GraphQLCompiler(schema).Compile( - @" + [Fact] + public void WildcardQueriesHonorRemovedFieldsOnObject() + { + // empty schema + var schema = SchemaBuilder.Create(); + schema.AddType("Person").AddField("name", p => p.Name, "Person's name"); + schema.Query().AddField("person", new { id = ArgumentHelper.Required() }, (p, args) => p.People.FirstOrDefault(p => p.Id == args.id), "Person"); + var ex = Assert.Throws( + () => + new GraphQLCompiler(schema).Compile( + @" { person(id: 1) }" - ) - ); - Assert.Equal("Field 'person' requires a selection set defining the fields you would like to select.", ex.Message); - } + ) + ); + Assert.Equal("Field 'person' requires a selection set defining the fields you would like to select.", ex.Message); + } - [Fact] - public void CanParseMultipleEntityQuery() - { - var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void CanParseMultipleEntityQuery() + { + var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { people { id name } users { id } }" - ); - - Assert.Single(tree.Operations); - Assert.Equal(2, tree.Operations.First().QueryFields.Count); - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); - var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); - // we only have the fields requested - Assert.Equal(2, person.GetType().GetFields().Length); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); - - Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["users"]!)); - var user = Enumerable.ElementAt((dynamic)result.Data!["users"]!, 0); - // we only have the fields requested - Assert.Single(user.GetType().GetFields()); - Assert.NotNull(user.GetType().GetField("id")); - } + ); + + Assert.Single(tree.Operations); + Assert.Equal(2, tree.Operations.First().QueryFields.Count); + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); + var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); + // we only have the fields requested + Assert.Equal(2, person.GetType().GetFields().Length); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); + + Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["users"]!)); + var user = Enumerable.ElementAt((dynamic)result.Data!["users"]!, 0); + // we only have the fields requested + Assert.Single(user.GetType().GetFields()); + Assert.NotNull(user.GetType().GetField("id")); + } - [Fact] - public void CanParseQueryWithRelation() - { - var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void CanParseQueryWithRelation() + { + var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { people { id name user { field1 } } }" - ); - // People.Select(p => new { Id = p.Id, Name = p.Name, User = new { Field1 = p.User.Field1 }) - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); - var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); - // we only have the fields requested - Assert.Equal(3, person.GetType().GetFields().Length); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); - // make sure we sub-select correctly to make the requested object graph - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "user"); - var user = person.user; - Assert.Single(user.GetType().GetFields()); - Assert.NotNull(user.GetType().GetField("field1")); - } + ); + // People.Select(p => new { Id = p.Id, Name = p.Name, User = new { Field1 = p.User.Field1 }) + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); + var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); + // we only have the fields requested + Assert.Equal(3, person.GetType().GetFields().Length); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); + // make sure we sub-select correctly to make the requested object graph + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "user"); + var user = person.user; + Assert.Single(user.GetType().GetFields()); + Assert.NotNull(user.GetType().GetField("field1")); + } - [Fact] - public void CanParseQueryWithRelationDeep() - { - var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void CanParseQueryWithRelationDeep() + { + var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { people { id name @@ -177,34 +177,34 @@ id name } } }" - ); - // People.Select(p => new { Id = p.Id, Name = p.Name, User = new { Field1 = p.User.Field1, NestedRelation = new { Id = p.User.NestedRelation.Id, Name = p.User.NestedRelation.Name } }) - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); - var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); - // we only have the fields requested - Assert.Equal(3, person.GetType().GetFields().Length); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); - // make sure we sub-select correctly to make the requested object graph - Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "user"); - var user = person.user; - Assert.Equal(2, user.GetType().GetFields().Length); - Assert.Contains((IEnumerable)user.GetType().GetFields(), f => f.Name == "field1"); - Assert.Contains((IEnumerable)user.GetType().GetFields(), f => f.Name == "nestedRelation"); - var nested = person.user.nestedRelation; - Assert.Equal(2, nested.GetType().GetFields().Length); - Assert.Contains((IEnumerable)nested.GetType().GetFields(), f => f.Name == "id"); - Assert.Contains((IEnumerable)nested.GetType().GetFields(), f => f.Name == "name"); - } + ); + // People.Select(p => new { Id = p.Id, Name = p.Name, User = new { Field1 = p.User.Field1, NestedRelation = new { Id = p.User.NestedRelation.Id, Name = p.User.NestedRelation.Name } }) + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.Equal(1, Enumerable.Count((dynamic)result.Data!["people"]!)); + var person = Enumerable.ElementAt((dynamic)result.Data["people"]!, 0); + // we only have the fields requested + Assert.Equal(3, person.GetType().GetFields().Length); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "name"); + // make sure we sub-select correctly to make the requested object graph + Assert.Contains((IEnumerable)person.GetType().GetFields(), f => f.Name == "user"); + var user = person.user; + Assert.Equal(2, user.GetType().GetFields().Length); + Assert.Contains((IEnumerable)user.GetType().GetFields(), f => f.Name == "field1"); + Assert.Contains((IEnumerable)user.GetType().GetFields(), f => f.Name == "nestedRelation"); + var nested = person.user.nestedRelation; + Assert.Equal(2, nested.GetType().GetFields().Length); + Assert.Contains((IEnumerable)nested.GetType().GetFields(), f => f.Name == "id"); + Assert.Contains((IEnumerable)nested.GetType().GetFields(), f => f.Name == "name"); + } - [Fact] - public void FailsNonExistingField() - { - var ex = Assert.Throws( - () => - new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void FailsNonExistingField() + { + var ex = Assert.Throws( + () => + new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { people { id projects { @@ -213,18 +213,18 @@ public void FailsNonExistingField() } } }" - ) - ); - Assert.Equal("Field 'blahs' not found on type 'Project'", ex.Message); - } + ) + ); + Assert.Equal("Field 'blahs' not found on type 'Project'", ex.Message); + } - [Fact] - public void FailsNonExistingField2() - { - var ex = Assert.Throws( - () => - new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void FailsNonExistingField2() + { + var ex = Assert.Throws( + () => + new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { people { id projects { @@ -232,296 +232,295 @@ public void FailsNonExistingField2() } } }" - ) - ); - Assert.Equal("Field 'name3' not found on type 'Project'", ex.Message); - } + ) + ); + Assert.Equal("Field 'name3' not found on type 'Project'", ex.Message); + } - [Fact] - public void TestAlias() - { - var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @" + [Fact] + public void TestAlias() + { + var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @" { projects { n: name } }" - ); + ); - Assert.Single(tree.Operations.First().QueryFields); - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.Equal("Project 3", ((dynamic)result.Data!["projects"]!)[0].n); - } + Assert.Single(tree.Operations.First().QueryFields); + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.Equal("Project 3", ((dynamic)result.Data!["projects"]!)[0].n); + } - [Fact] - public void TestAliasDeep() - { - var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( - @"{ + [Fact] + public void TestAliasDeep() + { + var tree = new GraphQLCompiler(SchemaBuilder.FromObject()).Compile( + @"{ people { id projects { n: name } } }" - ); + ); - Assert.Single(tree.Operations.First().QueryFields); - var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); - Assert.Equal("Project 3", Enumerable.First(Enumerable.First((dynamic)result.Data!["people"]!).projects).n); - } + Assert.Single(tree.Operations.First().QueryFields); + var result = tree.ExecuteQuery(new TestDataContext().FillWithTestData(), null, null); + Assert.Equal("Project 3", Enumerable.First(Enumerable.First((dynamic)result.Data!["people"]!).projects).n); + } - [Fact] - public void EnumTest() + [Fact] + public void EnumTest() + { + var schemaProvider = SchemaBuilder.FromObject(); + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ people { gender } } ", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Null(results.Errors); - } + var testSchema = new TestDataContext().FillWithTestData(); + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Null(results.Errors); + } - [Fact] - public void DateScalarsTest() + [Fact] + public void DateScalarsTest() + { + var schemaProvider = SchemaBuilder.FromObject(); + var gql = new QueryRequest { - var schemaProvider = SchemaBuilder.FromObject(); - var gql = new QueryRequest - { - Query = - @"{ + Query = + @"{ projects { created updated } } ", - }; + }; - var testSchema = new TestDataContext().FillWithTestData(); - var result = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Null(result.Errors); - Assert.NotNull(((dynamic)result.Data!["projects"]!)[0].created); - Assert.NotNull(((dynamic)result.Data!["projects"]!)[0].updated); - } + var testSchema = new TestDataContext().FillWithTestData(); + var result = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Null(result.Errors); + Assert.NotNull(((dynamic)result.Data!["projects"]!)[0].created); + Assert.NotNull(((dynamic)result.Data!["projects"]!)[0].updated); + } - [Fact] - public void TestTopLevelScalar() - { - var schemaProvider = SchemaBuilder.FromObject(); - var gql = new GraphQLCompiler(schemaProvider).Compile( - @" + [Fact] + public void TestTopLevelScalar() + { + var schemaProvider = SchemaBuilder.FromObject(); + var gql = new GraphQLCompiler(schemaProvider).Compile( + @" query { totalPeople }" - ); + ); - var context = new TestDataContext(); - context.People.Clear(); - for (int i = 0; i < 15; i++) - { - context.People.Add(new Person()); - } - var qr = gql.ExecuteQuery(context, null, null); - dynamic totalPeople = qr.Data!["totalPeople"]!; - // we only have the fields requested - Assert.Equal(15, totalPeople); + var context = new TestDataContext(); + context.People.Clear(); + for (int i = 0; i < 15; i++) + { + context.People.Add(new Person()); } + var qr = gql.ExecuteQuery(context, null, null); + dynamic totalPeople = qr.Data!["totalPeople"]!; + // we only have the fields requested + Assert.Equal(15, totalPeople); + } - [Fact] - public void TestDeepQuery() - { - var schemaProvider = SchemaBuilder.FromObject(); - var gql = new QueryRequest { Query = @"query deep { levelOnes { levelTwo { level3 { name }} }}", }; + [Fact] + public void TestDeepQuery() + { + var schemaProvider = SchemaBuilder.FromObject(); + var gql = new QueryRequest { Query = @"query deep { levelOnes { levelTwo { level3 { name }} }}" }; - var testSchema = new DeepContext(); + var testSchema = new DeepContext(); - var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Null(results.Errors); - } + var results = schemaProvider.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Null(results.Errors); + } - [Fact] - public void TestNoArgumentsOnEnum() - { - var schema = SchemaBuilder.FromObject(); + [Fact] + public void TestNoArgumentsOnEnum() + { + var schema = SchemaBuilder.FromObject(); - var ex = Assert.Throws(() => schema.Type().AddField("invalid", new { id = (int?)null }, (ctx, args) => 8, "Invalid field")); - Assert.Equal("Field 'invalid' on type 'Gender' has arguments but is a GraphQL 'Enum' type and can not have arguments.", ex.Message); - } + var ex = Assert.Throws(() => schema.Type().AddField("invalid", new { id = (int?)null }, (ctx, args) => 8, "Invalid field")); + Assert.Equal("Field 'invalid' on type 'Gender' has arguments but is a GraphQL 'Enum' type and can not have arguments.", ex.Message); + } - [Fact] - public void TestNoFieldsOnScalar() - { - var schema = SchemaBuilder.FromObject(); + [Fact] + public void TestNoFieldsOnScalar() + { + var schema = SchemaBuilder.FromObject(); - var ex = Assert.Throws(() => schema.Type().AddField("invalid", (ctx) => 8, "Invalid field")); - Assert.Equal("Cannot add field 'invalid' to type 'String', as 'String' is a scalar type and can not have fields.", ex.Message); - } + var ex = Assert.Throws(() => schema.Type().AddField("invalid", (ctx) => 8, "Invalid field")); + Assert.Equal("Cannot add field 'invalid' to type 'String', as 'String' is a scalar type and can not have fields.", ex.Message); + } - /// - /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/229 - /// - [Fact] - public void TestResolveWithServiceAndNavigationProp() - { - var schema = SchemaBuilder.FromObject(); + /// + /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/229 + /// + [Fact] + public void TestResolveWithServiceAndNavigationProp() + { + var schema = SchemaBuilder.FromObject(); - schema.UpdateType(x => - { - x.AddField("sum", "").Resolve(y => y.Tasks.Count()); - x.AddField("test", "").Resolve((y, db) => y.Updated!.Value.AddDays(3)); - }); + schema.UpdateType(x => + { + x.AddField("sum", "").Resolve(y => y.Tasks.Count()); + x.AddField("test", "").Resolve((y, db) => y.Updated!.Value.AddDays(3)); + }); - var testSchema = new TestDataContext() - { - Projects = new List() + var testSchema = new TestDataContext() + { + Projects = + [ + new Project() { - new Project() - { - Updated = new System.DateTime(2001, 1, 1), - Tasks = new List() { new Task(), new Task(), new Task(), } - } - } - }; + Updated = new System.DateTime(2001, 1, 1), + Tasks = new List() { new Task(), new Task(), new Task() }, + }, + ], + }; - var gql = new QueryRequest { Query = @"query deep { projects { sum }}", }; + var gql = new QueryRequest { Query = @"query deep { projects { sum }}" }; - var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Equal(3, ((dynamic)results.Data!)["projects"][0].sum); + var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Equal(3, ((dynamic)results.Data!)["projects"][0].sum); - gql = new QueryRequest { Query = @"query deep { projects { test }}", }; + gql = new QueryRequest { Query = @"query deep { projects { test }}" }; - results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Equal(new System.DateTime(2001, 1, 4), ((dynamic)results.Data!)["projects"][0].test); + results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Equal(new System.DateTime(2001, 1, 4), ((dynamic)results.Data!)["projects"][0].test); - gql = new QueryRequest - { - Query = - @"query deep { projects { + gql = new QueryRequest + { + Query = + @"query deep { projects { test sum }}", - }; + }; - results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Equal(3, ((dynamic)results.Data!)["projects"][0].sum); - Assert.Equal(new System.DateTime(2001, 1, 4), ((dynamic)results.Data!)["projects"][0].test); - } + Assert.Equal(3, ((dynamic)results.Data!)["projects"][0].sum); + Assert.Equal(new System.DateTime(2001, 1, 4), ((dynamic)results.Data!)["projects"][0].test); + } - [Fact] - public void TestResolveWithServiceEvaluatesOnce() + [Fact] + public void TestResolveWithServiceEvaluatesOnce() + { + var schema = SchemaBuilder.FromObject(); + + int v = 0; + var func = () => { - var schema = SchemaBuilder.FromObject(); + return v++; + }; + schema.Query().AddField("test", "").Resolve((y, db) => func()); - int v = 0; - var func = () => - { - return v++; - }; - schema.Query().AddField("test", "").Resolve((y, db) => func()); + var testSchema = new TestDataContext() { Projects = [new Project() { }] }; - var testSchema = new TestDataContext() { Projects = new List() { new Project() { } } }; + var gql = new QueryRequest { Query = @"query deep { test }" }; - var gql = new QueryRequest { Query = @"query deep { test }", }; + var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Equal(1, v); + } - var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Equal(1, v); - } + [Fact] + public void TestResolveWithServiceEvaluatesOnceForEnumerables() + { + var schema = SchemaBuilder.FromObject(); - [Fact] - public void TestResolveWithServiceEvaluatesOnceForEnumerables() + int v = 0; + var func = () => { - var schema = SchemaBuilder.FromObject(); + v++; + return new List(); + }; + schema.Query().AddField("test", "").Resolve((y, db) => func()); - int v = 0; - var func = () => - { - v++; - return new List(); - }; - schema.Query().AddField("test", "").Resolve((y, db) => func()); - - var testSchema = new TestDataContext() { Projects = new List() { new Project() { } } }; + var testSchema = new TestDataContext() { Projects = [new Project() { }] }; - var gql = new QueryRequest { Query = @"query deep { test }", }; + var gql = new QueryRequest { Query = @"query deep { test }" }; - var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Equal(1, v); - } + var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Equal(1, v); + } - public class UserDbContextNullable - { - public List? UserIds { get; set; } = null; - } + public class UserDbContextNullable + { + public List? UserIds { get; set; } = null; + } - /// - /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/221 - /// - [Fact] - public void TestForExtractingDataFromICollectionListEtcReturnsNull_221() - { - var schema = SchemaBuilder.FromObject(); + /// + /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/221 + /// + [Fact] + public void TestForExtractingDataFromICollectionListEtcReturnsNull_221() + { + var schema = SchemaBuilder.FromObject(); - var testSchema = new UserDbContextNullable() { }; + var testSchema = new UserDbContextNullable() { }; - var gql = new QueryRequest { Query = @"query deep { userIds }", }; + var gql = new QueryRequest { Query = @"query deep { userIds }" }; - var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.Null(((dynamic)results.Data!)["userIds"]); - } + var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.Null(((dynamic)results.Data!)["userIds"]); + } - public class UserDbContextNonNullable - { - [GraphQLNotNull] - public List UserIds { get; set; } = []; - } + public class UserDbContextNonNullable + { + [GraphQLNotNull] + public List UserIds { get; set; } = []; + } - /// - /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/221 - /// - [Fact] - public void TestForExtractingDataFromICollectionListEtcReturnsEmptyList_221() - { - var schema = SchemaBuilder.FromObject(); + /// + /// from issue https://github.com/EntityGraphQL/EntityGraphQL/issues/221 + /// + [Fact] + public void TestForExtractingDataFromICollectionListEtcReturnsEmptyList_221() + { + var schema = SchemaBuilder.FromObject(); - var testSchema = new UserDbContextNonNullable() { }; + var testSchema = new UserDbContextNonNullable() { }; - var gql = new QueryRequest { Query = @"query deep { userIds }", }; + var gql = new QueryRequest { Query = @"query deep { userIds }" }; - var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); - Assert.NotNull(((dynamic)results.Data!)["userIds"]); - Assert.Empty(((dynamic)results.Data!)["userIds"]); - } + var results = schema.ExecuteRequestWithContext(gql, testSchema, null, null); + Assert.NotNull(((dynamic)results.Data!)["userIds"]); + Assert.Empty(((dynamic)results.Data!)["userIds"]); } +} - public class DeepContext - { - public IList LevelOnes { get; set; } = new List(); +public class DeepContext +{ + public IList LevelOnes { get; set; } = []; - public class LevelOne - { - public LevelTwo? LevelTwo { get; set; } - } + public class LevelOne + { + public LevelTwo? LevelTwo { get; set; } + } - public class LevelTwo - { - public ICollection Level3 { get; set; } = new List(); - } + public class LevelTwo + { + public ICollection Level3 { get; set; } = []; + } - public class LevelThree - { - public string Name { get; set; } = string.Empty; - } + public class LevelThree + { + public string Name { get; set; } = string.Empty; } } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldBulkTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldBulkTests.cs index b970c0e6..695b9704 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldBulkTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldBulkTests.cs @@ -28,26 +28,26 @@ public void TestServicesBulkResolverFullObject() projects { name createdBy { id field2 } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, - }, + ], }; var serviceCollection = new ServiceCollection(); UserService userService = new(); @@ -85,7 +85,7 @@ public void TestServicesBulkResolverWithinToSingle() project(id: 1) { name createdBy { id field2 } } - }" + }", }; var context = new TestDataContext @@ -96,13 +96,13 @@ name createdBy { id field2 } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -130,7 +130,7 @@ public void TestServicesBulkResolverFullObjectWithOrderBy() .Query() .ReplaceField( "projects", - new { like = (string?)null, }, + new { like = (string?)null }, (ctx, args) => ctx.QueryableProjects.WhereWhen(f => f.Name.ToLower().Contains(args.like!.ToLower()), !string.IsNullOrEmpty(args.like)).OrderBy(f => f.Name), "Get projects" ); @@ -149,7 +149,7 @@ public void TestServicesBulkResolverFullObjectWithOrderBy() id name createdBy { id field2 } } - }" + }", }; var context = new TestDataContext @@ -160,13 +160,13 @@ name createdBy { id field2 } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -207,7 +207,7 @@ public void TestServicesBulkResolverScalar() projects { name createdByName } - }" + }", }; var context = new TestDataContext @@ -218,13 +218,13 @@ name createdByName { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -263,7 +263,7 @@ public void TestServicesBulkResolverList() projects { name assignedUsers { name } } - }" + }", }; var context = new TestDataContext @@ -274,13 +274,13 @@ name assignedUsers { name } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -319,7 +319,7 @@ public void TestServicesBulkResolverWithArg() projects { name assignedUser(id: 1) { name } } - }" + }", }; var context = new TestDataContext @@ -330,13 +330,13 @@ name assignedUser(id: 1) { name } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -375,7 +375,7 @@ public void TestServicesBulkResolverListWithArgs() projects { name assignedUsers(name: ""1"") { name } } - }" + }", }; var context = new TestDataContext @@ -386,13 +386,13 @@ name assignedUsers(name: ""1"") { name } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -433,7 +433,7 @@ public void TestServicesBulkResolverFullObjectDeep() name createdBy { id field2 } } } - }" + }", }; var context = new TestDataContext @@ -445,16 +445,16 @@ name createdBy { id field2 } Id = 1, CreatedBy = 1, Name = "Project 1", - Tasks = [new() { Id = 1, Name = "Task 1" }, new() { Id = 2, Name = "Task 2" },] + Tasks = [new() { Id = 1, Name = "Task 1" }, new() { Id = 2, Name = "Task 2" }], }, new Project { Id = 2, CreatedBy = 2, Name = "Project 2", - Tasks = [new() { Id = 3, Name = "Task 3" }, new() { Id = 4, Name = "Task 4" },] - } - ] + Tasks = [new() { Id = 3, Name = "Task 3" }, new() { Id = 4, Name = "Task 4" }], + }, + ], }; var serviceCollection = new ServiceCollection(); UserService userService = new(); @@ -499,7 +499,7 @@ public void TestServicesBulkResolverFullObjectDeep_Object_List_Object_List() } } } - }" + }", }; var context = new TestDataContext @@ -517,15 +517,15 @@ public void TestServicesBulkResolverFullObjectDeep_Object_List_Object_List() { Id = 1, Name = "Task 1", - Assignee = new Person { Id = 1 } + Assignee = new Person { Id = 1 }, }, new() { Id = 2, Name = "Task 2", - Assignee = new Person { Id = 2 } + Assignee = new Person { Id = 2 }, }, - ] + ], }, new Project { @@ -538,17 +538,17 @@ public void TestServicesBulkResolverFullObjectDeep_Object_List_Object_List() { Id = 3, Name = "Task 3", - Assignee = new Person { Id = 3 } + Assignee = new Person { Id = 3 }, }, new() { Id = 4, Name = "Task 4", - Assignee = new Person { Id = 1 } + Assignee = new Person { Id = 1 }, }, - ] - } - ] + ], + }, + ], }; // set up fake data with no null paths (normally this is done with EF and the null paths are handled by the compiler) context.Projects[0].Tasks.ElementAt(0).Assignee!.Projects = context.Projects; @@ -598,7 +598,7 @@ public void TestServicesBulkResolverFullObjectDeep_Object_List_Object_List_Handl } } } - }" + }", }; var context = new TestDataContext @@ -610,16 +610,16 @@ public void TestServicesBulkResolverFullObjectDeep_Object_List_Object_List_Handl Id = 1, CreatedBy = 1, Name = "Project 1", - Tasks = [new() { Id = 1, Name = "Task 1" }, new() { Id = 2, Name = "Task 2" },] + Tasks = [new() { Id = 1, Name = "Task 1" }, new() { Id = 2, Name = "Task 2" }], }, new() { Id = 2, CreatedBy = 1, Name = "Project 2", - Tasks = [new() { Id = 3, Name = "Task 3" }, new() { Id = 4, Name = "Task 4" },] - } - ] + Tasks = [new() { Id = 3, Name = "Task 3" }, new() { Id = 4, Name = "Task 4" }], + }, + ], }; // assignee is null in the data - bulk selector should handle this @@ -664,7 +664,7 @@ public void TestServicesBulkResolverFullObjectDeep_List_List_Object() } } } - }" + }", }; var context = new TestDataContext @@ -682,15 +682,15 @@ public void TestServicesBulkResolverFullObjectDeep_List_List_Object() { Id = 1, Name = "Task 1", - Assignee = new Person { Id = 1 } + Assignee = new Person { Id = 1 }, }, new() { Id = 2, Name = "Task 2", - Assignee = new Person { Id = 2 } + Assignee = new Person { Id = 2 }, }, - ] + ], }, new Project { @@ -703,17 +703,17 @@ public void TestServicesBulkResolverFullObjectDeep_List_List_Object() { Id = 3, Name = "Task 3", - Assignee = new Person { Id = 3 } + Assignee = new Person { Id = 3 }, }, new() { Id = 4, Name = "Task 4", - Assignee = new Person { Id = 1 } + Assignee = new Person { Id = 1 }, }, - ] - } - ] + ], + }, + ], }; // set up fake data with no null paths (normally this is done with EF and the null paths are handled by the compiler) @@ -763,7 +763,7 @@ name createdBy { id field2 } } } } - }" + }", }; var context = new TestDataContext @@ -774,13 +774,13 @@ name createdBy { id field2 } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; @@ -818,7 +818,7 @@ public void TestServicesBulkResolverFullObjectWithOffsetPaging() name createdBy { id field2 } } } - }" + }", }; var context = new TestDataContext @@ -829,13 +829,13 @@ name createdBy { id field2 } { Id = 1, CreatedBy = 1, - Name = "Project 1" + Name = "Project 1", }, new Project { Id = 2, CreatedBy = 2, - Name = "Project 2" + Name = "Project 2", }, ], }; diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldTests.cs index 1b2b1723..0dd50b35 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/ServiceFieldTests.cs @@ -27,7 +27,7 @@ public void TestServicesAtRoot() var gql = new QueryRequest { Query = @"{ projects { total items { id } } }" }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var serviceCollection = new ServiceCollection(); var pager = new EntityPager(); serviceCollection.AddSingleton(pager); @@ -52,7 +52,7 @@ public void TestServicesAtRoot2() var gql = new QueryRequest { Query = @"{ projects { total items { id } } }" }; - var context = new TestDataContext { Projects = new List() }; + var context = new TestDataContext { Projects = [] }; var serviceCollection = new ServiceCollection(); var pager = new EntityPager(); serviceCollection.AddSingleton(pager); @@ -76,11 +76,7 @@ public void TestServicesNonRoot() var gql = new QueryRequest { Query = @"{ people { projects { total items { id } } } }" }; - var context = new TestDataContext - { - Projects = new List(), - People = new List { new Person { Projects = new List() } } - }; + var context = new TestDataContext { Projects = [], People = [new Person { Projects = [] }] }; var serviceCollection = new ServiceCollection(); var pager = new EntityPager(); serviceCollection.AddSingleton(pager); @@ -112,10 +108,10 @@ public void TestServicesNonRootDeeper() Query = @"{ people { projects { config { type } - } } }" + } } }", }; - var context = new TestDataContext { People = new List { new Person { Projects = new List { new Project { Id = 4, } } } } }; + var context = new TestDataContext { People = [new Person { Projects = [new Project { Id = 4 }] }] }; var serviceCollection = new ServiceCollection(); var srv = new ConfigService(); serviceCollection.AddSingleton(srv); @@ -158,10 +154,10 @@ public void TestServicesNonRootDeeperWithFullEntitySelect() Query = @"{ people { projects { config { type } - } } }" + } } }", }; - var context = new TestDataContext { People = new List { new Person { Projects = new List { new Project { Id = 4, } } } } }; + var context = new TestDataContext { People = [new Person { Projects = [new Project { Id = 4 }] }] }; var serviceCollection = new ServiceCollection(); var srv = new ConfigService(); serviceCollection.AddSingleton(srv); @@ -190,14 +186,7 @@ public void TestServicesNonRootWithOtherFields() var gql = new QueryRequest { Query = @"{ people { projects { total items { id } } name } }" }; - var context = new TestDataContext - { - Projects = new List(), - People = new List - { - new Person { Name = "Alyssa", Projects = new List() } - } - }; + var context = new TestDataContext { Projects = [], People = [new Person { Name = "Alyssa", Projects = [] }] }; var serviceCollection = new ServiceCollection(); EntityPager pager = new(); serviceCollection.AddSingleton(pager); @@ -227,16 +216,16 @@ public void TestServicesNonRootWithOtherFieldsAndRelation() var context = new TestDataContext { - Projects = new List(), - People = new List - { + Projects = [], + People = + [ new Person { Name = "Alyssa", - Projects = new List(), - Manager = new Person { Name = "Jennifer" } - } - } + Projects = [], + Manager = new Person { Name = "Jennifer" }, + }, + ], }; var serviceCollection = new ServiceCollection(); EntityPager pager = new(); @@ -270,16 +259,16 @@ public void TestServicesNonRootWithOtherFieldsAndRelation2() var context = new TestDataContext { - Projects = new List(), - People = new List - { + Projects = [], + People = + [ new Person { Name = "Alyssa", - Projects = new List(), - Manager = new Person { Name = "Jennifer" } - } - } + Projects = [], + Manager = new Person { Name = "Jennifer" }, + }, + ], }; var serviceCollection = new ServiceCollection(); var ager = new AgeService(); @@ -312,21 +301,21 @@ public void TestServicesNonRootWithOtherFieldsAndRelationNotEnumerable() var gql = new QueryRequest { // the service field (age) is on a 1-1 relation not 1-Many so we don't build a .Select - Query = @"{ people { manager { name age } } }" + Query = @"{ people { manager { name age } } }", }; var context = new TestDataContext { - Projects = new List(), - People = new List - { + Projects = [], + People = + [ new Person { Name = "Alyssa", - Projects = new List(), - Manager = new Person { Name = "Jennifer" } - } - } + Projects = [], + Manager = new Person { Name = "Jennifer" }, + }, + ], }; var serviceCollection = new ServiceCollection(); var ager = new AgeService(); @@ -355,7 +344,7 @@ public void TestServicesReconnectToSchemaContext() var gql = new QueryRequest { Query = @"{ user { id projects { id } } }" }; - var context = new TestDataContext { Projects = [], People = [new Person { Projects = [] }], }; + var context = new TestDataContext { Projects = [], People = [new Person { Projects = [] }] }; var serviceCollection = new ServiceCollection(); UserService userService = new(); serviceCollection.AddSingleton(userService); @@ -382,7 +371,7 @@ public void TestServicesReconnectToSchemaContextListOf() var gql = new QueryRequest { Query = @"{ users { id projects { id } } }" }; - var context = new TestDataContext { Projects = [], People = [new() { Projects = [] }], }; + var context = new TestDataContext { Projects = [], People = [new() { Projects = [] }] }; var serviceCollection = new ServiceCollection(); UserService userService = new(); serviceCollection.AddSingleton(userService); @@ -409,11 +398,7 @@ public void TestServicesReconnectToSchemaContextFromObject() var gql = new QueryRequest { Query = @"{ user { id project { __typename id } } }" }; - var context = new TestDataContext - { - Projects = new List(), - People = new List { new Person { Projects = new List() } }, - }; + var context = new TestDataContext { Projects = [], People = [new Person { Projects = [] }] }; var serviceCollection = new ServiceCollection(); UserService userService = new(); serviceCollection.AddSingleton(userService); @@ -450,21 +435,21 @@ public void TestDbContextToServiceBackToDbContext() projects { id name } } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 1, Name = "Project 1", - CreatedBy = 1 - } - }, - People = new List { new Person { Projects = new List() } }, + CreatedBy = 1, + }, + ], + People = [new Person { Projects = [] }], }; var serviceCollection = new ServiceCollection(); UserService userService = new(); @@ -515,27 +500,27 @@ public void TestServicesMultipleReconnectToSchemaContextListOf_WithoutSelectionO indirectTasks { name } indirectTasksWithExplicitJoin { name } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Name = "Project 1", - Owner = new Person { Id = 10, } - } - }, + Owner = new Person { Id = 10 }, + }, + ], Tasks = new List { new Task { Name = "Task 1", - Assignee = new Person { Id = 10, } - } - } + Assignee = new Person { Id = 10 }, + }, + }, }; var serviceCollection = new ServiceCollection(); UserService userService = new(); @@ -576,10 +561,10 @@ public void TestSelectFromServiceDeepInLists() id # the service field below requires id. Make sure we don't select it twice } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Tasks = new List { new Task() } } }, }; + var context = new TestDataContext { Projects = [new Project { Tasks = new List { new Task() } }] }; var serviceCollection = new ServiceCollection(); var settings = new SettingsService(); serviceCollection.AddSingleton(settings); @@ -614,10 +599,10 @@ public void TestComplexFieldWithServiceField() allowComments } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Tasks = new List { new Task() } } }, }; + var context = new TestDataContext { Projects = [new Project { Tasks = new List { new Task() } }] }; var serviceCollection = new ServiceCollection(); var settings = new SettingsService(); serviceCollection.AddSingleton(settings); @@ -655,23 +640,23 @@ public void TestComplexFieldInObjectWithServiceField() allowComments } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Tasks = new List { new Task() }, Owner = new Person { Id = 77, - Manager = new Person { Id = 99 } - } - } - }, + Manager = new Person { Id = 99 }, + }, + }, + ], }; var serviceCollection = new ServiceCollection(); var settings = new SettingsService(); @@ -712,10 +697,10 @@ public void TestServiceThenSubSelectWithWhere() config { type } tasks { id } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Tasks = new List { new Task { Id = 98 } } } }, }; + var context = new TestDataContext { Projects = [new Project { Tasks = new List { new Task { Id = 98 } } }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -749,10 +734,10 @@ public void TestServiceThenSubSelectWithWhereAlreadySelectedFieldInWhere() config { type } tasks { id isActive } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Tasks = new List { new Task { Id = 98 } } } }, }; + var context = new TestDataContext { Projects = [new Project { Tasks = new List { new Task { Id = 98 } } }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -786,21 +771,21 @@ public void TestServiceThenSubSelectWithWhereDuplicateFieldNames() config { type } tasks { isActive project { isActive } } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Tasks = new List { - new Task { Id = 98, IsActive = true } - } - } - }, + new Task { Id = 98, IsActive = true }, + }, + }, + ], }; context.Projects.First().Tasks.First().Project = context.Projects.First(); @@ -836,21 +821,21 @@ public void TestServiceThenSubOrderBy() config { type } tasks { isActive } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Tasks = new List { - new Task { Id = 98, IsActive = true } - } - } - }, + new Task { Id = 98, IsActive = true }, + }, + }, + ], }; context.Projects.First().Tasks.First().Project = context.Projects.First(); @@ -874,7 +859,7 @@ public void TestWhereWhenWithServiceField() .Query() .ReplaceField( "projects", - new { search = (string?)null, }, + new { search = (string?)null }, (ctx, args) => ctx.Projects.WhereWhen(p => p.Description.ToLower().Contains(args.search!), !string.IsNullOrEmpty(args.search)).OrderBy(p => p.Description), "List of projects" ); @@ -890,10 +875,10 @@ public void TestWhereWhenWithServiceField() projects { configType } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Description = "Hello" } }, }; + var context = new TestDataContext { Projects = [new Project { Description = "Hello" }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -921,10 +906,10 @@ public void TestWhereWhenWithServiceFieldAndArgument() project(id: 0) { configType } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Description = "Hello" } }, }; + var context = new TestDataContext { Projects = [new Project { Description = "Hello" }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -977,13 +962,13 @@ fragment taskFrag on Task { ...taskFrag } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, @@ -992,11 +977,11 @@ fragment taskFrag on Task { { new Task { - Assignee = new Person { Name = "Billy", Projects = new List() } - } + Assignee = new Person { Name = "Billy", Projects = [] }, + }, }, - } - }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1026,7 +1011,7 @@ public void TestCollectionToSingleWithServiceInCollection() configType } } - }" + }", }; var context = new TestDataContext @@ -1036,8 +1021,8 @@ public void TestCollectionToSingleWithServiceInCollection() new Task { Id = 1, - Project = new Project { Id = 0, Description = "Hello", } - } + Project = new Project { Id = 0, Description = "Hello" }, + }, }, }; @@ -1070,7 +1055,7 @@ public void TestCollectionToSingleWithServiceTypeInCollection() } } } - }" + }", }; var context = new TestDataContext @@ -1080,8 +1065,8 @@ public void TestCollectionToSingleWithServiceTypeInCollection() new Task { Id = 1, - Project = new Project { Id = 0, Description = "Hello", } - } + Project = new Project { Id = 0, Description = "Hello" }, + }, }, }; @@ -1114,7 +1099,7 @@ public void TestCollectionToSingleWithServiceTypeInCollectionThatUsesAContextFie } } } - }" + }", }; var context = new TestDataContext @@ -1124,8 +1109,8 @@ public void TestCollectionToSingleWithServiceTypeInCollectionThatUsesAContextFie new Task { Id = 1, - Project = new Project { Id = 0, Description = "Hello", } - } + Project = new Project { Id = 0, Description = "Hello" }, + }, }, }; @@ -1159,7 +1144,7 @@ public void TestCollectionToSingle_ToObjectRelation_WithAsyncServiceArrayField_U arrayField } } - }" + }", }; var context = new TestDataContext @@ -1169,8 +1154,8 @@ public void TestCollectionToSingle_ToObjectRelation_WithAsyncServiceArrayField_U new Task { Id = 1, - Project = new Project { Id = 0, Description = "Hello", } - } + Project = new Project { Id = 0, Description = "Hello" }, + }, }, }; @@ -1200,19 +1185,19 @@ public void TestCollectionToSingle_WithAsyncService_UsingContextFieldEnumerable( project(id: 1) { serviceField } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 1, - Tasks = new List { new Task { Id = 0, } } - } - }, + Tasks = new List { new Task { Id = 0 } }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1244,20 +1229,20 @@ public void TestServiceAfterMultipleCollectionToSingle() } } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, Description = "Hello", - Tasks = new List { new Task { Id = 1, } }, - } - } + Tasks = new List { new Task { Id = 1 } }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1289,7 +1274,7 @@ public void TestCollectionCollectionObjectThenService() } } } - }" + }", }; var context = new TestDataContext(); @@ -1332,16 +1317,62 @@ public void TestCollectionWithService() type } } - }" + }", }; var context = new TestDataContext(); context.FillWithTestData(); - var doc = new GraphQLCompiler(schema).Compile(gql); + var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(1, service.CallCount); + dynamic people = res.Data!["people"]!; + Assert.Equal(1, people[0].GetType().GetFields().Length); + Assert.Equal("resolvedSettings", Enumerable.ElementAt(people[0].GetType().GetFields(), 0).Name); + } + + [Fact] + public void TestCollectionWithServiceUsingNewInExpression() + { + var schema = SchemaBuilder.FromObject(); + + schema.AddType("TestThing").AddAllFields(); + schema.AddType("ProjectConfig").AddAllFields(); + schema + .Query() + .ReplaceField( + "people", + new { height = (int?)null }, + (ctx, args) => ctx.People.WhereWhen(p => p.Height > args.height, args.height.HasValue).OrderBy(p => p.Height), + "Get people with height > {height}" + ); + schema.Type().AddField("resolvedSettings", "Return resolved settings").Resolve((f, b) => new TestThing(f.Id, b.Get(f.Id))).IsNullable(false); - Assert.Single(doc.Operations); - Assert.Single(doc.Operations[0].QueryFields); + var serviceCollection = new ServiceCollection(); + ConfigService service = new(); + serviceCollection.AddSingleton(service); + + var gql = new QueryRequest + { + Query = + @"query OpName { + people { + resolvedSettings { + id + } + } + }", + }; + + var context = new TestDataContext(); + context.FillWithTestData(); + + var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(1, service.CallCount); + dynamic people = res.Data!["people"]!; + Assert.Equal(1, people[0].GetType().GetFields().Length); + Assert.Equal("resolvedSettings", Enumerable.ElementAt(people[0].GetType().GetFields(), 0).Name); } [Fact] @@ -1358,7 +1389,7 @@ public void TestRootServiceFieldBackToContext() currentUser { projectNames } - }" + }", }; var context = new TestDataContext(); @@ -1417,7 +1448,7 @@ public void TestServiceFieldNullableCheckOnChildObject() field2 relation { id } } - }" + }", }; var context = new TestDataContext(); @@ -1447,7 +1478,7 @@ public void TestServiceFieldReturnsNullList() .Query() .ReplaceField( "project", - new { id = (int?)null, search = (string?)null, }, + new { id = (int?)null, search = (string?)null }, (ctx, args) => ctx.Projects.WhereWhen(c => c.Id == args.id, args.id != null).WhereWhen(p => p.Description.ToLower().Contains(args.search!), !string.IsNullOrEmpty(args.search)).SingleOrDefault(), "project details" @@ -1464,10 +1495,10 @@ public void TestServiceFieldReturnsNullList() project(id: 0) { configs { type } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Id = 0, } }, }; + var context = new TestDataContext { Projects = [new Project { Id = 0 }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -1491,7 +1522,7 @@ public void TestServiceFieldReturnsList() .Query() .ReplaceField( "project", - new { id = (int?)null, search = (string?)null, }, + new { id = (int?)null, search = (string?)null }, (ctx, args) => ctx.Projects.WhereWhen(c => c.Id == args.id, args.id != null).WhereWhen(p => p.Description.ToLower().Contains(args.search!), !string.IsNullOrEmpty(args.search)).SingleOrDefault(), "project details" @@ -1508,10 +1539,10 @@ public void TestServiceFieldReturnsList() project(id: 0) { configs { type } } - }" + }", }; - var context = new TestDataContext { Projects = new List { new Project { Id = 0, } }, }; + var context = new TestDataContext { Projects = [new Project { Id = 0 }] }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); Assert.Null(res.Errors); @@ -1537,7 +1568,7 @@ public void TestServiceFieldWithQueryable() .Query() .ReplaceField( "projects", - new { id = (int?)null, search = (string?)null, }, + new { id = (int?)null, search = (string?)null }, (ctx, args) => ctx .QueryableProjects.WhereWhen(c => c.Id == args.id, args.id != null) @@ -1561,19 +1592,19 @@ public void TestServiceFieldWithQueryable() id } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, - Tasks = new List { new Task { Id = 1, } } - } - }, + Tasks = new List { new Task { Id = 1 } }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1605,19 +1636,19 @@ public void TestServiceFieldWithStaticMethod() projects { config { type __typename } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, - Tasks = new List { new Task { Id = 1, } } - } - }, + Tasks = new List { new Task { Id = 1 } }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1647,19 +1678,19 @@ public void TestServiceFieldWithInstanceMethod() projects { config { type __typename } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, - Tasks = new List { new Task { Id = 1, } } - } - }, + Tasks = new List { new Task { Id = 1 } }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1693,13 +1724,13 @@ public void TestScalarServiceFieldWithSameTypeReferencedExpression() projects { someService } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, @@ -1707,10 +1738,10 @@ public void TestScalarServiceFieldWithSameTypeReferencedExpression() Children = new List { new Project { Id = 1, Name = "Child1" }, - new Project { Id = 2, Name = "Child2" } + new Project { Id = 2, Name = "Child2" }, }, - } - }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1743,13 +1774,13 @@ public void TestServiceFieldWithExpressionReturnCollection() projects { someService { id } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, @@ -1757,10 +1788,10 @@ public void TestServiceFieldWithExpressionReturnCollection() Children = new List { new Project { Id = 1, Name = "Child1" }, - new Project { Id = 2, Name = "Child2" } + new Project { Id = 2, Name = "Child2" }, }, - } - }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1780,10 +1811,7 @@ public void TestServiceFieldWithExpressionReturnCollectionToSingle() var schema = SchemaBuilder.FromObject(); schema.AddType("ProjectConfig").AddAllFields(); - schema - .Type() - .AddField("someService", "Get project configs if they exists") - .ResolveWithService((ctx, srv) => srv.GetCollection(ctx.Name, ctx.Description).FirstOrDefault()); + schema.Type().AddField("someService", "Get project configs if they exists").Resolve((ctx, srv) => srv.GetCollection(ctx.Name, ctx.Description).FirstOrDefault()); var serviceCollection = new ServiceCollection(); var srv = new ConfigService(); @@ -1796,13 +1824,13 @@ public void TestServiceFieldWithExpressionReturnCollectionToSingle() projects { someService { type } } - }" + }", }; var context = new TestDataContext { - Projects = new List - { + Projects = + [ new Project { Id = 0, @@ -1811,8 +1839,8 @@ public void TestServiceFieldWithExpressionReturnCollectionToSingle() { new Project { Id = 1, Name = "Child1" }, }, - } - }, + }, + ], }; var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); @@ -1827,10 +1855,7 @@ public void TestRootServiceFieldWithExpressionReturnCollectionToSingle() var schema = SchemaBuilder.FromObject(); schema.AddType("ProjectConfig").AddAllFields(); - schema - .Query() - .AddField("someService", "Get project configs if they exists") - .ResolveWithService((ctx, srv) => srv.GetCollection(ctx.TotalPeople.ToString(), "b").FirstOrDefault()); + schema.Query().AddField("someService", "Get project configs if they exists").Resolve((ctx, srv) => srv.GetCollection(ctx.TotalPeople.ToString(), "b").FirstOrDefault()); var serviceCollection = new ServiceCollection(); var srv = new ConfigService(); @@ -1841,7 +1866,7 @@ public void TestRootServiceFieldWithExpressionReturnCollectionToSingle() Query = @"{ someService { type } - }" + }", }; var context = new TestDataContext(); @@ -1852,6 +1877,106 @@ public void TestRootServiceFieldWithExpressionReturnCollectionToSingle() Assert.Equal(1, srv.CallCount); } + [Fact] + public void TestServiceFieldsNotNamedSameAsDotnetProperties() + { + var schema = SchemaBuilder.FromObject(); + + schema.Type().RemoveField("name"); + schema.Type().AddField("username", u => u.Name, "Get username"); + + schema.Type().ReplaceField("createdBy", "Get user that created the project").Resolve((p, users) => users.GetUserById(p.CreatedBy)); + + var gql = new QueryRequest + { + Query = + @"{ + project(id: 1) { + name + createdBy { + username + } + } + }", + }; + + var context = new TestDataContext + { + Projects = + [ + new Project + { + Id = 1, + Name = "Project 1", + CreatedBy = 1, + }, + ], + People = [new Person { Projects = [] }], + }; + var serviceCollection = new ServiceCollection(); + UserService userService = new(); + serviceCollection.AddSingleton(userService); + serviceCollection.AddSingleton(context); + + var res = schema.ExecuteRequestWithContext(gql, context, serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(1, userService.CallCount); + dynamic project = res.Data!["project"]!; + Assert.Equal(2, project.GetType().GetFields().Length); + Assert.Equal("name", Enumerable.ElementAt(project.GetType().GetFields(), 0).Name); + Assert.Equal("createdBy", Enumerable.ElementAt(project.GetType().GetFields(), 1).Name); + Assert.Equal("username", project.createdBy.GetType().GetFields()[0].Name); + } + + [Fact] + public void TestGeneratedNamesDoNotCollide() + { + var schema = SchemaBuilder.FromObject(); + + schema.UpdateType(p => + { + // Here the expression project is extracted and the expression is used for a field name, which is a duplicate of the project field + p.AddField("getProjectId", "Something").Resolve((project, us) => us.GetProjectId(project).GetAwaiter().GetResult()); + }); + + var gql = new QueryRequest + { + Query = + @"{ + project(id: 1) { + name + getProjectId + } + }", + }; + + var context = new TestDataContext + { + Projects = + [ + new Project + { + Id = 1, + Name = "Project 1", + CreatedBy = 1, + }, + ], + }; + var serviceCollection = new ServiceCollection(); + UserService userService = new(); + serviceCollection.AddSingleton(userService); + serviceCollection.AddSingleton(context); + + var res = schema.ExecuteRequest(gql, serviceCollection.BuildServiceProvider(), null); + Assert.Null(res.Errors); + Assert.Equal(1, userService.CallCount); + Assert.NotNull(res.Data); + dynamic project = res.Data["project"]!; + Assert.Equal(2, project.GetType().GetFields().Length); + Assert.Equal("name", Enumerable.ElementAt(project.GetType().GetFields(), 0).Name); + Assert.Equal("getProjectId", Enumerable.ElementAt(project.GetType().GetFields(), 1).Name); + } + public class ConfigService { public ConfigService() @@ -1882,7 +2007,7 @@ public IEnumerable GetCollection(string str1, string str2) { new Project { Id = 1, Name = str1 }, new Project { Id = 2, Name = str2 }, - new Project { Id = 3, Name = str1 + str2 } + new Project { Id = 3, Name = str1 + str2 }, }; } @@ -1949,7 +2074,7 @@ public Pagination PageProjects(TestDataContext db, dynamic arg) { Total = total, PageCount = pagecount, - Items = projects + Items = projects, }; } @@ -1971,7 +2096,7 @@ public Pagination PageProjects(IEnumerable projects, dynamic a { Total = total, PageCount = pagecount, - Items = newProjects + Items = newProjects, }; } } @@ -1989,6 +2114,18 @@ public class Pagination } } +internal class TestThing +{ + public int Id { get; set; } + private ProjectConfig projectConfig; + + public TestThing(int id, ProjectConfig projectConfig) + { + this.Id = id; + this.projectConfig = projectConfig; + } +} + public class SettingsService { public int CallCount { get; internal set; } @@ -2031,7 +2168,7 @@ public IEnumerable GetUsers(int? id = null) { CallCount += 1; Calls.Add(nameof(GetUsers)); - return [new User { Id = id ?? 0, }]; + return [new User { Id = id ?? 0 }]; } public IDictionary GetAllUsers(IEnumerable data) @@ -2043,7 +2180,7 @@ public IDictionary GetAllUsers(IEnumerable data) { Id = id, Field2 = "Hello", - Name = $"Name_{id}" + Name = $"Name_{id}", }) .ToDictionary(u => u.Id, u => u); } @@ -2061,7 +2198,7 @@ internal List GetUsersByProjectId(int id, string? nameFilter) Calls.Add(nameof(GetUsersByProjectId)); var users = new List { - new User { Id = id, Name = "Blah" } + new User { Id = id, Name = "Blah" }, }; return string.IsNullOrEmpty(nameFilter) ? users : users.Where(u => u.Name.Contains(nameFilter)).ToList(); } @@ -2078,8 +2215,8 @@ internal IDictionary GetUsersByProjectId(IEnumerable ids) { Id = id, Name = "Blah", - Field2 = "Hello" - } + Field2 = "Hello", + }, }) .ToDictionary(u => u.ProjectId, u => u.User); } @@ -2094,7 +2231,7 @@ internal IDictionary> GetUsersByProjectId(IEnumerable ids, id => new List { - new() { Id = id, Name = $"Name_{id}" } + new() { Id = id, Name = $"Name_{id}" }, } .Where(u => string.IsNullOrEmpty(nameFilter) ? true : u.Name.Contains(nameFilter)) .ToList() @@ -2115,6 +2252,12 @@ internal User GetUserByProjectId(int projectId, int userId) // "load" users from IDs and return them return ids.ToDictionary(id => id, i => i != id ? null : new User { Id = i, Name = $"Name_{i}" }); } + + internal async Task GetProjectId(Project project) + { + CallCount += 1; + return await System.Threading.Tasks.Task.FromResult(project.Id); + } } public class AgeService diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/StarWarsInterfaceTest.cs b/src/tests/EntityGraphQL.Tests/QueryTests/StarWarsInterfaceTest.cs index ec70a501..a7265e34 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/StarWarsInterfaceTest.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/StarWarsInterfaceTest.cs @@ -2,62 +2,61 @@ using EntityGraphQL.Schema; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class StarWarsInterfaceTest { - public class StarWarsInterfaceTest + public abstract class Character + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public IEnumerable Friends { get; set; } = []; + } + + public class Human : Character + { + public int TotalCredits { get; set; } + } + + public class Droid : Character + { + public string PrimaryFunction { get; set; } = string.Empty; + } + + public class StarWarsContext { - public abstract class Character - { - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public IEnumerable Friends { get; set; } = []; - } - - public class Human : Character - { - public int TotalCredits { get; set; } - } - - public class Droid : Character - { - public string PrimaryFunction { get; set; } = string.Empty; - } - - public class StarWarsContext - { - public IList Characters { get; set; } = []; - } - - [Fact] - public void StarWarsInterfaceTest_ManualCreation() - { - var schema = new SchemaProvider(); - - schema.AddInterface(name: "Character", description: "represents any character in the Star Wars trilogy").AddAllFields(); - - schema.AddType("").AddAllFields().Implements(); - - schema.AddType("").Implements(); - - var sdl = schema.ToGraphQLSchemaString(); - - Assert.Contains("interface Character", sdl); - Assert.Contains("type Human implements Character", sdl); - Assert.Contains("type Droid implements Character", sdl); - } - - [Fact] - public void StarWarsInterfaceTest_AutoCreation() - { - var schema = SchemaBuilder.FromObject(); - schema.AddType("Human").AddAllFields().ImplementAllBaseTypes(); - schema.AddType("Droid").AddAllFields().ImplementAllBaseTypes(); - - var sdl = schema.ToGraphQLSchemaString(); - - Assert.Contains("interface Character", sdl); - Assert.Contains("type Human implements Character", sdl); - Assert.Contains("type Droid implements Character", sdl); - } + public IList Characters { get; set; } = []; + } + + [Fact] + public void StarWarsInterfaceTest_ManualCreation() + { + var schema = new SchemaProvider(); + + schema.AddInterface(name: "Character", description: "represents any character in the Star Wars trilogy").AddAllFields(); + + schema.AddType("").AddAllFields().Implements(); + + schema.AddType("").Implements(); + + var sdl = schema.ToGraphQLSchemaString(); + + Assert.Contains("interface Character", sdl); + Assert.Contains("type Human implements Character", sdl); + Assert.Contains("type Droid implements Character", sdl); + } + + [Fact] + public void StarWarsInterfaceTest_AutoCreation() + { + var schema = SchemaBuilder.FromObject(); + schema.AddType("Human").AddAllFields().ImplementAllBaseTypes(); + schema.AddType("Droid").AddAllFields().ImplementAllBaseTypes(); + + var sdl = schema.ToGraphQLSchemaString(); + + Assert.Contains("interface Character", sdl); + Assert.Contains("type Human implements Character", sdl); + Assert.Contains("type Droid implements Character", sdl); } } diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/StructFieldsTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/StructFieldsTests.cs index 8d0d8b08..bd8e47b9 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/StructFieldsTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/StructFieldsTests.cs @@ -28,12 +28,12 @@ public void TestStructFields() name age } - }" + }", }; var data = new TestData { - StructField = new TestStruct { Name = "Test", Age = 10 } + StructField = new TestStruct { Name = "Test", Age = 10 }, }; var res = schema.ExecuteRequestWithContext(query, data, null, null); @@ -68,12 +68,12 @@ public void TestNullableStructFields() name age } - }" + }", }; var data = new TestData { - NullableStructField = new TestStruct { Name = "Test", Age = 10 } + NullableStructField = new TestStruct { Name = "Test", Age = 10 }, }; var res = schema.ExecuteRequestWithContext(query, data, null, null); diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/UnionTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/UnionTests.cs index ed15081b..29543f05 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/UnionTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/UnionTests.cs @@ -260,14 +260,14 @@ ... on Dog { new() { Id = 1, - FavoritePet = new Dog() { Name = "steve", HasBone = true } + FavoritePet = new Dog() { Name = "steve", HasBone = true }, } ); context.PetOwners.Add( new() { Id = 2, - FavoritePet = new Cat() { Name = "george", Lives = 9 } + FavoritePet = new Cat() { Name = "george", Lives = 9 }, } ); @@ -315,14 +315,14 @@ ... on Dog { new() { Id = 1, - FavoritePet = new Dog() { Name = "steve", HasBone = true } + FavoritePet = new Dog() { Name = "steve", HasBone = true }, } ); context.PetOwners.Add( new() { Id = 2, - FavoritePet = new Cat() { Name = "george", Lives = 9 } + FavoritePet = new Cat() { Name = "george", Lives = 9 }, } ); diff --git a/src/tests/EntityGraphQL.Tests/QueryTests/VariableTests.cs b/src/tests/EntityGraphQL.Tests/QueryTests/VariableTests.cs index ecc7d958..be431e38 100644 --- a/src/tests/EntityGraphQL.Tests/QueryTests/VariableTests.cs +++ b/src/tests/EntityGraphQL.Tests/QueryTests/VariableTests.cs @@ -23,7 +23,7 @@ query MyQuery($limit: Int) { people(limit: $limit) { id name } } ", - Variables = new QueryVariables { { "limit", 5 } } + Variables = new QueryVariables { { "limit", 5 } }, }; var tree = new GraphQLCompiler(schema).Compile(gql); diff --git a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderFromObjectTests.cs b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderFromObjectTests.cs index e8b5eb8e..66255819 100644 --- a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderFromObjectTests.cs +++ b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderFromObjectTests.cs @@ -141,7 +141,7 @@ query IntrospectionQuery { name kind } - }" + }", }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema3(), null, null); @@ -172,7 +172,7 @@ query IntrospectionQuery { kind } } - }" + }", }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema3(), null, null); @@ -199,7 +199,7 @@ query IntrospectionQuery { name kind } - }" + }", }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema2(), null, null); @@ -270,7 +270,7 @@ query IntrospectionQuery { name kind } - }" + }", }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema4(), null, null); @@ -296,7 +296,7 @@ query IntrospectionQuery { kind possibleTypes { name } } - }" + }", }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema4(), null, null); @@ -317,7 +317,7 @@ public void UnionsCantHaveFields() [Fact] public void TestIgnoreReferencedTypes() { - var schemaBuilderOptions = new SchemaBuilderOptions { IgnoreTypes = new HashSet { typeof(C) } }; + var schemaBuilderOptions = new SchemaBuilderOptions { IgnoreTypes = [typeof(C)] }; var schemaProvider = new SchemaProvider(); schemaProvider.AddType(typeof(B).Name, null).AddAllFields(schemaBuilderOptions); @@ -347,7 +347,7 @@ query IntrospectionQuery { } } } - """ + """, }; var res = schemaProvider.ExecuteRequestWithContext(gql, new TestSchema5(), null, null); @@ -363,7 +363,7 @@ public void TestIgnoreQueryFails() { var schemaProvider = SchemaBuilder.FromObject(); // Add a argument field with a require parameter - var gql = new QueryRequest { Query = @"query Test { movies { id } }", }; + var gql = new QueryRequest { Query = @"query Test { movies { id } }" }; dynamic results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null).Errors!; var err = Enumerable.First(results); Assert.Equal("Field 'movies' not found on type 'Query'", err.Message); @@ -374,7 +374,7 @@ public void TestIgnoreQueryPasses() { var schemaProvider = SchemaBuilder.FromObject(); // Add a argument field with a require parameter - var gql = new QueryRequest { Query = @"query Test { albums { id } }", }; + var gql = new QueryRequest { Query = @"query Test { albums { id } }" }; var results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); Assert.Empty((IEnumerable)results.Data!["albums"]!); } @@ -393,7 +393,7 @@ public void TestIgnoreInputFails() id } }", - Variables = new QueryVariables { { "name", "Balance, Not Symmetry" }, { "hiddenInputField", "yeh" }, } + Variables = new QueryVariables { { "name", "Balance, Not Symmetry" }, { "hiddenInputField", "yeh" } }, }; var results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); var error = results.Errors!.First(); @@ -414,7 +414,7 @@ public void TestIgnoreInputPasses() id name hiddenInputField } }", - Variables = new QueryVariables { { "name", "Balance, Not Symmetry" }, } + Variables = new QueryVariables { { "name", "Balance, Not Symmetry" } }, }; var results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); Assert.Null(results.Errors); @@ -438,7 +438,7 @@ public void TestIgnoreAllInInput() id } }", - Variables = new QueryVariables { { "name", "Balance, Not Symmetry" }, { "hiddenField", "yeh" }, } + Variables = new QueryVariables { { "name", "Balance, Not Symmetry" }, { "hiddenField", "yeh" } }, }; var results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); var error = results.Errors!.First(); @@ -458,7 +458,7 @@ public void TestIgnoreAllInQuery() id hiddenInputField hiddenField } }", - Variables = new QueryVariables { } + Variables = [], }; var results = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); Assert.NotNull(results.Errors); @@ -469,7 +469,7 @@ id hiddenInputField hiddenField [Fact] public void TestIgnoreDataAnnotations() { - var options = new SchemaBuilderOptions { IgnoreAttributes = new HashSet { typeof(CustomIgnoreAttribute) } }; + var options = new SchemaBuilderOptions { IgnoreAttributes = [typeof(CustomIgnoreAttribute)] }; var schema = SchemaBuilder.FromObject(options); Assert.True(schema.Type().HasField("normalField", null)); diff --git a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderTests.cs b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderTests.cs index 83f95f3f..f4044746 100644 --- a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderTests.cs +++ b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaBuilderTests.cs @@ -18,7 +18,7 @@ public void TestMissingTypeError() people { name } - }" + }", }; var context = new TestDataContext().FillWithTestData(); // we have not added Person type before trying to execute @@ -40,7 +40,7 @@ public void TestMissingTypeErrorNonRoot() people { tasks { name } } - }" + }", }; var context = new TestDataContext().FillWithTestData(); context.People[0].Tasks = []; diff --git a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaProviderTests.cs b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaProviderTests.cs index bebe3b83..eb9f1169 100644 --- a/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaProviderTests.cs +++ b/src/tests/EntityGraphQL.Tests/SchemaTests/SchemaProviderTests.cs @@ -4,140 +4,139 @@ using EntityGraphQL.Tests.ApiVersion1; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class SchemaProviderTests { - public class SchemaProviderTests + [Fact] + public void ReadsContextType() { - [Fact] - public void ReadsContextType() - { - var provider = new TestObjectGraphSchema(); - Assert.Equal(typeof(TestDataContext), provider.QueryContextType); - } + var provider = new TestObjectGraphSchema(); + Assert.Equal(typeof(TestDataContext), provider.QueryContextType); + } - [Fact] - public void ExposesFieldsFromObjectWhenNotDefined() - { - var provider = new TestObjectGraphSchema(); - Assert.True(provider.Type("Location").HasField("id", null)); - Assert.True(provider.Type("Location").HasField("address", null)); - Assert.True(provider.Type("Location").HasField("state", null)); - Assert.True(provider.Type("Location").HasField("country", null)); - Assert.True(provider.Type("Location").HasField("planet", null)); - } - - [Fact] - public void ExposesDefinedFields() - { - var provider = new TestObjectGraphSchema(); - Assert.True(provider.Type("Person").HasField("id", null)); - Assert.True(provider.Type("Person").HasField("name", null)); - // Not exposed in our schema - Assert.True(provider.Type("Person").HasField("fullName", null)); - } - - [Fact] - public void ReturnsActualName() - { - var schema = new TestObjectGraphSchema(); - Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); - Assert.Equal("name", schema.Type("Project").GetField("name", null).Name); - } + [Fact] + public void ExposesFieldsFromObjectWhenNotDefined() + { + var provider = new TestObjectGraphSchema(); + Assert.True(provider.Type("Location").HasField("id", null)); + Assert.True(provider.Type("Location").HasField("address", null)); + Assert.True(provider.Type("Location").HasField("state", null)); + Assert.True(provider.Type("Location").HasField("country", null)); + Assert.True(provider.Type("Location").HasField("planet", null)); + } - [Fact] - public void SupportsEnum() - { - var schema = new TestObjectGraphSchema(); - Assert.Equal("Gender", schema.Type("Gender").Name); - Assert.True(schema.Type("Gender").IsEnum); - Assert.Equal(4, schema.Type("Gender").GetFields().Count()); - Assert.Equal("__typename", schema.Type("Gender").GetFields().ElementAt(0).Name); - Assert.Equal("Female", schema.Type("Gender").GetFields().ElementAt(1).Name); - } - - [Fact] - public void RemovesTypeAndFields() - { - var schema = new TestObjectGraphSchema(); - Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); - schema.RemoveTypeAndAllFields(); - Assert.Empty(schema.Query().GetFields().Where(s => s.ReturnType.SchemaType.Name == "project")); - } - - [Fact] - public void RemovesTypeAndFields2() - { - var schema = new TestObjectGraphSchema(); - Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); - schema.RemoveTypeAndAllFields("Project"); - Assert.Empty(schema.Query().GetFields().Where(s => s.ReturnType.SchemaType.Name == "project")); - } - - [Fact] - public void SupportsAbstract() - { - var schema = new TestAbstractDataGraphSchema(); - Assert.Equal("Animal", schema.Type("Animal").Name); - Assert.True(schema.Type("Animal").IsInterface); + [Fact] + public void ExposesDefinedFields() + { + var provider = new TestObjectGraphSchema(); + Assert.True(provider.Type("Person").HasField("id", null)); + Assert.True(provider.Type("Person").HasField("name", null)); + // Not exposed in our schema + Assert.True(provider.Type("Person").HasField("fullName", null)); + } - Assert.Equal("Dog", schema.Type("Dog").Name); - Assert.False(schema.Type("Dog").IsInterface); - Assert.Equal("Animal", schema.Type("Dog").BaseTypesReadOnly[0].Name); + [Fact] + public void ReturnsActualName() + { + var schema = new TestObjectGraphSchema(); + Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); + Assert.Equal("name", schema.Type("Project").GetField("name", null).Name); + } - Assert.Equal("Cat", schema.Type("Cat").Name); - Assert.False(schema.Type("Cat").IsInterface); - Assert.Equal("Animal", schema.Type("Cat").BaseTypesReadOnly[0].Name); - } + [Fact] + public void SupportsEnum() + { + var schema = new TestObjectGraphSchema(); + Assert.Equal("Gender", schema.Type("Gender").Name); + Assert.True(schema.Type("Gender").IsEnum); + Assert.Equal(4, schema.Type("Gender").GetFields().Count()); + Assert.Equal("__typename", schema.Type("Gender").GetFields().ElementAt(0).Name); + Assert.Equal("Female", schema.Type("Gender").GetFields().ElementAt(1).Name); + } - [Fact] - public void HasTypeChecksMappings() - { - var schema = new TestObjectGraphSchema(); - Assert.True(schema.HasType(typeof(byte[]))); - } + [Fact] + public void RemovesTypeAndFields() + { + var schema = new TestObjectGraphSchema(); + Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); + schema.RemoveTypeAndAllFields(); + Assert.DoesNotContain(schema.Query().GetFields(), s => s.ReturnType.SchemaType.Name == "project"); + } - [Fact] - public void Scalar_SpecifiedBy() - { - var schema = new TestObjectGraphSchema(); - schema.Type().SpecifiedBy("https://www.example.com"); - var sdl = schema.ToGraphQLSchemaString(); + [Fact] + public void RemovesTypeAndFields2() + { + var schema = new TestObjectGraphSchema(); + Assert.Equal("id", schema.Type("Project").GetField("id", null).Name); + schema.RemoveTypeAndAllFields("Project"); + Assert.DoesNotContain(schema.Query().GetFields(), s => s.ReturnType.SchemaType.Name == "project"); + } + + [Fact] + public void SupportsAbstract() + { + var schema = new TestAbstractDataGraphSchema(); + Assert.Equal("Animal", schema.Type("Animal").Name); + Assert.True(schema.Type("Animal").IsInterface); + + Assert.Equal("Dog", schema.Type("Dog").Name); + Assert.False(schema.Type("Dog").IsInterface); + Assert.Equal("Animal", schema.Type("Dog").BaseTypesReadOnly[0].Name); - Assert.Contains("scalar Int @specifiedBy(url: \"https://www.example.com\")", sdl); + Assert.Equal("Cat", schema.Type("Cat").Name); + Assert.False(schema.Type("Cat").IsInterface); + Assert.Equal("Animal", schema.Type("Cat").BaseTypesReadOnly[0].Name); + } - var gql = new QueryRequest - { - Query = - @" + [Fact] + public void HasTypeChecksMappings() + { + var schema = new TestObjectGraphSchema(); + Assert.True(schema.HasType(typeof(byte[]))); + } + + [Fact] + public void Scalar_SpecifiedBy() + { + var schema = new TestObjectGraphSchema(); + schema.Type().SpecifiedBy("https://www.example.com"); + var sdl = schema.ToGraphQLSchemaString(); + + Assert.Contains("scalar Int @specifiedBy(url: \"https://www.example.com\")", sdl); + + var gql = new QueryRequest + { + Query = + @" query IntrospectionQuery { __type(name: ""Int"") { name specifiedByURL } } - " - }; + ", + }; - var context = new TestDataContext(); + var context = new TestDataContext(); - var res = schema.ExecuteRequestWithContext(gql, context, null, null); - Assert.Null(res.Errors); + var res = schema.ExecuteRequestWithContext(gql, context, null, null); + Assert.Null(res.Errors); - var schemaType = (dynamic)res.Data!["__type"]!; - Assert.Equal("https://www.example.com", schemaType.specifiedByURL); - } + var schemaType = (dynamic)res.Data!["__type"]!; + Assert.Equal("https://www.example.com", schemaType.specifiedByURL); + } - [Fact] - public void Scalar_SpecifiedBy_ErrorsOnObjectType() - { - var schema = new TestObjectGraphSchema(); + [Fact] + public void Scalar_SpecifiedBy_ErrorsOnObjectType() + { + var schema = new TestObjectGraphSchema(); - var ex = Assert.Throws(() => - { - schema.Type().SpecifiedBy("https://www.example.com"); - }); + var ex = Assert.Throws(() => + { + schema.Type().SpecifiedBy("https://www.example.com"); + }); - Assert.Equal("@specifiedBy can only be used on scalars", ex.Message); - } + Assert.Equal("@specifiedBy can only be used on scalars", ex.Message); } } diff --git a/src/tests/EntityGraphQL.Tests/SchemaTests/ToGraphQLSchemaStringTests.cs b/src/tests/EntityGraphQL.Tests/SchemaTests/ToGraphQLSchemaStringTests.cs index 0843c386..1709f569 100644 --- a/src/tests/EntityGraphQL.Tests/SchemaTests/ToGraphQLSchemaStringTests.cs +++ b/src/tests/EntityGraphQL.Tests/SchemaTests/ToGraphQLSchemaStringTests.cs @@ -1,171 +1,170 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using EntityGraphQL.Schema; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class ToGraphQLSchemaStringTests { - public class ToGraphQLSchemaStringTests + [Fact] + public void TestIgnoreWithSchema() { - [Fact] - public void TestIgnoreWithSchema() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - schemaProvider.Type().RemoveField("old"); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.DoesNotContain("hiddenField", schema); - // this exists as it is available for querying - Assert.Contains( - @"type Album { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + schemaProvider.Type().RemoveField("old"); + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.DoesNotContain("hiddenField", schema); + // this exists as it is available for querying + Assert.Contains( + @"type Album { genre: Genre! hiddenInputField: String id: Int! name: String! }", - schema - ); - // doesn't include the hidden input fields - Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); - } - - [Fact] - public void TestIgnoreWithSchemaBuilder() - { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderOptions() { IgnoreTypes = new[] { typeof(Album) }.ToHashSet() }); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.DoesNotContain("album", schema); - } + schema + ); + // doesn't include the hidden input fields + Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); + } - [Fact] - public void TestIgnoreEnumWithSchemaBuilder() - { - var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderOptions() { IgnoreTypes = new[] { typeof(Genre) }.ToHashSet() }); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.DoesNotContain("genre", schema); - } + [Fact] + public void TestIgnoreWithSchemaBuilder() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderOptions() { IgnoreTypes = new[] { typeof(Album) }.ToHashSet() }); + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.DoesNotContain("album", schema); + } - [Fact] - public void TestMutationWithListReturnType() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.Contains("addAlbum2(name: String!, genre: Genre!): [Album!]", schema); - } - - [Fact] - public void TestNotNullTypes() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.Type().RemoveField("old"); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains( - @"type Album { + [Fact] + public void TestIgnoreEnumWithSchemaBuilder() + { + var schemaProvider = SchemaBuilder.FromObject(new SchemaBuilderOptions() { IgnoreTypes = new[] { typeof(Genre) }.ToHashSet() }); + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.DoesNotContain("genre", schema); + } + + [Fact] + public void TestMutationWithListReturnType() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.Contains("addAlbum2(name: String!, genre: Genre!): [Album!]", schema); + } + + [Fact] + public void TestNotNullTypes() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.Type().RemoveField("old"); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains( + @"type Album { genre: Genre! hiddenInputField: String id: Int! name: String! }", - schema - ); - } + schema + ); + } - [Fact] - public void TestNullableEnumInType() - { - var schemaProvider = SchemaBuilder.FromObject(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains( - @"type Artist { + [Fact] + public void TestNullableEnumInType() + { + var schemaProvider = SchemaBuilder.FromObject(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains( + @"type Artist { id: Int! type: ArtistType }", - schema - ); - } + schema + ); + } - [Fact] - public void TestNotNullArgs() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); - } - - [Fact] - public void TestNotNullEnumerableElementByDefault() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("albums: [Album!]", schema); - } - - [Fact] - public void TestNullEnumerableElement() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("nullAlbums: [Album]", schema); - } - - [Fact] - public void TestDeprecatedField() - { - var schemaProvider = SchemaBuilder.FromObject(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("old: Int! @deprecated(reason: \"because\")", schema); - } - - [Fact] - public void TestDeprecatedMutationField() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("addAlbumOld(name: String!, genre: Genre!): Album! @deprecated(reason: \"This is obsolete\")", schema); - } - - [Fact] - public void TestDeprecatedEnumField() - { - var schemaProvider = SchemaBuilder.FromObject(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("Obsolete @deprecated(reason: \"This is an obsolete genre\")", schema); - } - - [Fact] - public void TestNullableRefTypeMutationField() - { - var schemaProvider = SchemaBuilder.FromObject(); - schemaProvider.AddMutationsFrom(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); - Assert.DoesNotContain("addAlbum(name: String!, genre: Genre!): Album!", schema); - Assert.Contains("addAlbum2(name: String!, genre: Genre!): Album!", schema); - Assert.Contains("addAlbum3(name: String!, genre: Genre!): Album", schema); - Assert.DoesNotContain("addAlbum3(name: String!, genre: Genre!): Album!", schema); - - var gql = new QueryRequest - { - Query = - @" + [Fact] + public void TestNotNullArgs() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); + } + + [Fact] + public void TestNotNullEnumerableElementByDefault() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("albums: [Album!]", schema); + } + + [Fact] + public void TestNullEnumerableElement() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("nullAlbums: [Album]", schema); + } + + [Fact] + public void TestDeprecatedField() + { + var schemaProvider = SchemaBuilder.FromObject(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("old: Int! @deprecated(reason: \"because\")", schema); + } + + [Fact] + public void TestDeprecatedMutationField() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("addAlbumOld(name: String!, genre: Genre!): Album! @deprecated(reason: \"This is obsolete\")", schema); + } + + [Fact] + public void TestDeprecatedEnumField() + { + var schemaProvider = SchemaBuilder.FromObject(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("Obsolete @deprecated(reason: \"This is an obsolete genre\")", schema); + } + + [Fact] + public void TestNullableRefTypeMutationField() + { + var schemaProvider = SchemaBuilder.FromObject(); + schemaProvider.AddMutationsFrom(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains("addAlbum(name: String!, genre: Genre!): Album", schema); + Assert.DoesNotContain("addAlbum(name: String!, genre: Genre!): Album!", schema); + Assert.Contains("addAlbum2(name: String!, genre: Genre!): Album!", schema); + Assert.Contains("addAlbum3(name: String!, genre: Genre!): Album", schema); + Assert.DoesNotContain("addAlbum3(name: String!, genre: Genre!): Album!", schema); + + var gql = new QueryRequest + { + Query = + @" query { __type(name: ""Mutation"") { fields { @@ -185,327 +184,326 @@ public void TestNullableRefTypeMutationField() } } } - " - }; - - var res = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); - Assert.Null(res.Errors); - - var mutation = (dynamic)res.Data!["__type"]!; - - Assert.Equal("addAlbum", mutation.fields[0].name); - Assert.Equal("Album", mutation.fields[0].type.name); - Assert.Equal("OBJECT", mutation.fields[0].type.kind); - Assert.Equal((string?)null, mutation.fields[0].type.ofType); - Assert.Equal("name", mutation.fields[0].args[0].name); - Assert.Equal("NON_NULL", mutation.fields[0].args[0].type.kind); - Assert.Equal("genre", mutation.fields[0].args[1].name); - Assert.Equal("NON_NULL", mutation.fields[0].args[1].type.kind); - - Assert.Equal("addAlbum2", mutation.fields[1].name); - Assert.Equal((string?)null, mutation.fields[1].type.name); - Assert.Equal("NON_NULL", mutation.fields[1].type.kind); - Assert.Equal("Album", mutation.fields[1].type.ofType.name); - Assert.Equal("name", mutation.fields[1].args[0].name); - Assert.Equal("NON_NULL", mutation.fields[1].args[0].type.kind); - Assert.Equal("genre", mutation.fields[1].args[1].name); - Assert.Equal("NON_NULL", mutation.fields[1].args[1].type.kind); - - Assert.Equal("addAlbum3", mutation.fields[2].name); - Assert.Equal("Album", mutation.fields[2].type.name); - Assert.Equal("OBJECT", mutation.fields[2].type.kind); - Assert.Equal((string?)null, mutation.fields[2].type.ofType); - Assert.Equal("name", mutation.fields[2].args[0].name); - Assert.Equal("NON_NULL", mutation.fields[2].args[0].type.kind); - Assert.Equal("genre", mutation.fields[2].args[1].name); - Assert.Equal("NON_NULL", mutation.fields[2].args[1].type.kind); - } - - [Fact] - public void TestAbstractClassToGraphQLSchemaString() - { - var schemaProvider = SchemaBuilder.FromObject(); - - schemaProvider.AddType("Dogs are animals").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("Cats are animals").Implements().AddAllFields(); - schemaProvider.AddType("Fish are animals"); + ", + }; + + var res = schemaProvider.ExecuteRequestWithContext(gql, new IgnoreTestSchema(), null, null); + Assert.Null(res.Errors); + + var mutation = (dynamic)res.Data!["__type"]!; + + Assert.Equal("addAlbum", mutation.fields[0].name); + Assert.Equal("Album", mutation.fields[0].type.name); + Assert.Equal("OBJECT", mutation.fields[0].type.kind); + Assert.Equal((string?)null, mutation.fields[0].type.ofType); + Assert.Equal("name", mutation.fields[0].args[0].name); + Assert.Equal("NON_NULL", mutation.fields[0].args[0].type.kind); + Assert.Equal("genre", mutation.fields[0].args[1].name); + Assert.Equal("NON_NULL", mutation.fields[0].args[1].type.kind); + + Assert.Equal("addAlbum2", mutation.fields[1].name); + Assert.Equal((string?)null, mutation.fields[1].type.name); + Assert.Equal("NON_NULL", mutation.fields[1].type.kind); + Assert.Equal("Album", mutation.fields[1].type.ofType.name); + Assert.Equal("name", mutation.fields[1].args[0].name); + Assert.Equal("NON_NULL", mutation.fields[1].args[0].type.kind); + Assert.Equal("genre", mutation.fields[1].args[1].name); + Assert.Equal("NON_NULL", mutation.fields[1].args[1].type.kind); + + Assert.Equal("addAlbum3", mutation.fields[2].name); + Assert.Equal("Album", mutation.fields[2].type.name); + Assert.Equal("OBJECT", mutation.fields[2].type.kind); + Assert.Equal((string?)null, mutation.fields[2].type.ofType); + Assert.Equal("name", mutation.fields[2].args[0].name); + Assert.Equal("NON_NULL", mutation.fields[2].args[0].type.kind); + Assert.Equal("genre", mutation.fields[2].args[1].name); + Assert.Equal("NON_NULL", mutation.fields[2].args[1].type.kind); + } - schemaProvider.UpdateType(x => x.Implements("Animal").AddAllFields()); + [Fact] + public void TestAbstractClassToGraphQLSchemaString() + { + var schemaProvider = SchemaBuilder.FromObject(); - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains(@"interface Animal", schema); + schemaProvider.AddType("Dogs are animals").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("Cats are animals").Implements().AddAllFields(); + schemaProvider.AddType("Fish are animals"); - Assert.Contains(@"type Cat implements Animal", schema); - Assert.Contains(@"type Dog implements Animal", schema); - Assert.Contains(@"type Fish implements Animal", schema); - } + schemaProvider.UpdateType(x => x.Implements("Animal").AddAllFields()); - [Fact] - public void TestMultipleInheritanceToGraphQLSchemaString() - { - var schemaProvider = SchemaBuilder.FromObject(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains(@"interface Animal", schema); - schemaProvider.AddType("").AddAllFields(); - schemaProvider.AddType("Dogs are animals").ImplementAllBaseTypes().AddAllFields(); - schemaProvider.AddType("Cats are animals").Implements().AddAllFields(); - schemaProvider.AddType("Fish are animals").ImplementAllBaseTypes().AddAllFields(); + Assert.Contains(@"type Cat implements Animal", schema); + Assert.Contains(@"type Dog implements Animal", schema); + Assert.Contains(@"type Fish implements Animal", schema); + } - var schema = schemaProvider.ToGraphQLSchemaString(); - // this exists as it is not null - Assert.Contains(@"interface Animal", schema); - Assert.Contains(@"interface ISwim", schema); + [Fact] + public void TestMultipleInheritanceToGraphQLSchemaString() + { + var schemaProvider = SchemaBuilder.FromObject(); - Assert.Contains(@"type Cat implements Animal", schema); - Assert.Contains(@"type Dog implements Animal", schema); - Assert.Contains(@"type Fish implements Animal & IAnimal & ISwim", schema); - } + schemaProvider.AddType("").AddAllFields(); + schemaProvider.AddType("Dogs are animals").ImplementAllBaseTypes().AddAllFields(); + schemaProvider.AddType("Cats are animals").Implements().AddAllFields(); + schemaProvider.AddType("Fish are animals").ImplementAllBaseTypes().AddAllFields(); - [Fact] - public void TestNoMutations() - { - var schemaProvider = SchemaBuilder.FromObject(); + var schema = schemaProvider.ToGraphQLSchemaString(); + // this exists as it is not null + Assert.Contains(@"interface Animal", schema); + Assert.Contains(@"interface ISwim", schema); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.DoesNotContain("mutation:", schema); - Assert.DoesNotContain($"type {schemaProvider.Mutation().SchemaType.Name}", schema); - } + Assert.Contains(@"type Cat implements Animal", schema); + Assert.Contains(@"type Dog implements Animal", schema); + Assert.Contains(@"type Fish implements Animal & IAnimal & ISwim", schema); + } - [Fact] - public void TestOutputsScalarDescription() - { - var schemaProvider = SchemaBuilder.FromObject(); + [Fact] + public void TestNoMutations() + { + var schemaProvider = SchemaBuilder.FromObject(); - var schema = schemaProvider.ToGraphQLSchemaString(); - Assert.Contains("\"\"\"Date with time scalar\"\"\"", schema); - } + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.DoesNotContain("mutation:", schema); + Assert.DoesNotContain($"type {schemaProvider.Mutation().SchemaType.Name}", schema); + } - [Fact] - public void TestGetArgDefaultValue_Null() - { - Assert.Equal("", SchemaGenerator.GetArgDefaultValue(null, (e) => e)); - } + [Fact] + public void TestOutputsScalarDescription() + { + var schemaProvider = SchemaBuilder.FromObject(); - [Fact] - public void TestGetArgDefaultValue_DbNull() - { - Assert.Equal("", SchemaGenerator.GetArgDefaultValue(DBNull.Value, (e) => e)); - } + var schema = schemaProvider.ToGraphQLSchemaString(); + Assert.Contains("\"\"\"Date with time scalar\"\"\"", schema); + } - [Fact] - public void TestGetArgDefaultValue_Int() - { - Assert.Equal("3", SchemaGenerator.GetArgDefaultValue(3, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_Null() + { + Assert.Equal("", SchemaGenerator.GetArgDefaultValue(null, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_Decimal() - { - Assert.Equal("3.14", SchemaGenerator.GetArgDefaultValue(3.14, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_DbNull() + { + Assert.Equal("", SchemaGenerator.GetArgDefaultValue(DBNull.Value, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_String() - { - Assert.Equal("\"stringValue\"", SchemaGenerator.GetArgDefaultValue("stringValue", (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_Int() + { + Assert.Equal("3", SchemaGenerator.GetArgDefaultValue(3, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_String_Empty() - { - Assert.Equal("\"\"", SchemaGenerator.GetArgDefaultValue("", (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_Decimal() + { + Assert.Equal("3.14", SchemaGenerator.GetArgDefaultValue(3.14, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_IntArray() - { - Assert.Equal("[1, 2, 3]", SchemaGenerator.GetArgDefaultValue(new[] { 1, 2, 3 }, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_String() + { + Assert.Equal("\"stringValue\"", SchemaGenerator.GetArgDefaultValue("stringValue", (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_StringArray() - { - Assert.Equal("[\"one\", \"two\", \"three\"]", SchemaGenerator.GetArgDefaultValue(new[] { "one", "two", "three" }, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_String_Empty() + { + Assert.Equal("\"\"", SchemaGenerator.GetArgDefaultValue("", (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_Enum() - { - Assert.Equal("Alternitive", SchemaGenerator.GetArgDefaultValue(Genre.Alternitive, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_IntArray() + { + Assert.Equal("[1, 2, 3]", SchemaGenerator.GetArgDefaultValue(new[] { 1, 2, 3 }, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_Filter() - { - Assert.Equal("", SchemaGenerator.GetArgDefaultValue(new EntityQueryType(), (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_StringArray() + { + Assert.Equal("[\"one\", \"two\", \"three\"]", SchemaGenerator.GetArgDefaultValue(new[] { "one", "two", "three" }, (e) => e)); + } - [Fact] - public void TestGetArgDefaultValue_Object() - { - Assert.Equal("{ Id: 5, Name: \"Test\", Genre: Rock, Old: 0 }", SchemaGenerator.GetArgDefaultValue(new Album { Id = 5, Name = "Test", }, (e) => e)); - } + [Fact] + public void TestGetArgDefaultValue_Enum() + { + Assert.Equal("Alternitive", SchemaGenerator.GetArgDefaultValue(Genre.Alternitive, (e) => e)); } - public class IgnoreTestMutations + [Fact] + public void TestGetArgDefaultValue_Filter() { - [GraphQLMutation] - public Expression> AddAlbum(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); - } + Assert.Equal("", SchemaGenerator.GetArgDefaultValue(new EntityQueryType(), (e) => e)); + } - [GraphQLMutation("Test correct generation of return type for a list")] - public Expression>> AddAlbum2(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums; - } - - [GraphQLMutation] - [Obsolete("This is obsolete")] - public Expression> AddAlbumOld(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); - } + [Fact] + public void TestGetArgDefaultValue_Object() + { + Assert.Equal("{ Id: 5, Name: \"Test\", Genre: Rock, Old: 0 }", SchemaGenerator.GetArgDefaultValue(new Album { Id = 5, Name = "Test" }, (e) => e)); } +} - public class NullableRefTypeMutations +public class IgnoreTestMutations +{ + [GraphQLMutation] + public Expression> AddAlbum(IgnoreTestSchema db, Album args) { - [GraphQLMutation] - public Expression> AddAlbum(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums.FirstOrDefault(a => a.Id == newAlbum.Id); - } + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); + } - [GraphQLMutation] - public Expression> AddAlbum2(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); - } + [GraphQLMutation("Test correct generation of return type for a list")] + public Expression>> AddAlbum2(IgnoreTestSchema db, Album args) + { + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums; + } - [GraphQLMutation] - public Expression> AddAlbum3(IgnoreTestSchema db, Album args) - { - var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name, }; - db.Albums.Add(newAlbum); - return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); - } + [GraphQLMutation] + [Obsolete("This is obsolete")] + public Expression> AddAlbumOld(IgnoreTestSchema db, Album args) + { + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); } +} - public class MovieArgs +public class NullableRefTypeMutations +{ + [GraphQLMutation] + public Expression> AddAlbum(IgnoreTestSchema db, Album args) { - [GraphQLNotNull] - public string Name { get; set; } = string.Empty; + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums.FirstOrDefault(a => a.Id == newAlbum.Id); + } - [GraphQLIgnore(GraphQLIgnoreType.Input)] - public string Hidden { get; set; } = string.Empty; + [GraphQLMutation] + public Expression> AddAlbum2(IgnoreTestSchema db, Album args) + { + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); } - public class AbstractClassTestSchema + [GraphQLMutation] + public Expression> AddAlbum3(IgnoreTestSchema db, Album args) { - public List Animals { get; set; } = []; + var newAlbum = new Album { Id = new Random().Next(100), Name = args.Name }; + db.Albums.Add(newAlbum); + return ctx => ctx.Albums.First(a => a.Id == newAlbum.Id); + } +} - public interface ISwim - { - public int Fins { get; set; } - } +public class MovieArgs +{ + [GraphQLNotNull] + public string Name { get; set; } = string.Empty; - public class Cat : Animal - { - public int Lives { get; set; } - } + [GraphQLIgnore(GraphQLIgnoreType.Input)] + public string Hidden { get; set; } = string.Empty; +} - public class Dog : Animal - { - public int Bones { get; set; } - } +public class AbstractClassTestSchema +{ + public List Animals { get; set; } = []; - public class Fish : Animal, ISwim - { - public int Fins { get; set; } - } + public interface ISwim + { + public int Fins { get; set; } } - public class IgnoreTestSchema + public class Cat : Animal { - public IgnoreTestSchema() - { - Movies = new List(); - Albums = new List(); - NullAlbums = new List(); - Artists = new List(); - } - - [GraphQLIgnore(GraphQLIgnoreType.Query)] - public List Movies { get; set; } - public List Albums { get; set; } - - [GraphQLElementTypeNullableAttribute] - public List NullAlbums { get; set; } - public List Artists { get; set; } + public int Lives { get; set; } } - public enum Genre + public class Dog : Animal { - Rock, - Classical, - Jazz, - Alternitive, - Pop, + public int Bones { get; set; } + } - [Obsolete("This is an obsolete genre")] - Obsolete + public class Fish : Animal, ISwim + { + public int Fins { get; set; } } +} - [GraphQLArguments] - public class Album +public class IgnoreTestSchema +{ + public IgnoreTestSchema() { - [GraphQLIgnore(GraphQLIgnoreType.Input)] - public int Id { get; set; } + Movies = []; + Albums = []; + NullAlbums = []; + Artists = []; + } + + [GraphQLIgnore(GraphQLIgnoreType.Query)] + public List Movies { get; set; } + public List Albums { get; set; } + + [GraphQLElementTypeNullableAttribute] + public List NullAlbums { get; set; } + public List Artists { get; set; } +} - [GraphQLNotNull] - public string Name { get; set; } = string.Empty; +public enum Genre +{ + Rock, + Classical, + Jazz, + Alternitive, + Pop, + + [Obsolete("This is an obsolete genre")] + Obsolete, +} - [GraphQLIgnore(GraphQLIgnoreType.Input)] - public string? HiddenInputField { get; set; } +[GraphQLArguments] +public class Album +{ + [GraphQLIgnore(GraphQLIgnoreType.Input)] + public int Id { get; set; } - [GraphQLIgnore(GraphQLIgnoreType.All)] // default - public string? HiddenAllField { get; set; } + [GraphQLNotNull] + public string Name { get; set; } = string.Empty; - [GraphQLNotNull] - public Genre Genre { get; set; } + [GraphQLIgnore(GraphQLIgnoreType.Input)] + public string? HiddenInputField { get; set; } - [Obsolete("because")] - [GraphQLIgnore(GraphQLIgnoreType.Input)] - public int Old { get; set; } - } + [GraphQLIgnore(GraphQLIgnoreType.All)] // default + public string? HiddenAllField { get; set; } - public class Movie - { - public int Id { get; set; } - public string Title { get; set; } = string.Empty; - } + [GraphQLNotNull] + public Genre Genre { get; set; } - public enum ArtistType - { - Solo, - Band, - Supergroup, - } + [Obsolete("because")] + [GraphQLIgnore(GraphQLIgnoreType.Input)] + public int Old { get; set; } +} - public class Artist - { - public int Id { get; set; } - public ArtistType? Type { get; set; } - } +public class Movie +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; +} + +public enum ArtistType +{ + Solo, + Band, + Supergroup, +} + +public class Artist +{ + public int Id { get; set; } + public ArtistType? Type { get; set; } } diff --git a/src/tests/EntityGraphQL.Tests/ServiceLifetimeTests.cs b/src/tests/EntityGraphQL.Tests/ServiceLifetimeTests.cs index e0af0788..2272f448 100644 --- a/src/tests/EntityGraphQL.Tests/ServiceLifetimeTests.cs +++ b/src/tests/EntityGraphQL.Tests/ServiceLifetimeTests.cs @@ -31,7 +31,7 @@ public void TestDataContextLifetimeTransientTwoTimes() movies { title } - }" + }", }; var result = schema.ExecuteRequest(gql, provider, null, null); @@ -68,7 +68,7 @@ public void TestDataContextLifetimeTransientField() title directorAgeOnRelease } - }" + }", }; var result = schema.ExecuteRequest(gql, provider, null, null); @@ -113,7 +113,7 @@ public void TestDataContextLifetimeTransientFieldManySameService() directorAgeOnRelease hoursOld } - }" + }", }; var result = schema.ExecuteRequest(gql, provider, null, null); @@ -161,7 +161,7 @@ public void TestDataContextLifetimeScoped() directorAgeOnRelease hoursOld } - }" + }", }; var result = schema.ExecuteRequest(gql, provider, null, null); @@ -203,7 +203,7 @@ public void TestDataContextLifetimeSingleton() directorAgeOnRelease hoursOld } - }" + }", }; var result = schema.ExecuteRequest(gql, provider, null, null); @@ -216,26 +216,24 @@ internal class MyDataContext { public string Name { get; set; } = "Test"; public List Movies { get; set; } = - new List - { + [ new MyMovie { Id = 1, Title = "Movie 1", DirectorId = 11, - Released = new DateTime(1999, 6, 19) - } - }; + Released = new DateTime(1999, 6, 19), + }, + ]; public List Directors { get; set; } = - new List - { + [ new MyDirector { Id = 11, Name = "Director 1", - Dob = new DateTime(1978, 4, 6) - } - }; + Dob = new DateTime(1978, 4, 6), + }, + ]; } internal class MyDirector diff --git a/src/tests/EntityGraphQL.Tests/SortTests/SortTests.cs b/src/tests/EntityGraphQL.Tests/SortTests/SortTests.cs index 52309d45..600baca4 100644 --- a/src/tests/EntityGraphQL.Tests/SortTests/SortTests.cs +++ b/src/tests/EntityGraphQL.Tests/SortTests/SortTests.cs @@ -19,7 +19,7 @@ public void SupportUseSort() @"query($sort: [QueryPeopleSortInput]) { people(sort: $sort) { lastName } }", - Variables = new QueryVariables { { "sort", new[] { new { lastName = SortDirection.DESC } } } } + Variables = new QueryVariables { { "sort", new[] { new { lastName = SortDirection.DESC } } } }, }; var context = new TestDataContext().FillWithTestData(); context.People.Add(new Person { LastName = "Zoo" }); @@ -42,7 +42,7 @@ public void TestSortAttribute() @"query($sort: [QueryPeopleSortInput]) { people(sort: $sort) { lastName } }", - Variables = new QueryVariables { { "sort", new[] { new { lastName = "DESC" } } } } + Variables = new QueryVariables { { "sort", new[] { new { lastName = "DESC" } } } }, }; TestDataContext2 context = new(); context.FillWithTestData(); @@ -76,7 +76,7 @@ public void SupportUseSortSelectSortFields() @"query($sort: [QueryPeopleSortInput]) { people(sort: $sort) { lastName } }", - Variables = new QueryVariables { { "sort", new[] { new { height = "ASC" } } } } + Variables = new QueryVariables { { "sort", new[] { new { height = "ASC" } } } }, }; var context = new TestDataContext().FillWithTestData(); context.People.Add(new Person { LastName = "Zoo", Height = 1 }); @@ -105,7 +105,7 @@ public void SupportUseSortSelectSortFieldsDeepProperty() @"query($sort: [QueryPeopleSortInput]) { people(sort: $sort) { lastName } }", - Variables = new QueryVariables { { "sort", new[] { new { managerName = "ASC" } } } } + Variables = new QueryVariables { { "sort", new[] { new { managerName = "ASC" } } } }, }; var context = new TestDataContext().FillWithTestData(); foreach (var p in context.People) @@ -116,7 +116,7 @@ public void SupportUseSortSelectSortFieldsDeepProperty() new Person { LastName = "Zoo", - Manager = new Person { Name = "Abe" } + Manager = new Person { Name = "Abe" }, } ); var tree = schema.ExecuteRequestWithContext(gql, context, null, null); @@ -230,7 +230,7 @@ public void SupportUseSortOnNonRoot() tasks(sort: $sort) { id } } }", - Variables = new QueryVariables { { "sort", new[] { new { id = "DESC" } } } } + Variables = new QueryVariables { { "sort", new[] { new { id = "DESC" } } } }, }; var context = new TestDataContext().FillWithTestData(); var tree = schema.ExecuteRequestWithContext(gql, context, null, null); @@ -263,8 +263,8 @@ public void SupportUseSortOnNonRootVariableWithClass() { "sort", new List { new IdSort { Id = SortDirection.DESC } } - } - } + }, + }, }; var context = new TestDataContext().FillWithTestData(); var tree = schema.ExecuteRequestWithContext(gql, context, null, null); @@ -292,7 +292,7 @@ public void SupportUseSortOnNonRoot2() tasks(sort: $sort) { id } } }", - Variables = new QueryVariables { { "sort", new[] { new { id = "DESC" } } } } + Variables = new QueryVariables { { "sort", new[] { new { id = "DESC" } } } }, }; var context = new TestDataContext().FillWithTestData(); var tree = schema.ExecuteRequestWithContext(gql, context, null, null); diff --git a/src/tests/EntityGraphQL.Tests/SubscriptionTests/SubscriptionTests.cs b/src/tests/EntityGraphQL.Tests/SubscriptionTests/SubscriptionTests.cs index 3e09cd08..f0662de7 100644 --- a/src/tests/EntityGraphQL.Tests/SubscriptionTests/SubscriptionTests.cs +++ b/src/tests/EntityGraphQL.Tests/SubscriptionTests/SubscriptionTests.cs @@ -161,7 +161,7 @@ internal class ChatService public Message PostMessage(string message) { - var msg = new Message { Id = 2, Text = message, }; + var msg = new Message { Id = 2, Text = message }; broadcaster.OnNext(msg); diff --git a/src/tests/EntityGraphQL.Tests/TestAbstractDataContext.cs b/src/tests/EntityGraphQL.Tests/TestAbstractDataContext.cs index b822d63a..8ade5824 100644 --- a/src/tests/EntityGraphQL.Tests/TestAbstractDataContext.cs +++ b/src/tests/EntityGraphQL.Tests/TestAbstractDataContext.cs @@ -4,62 +4,61 @@ using System.Linq.Expressions; using EntityGraphQL.Schema; -namespace EntityGraphQL.Tests -{ - /// - /// This is a mock datamodel, what would be your real datamodel and/or EF context - /// - /// Used by most of the tests - /// - public class TestAbstractDataContext - { - public List Animals { get; set; } = new List(); - public List Cats { get; set; } = new List(); - public List Dogs { get; set; } = new List(); +namespace EntityGraphQL.Tests; - public List People { get; set; } = new List(); +/// +/// This is a mock datamodel, what would be your real datamodel and/or EF context +/// +/// Used by most of the tests +/// +public class TestAbstractDataContext +{ + public List Animals { get; set; } = []; + public List Cats { get; set; } = []; + public List Dogs { get; set; } = []; - [GraphQLMutation] - public Expression> TestMutation(int id) - { - return (db) => db.Animals.First(); - } - } + public List People { get; set; } = []; - public class TestAbstractDataContextNoAnimals + [GraphQLMutation] + public Expression> TestMutation(int id) { - public List Cats { get; set; } = new List(); - public List Dogs { get; set; } = new List(); + return (db) => db.Animals.First(); } +} - public class TestUnionDataContext - { - public List Animals { get; set; } = new List(); - } +public class TestAbstractDataContextNoAnimals +{ + public List Cats { get; set; } = []; + public List Dogs { get; set; } = []; +} - public interface IAnimal { } +public class TestUnionDataContext +{ + public List Animals { get; set; } = []; +} - public abstract class Animal : IAnimal - { - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - } +public interface IAnimal { } - public class Cat : Animal - { - public int Lives { get; set; } - } +public abstract class Animal : IAnimal +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; +} - public class Dog : Animal - { - public bool HasBone { get; set; } - } +public class Cat : Animal +{ + public int Lives { get; set; } +} - public class PersonType - { - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public DateTime? Birthday { get; set; } - public IEnumerable Dogs { get; set; } = []; - } +public class Dog : Animal +{ + public bool HasBone { get; set; } +} + +public class PersonType +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public DateTime? Birthday { get; set; } + public IEnumerable Dogs { get; set; } = []; } diff --git a/src/tests/EntityGraphQL.Tests/TestDataContext.cs b/src/tests/EntityGraphQL.Tests/TestDataContext.cs index 03afc3b2..11b51fb7 100644 --- a/src/tests/EntityGraphQL.Tests/TestDataContext.cs +++ b/src/tests/EntityGraphQL.Tests/TestDataContext.cs @@ -41,9 +41,9 @@ public IQueryable QueryableProjects public class TestDataContext2 { - public List Tasks { get; set; } = new(); - public List People { get; set; } = new(); - public List Users { get; set; } = new(); + public List Tasks { get; set; } = []; + public List People { get; set; } = []; + public List Users { get; set; } = []; } public class ProjectOld { } @@ -53,13 +53,13 @@ public enum Gender { Female, Male, - NotSpecified + NotSpecified, } public enum UserType { Admin, - User + User, } public class User @@ -115,10 +115,7 @@ public string Error_AggregateException get => throw new AggregateException(Enumerable.Range(0, 2).Select(_ => new Exception("You should not see this message outside of Development"))); set => throw new AggregateException(Enumerable.Range(0, 2).Select(_ => new Exception("You should not see this message outside of Development"))); } - public string Error_Allowed - { - get => throw new TestException(); - } + public string Error_Allowed => throw new TestException(); public double GetHeight(HeightUnit unit) { @@ -186,7 +183,7 @@ public enum HeightUnit { Cm, Meter, - Feet + Feet, } internal static class DataFiller @@ -200,7 +197,7 @@ internal static T FillWithTestData(this T context) Field1 = 2, Field2 = "2", Relation = new Person(), - NestedRelation = new Task() + NestedRelation = new Task(), }; context.Users = [user]; @@ -229,7 +226,7 @@ public static Person MakePerson(int id, User? user, Project? project) User = user, Height = 183, Gender = Gender.Male, - Projects = project != null ? new List { project } : new List(), + Projects = project != null ? [project] : [], }; } } diff --git a/src/tests/EntityGraphQL.Tests/TestExtensions.cs b/src/tests/EntityGraphQL.Tests/TestExtensions.cs index 82b3a04a..38549d39 100644 --- a/src/tests/EntityGraphQL.Tests/TestExtensions.cs +++ b/src/tests/EntityGraphQL.Tests/TestExtensions.cs @@ -3,20 +3,19 @@ using EntityGraphQL.Extensions; using Xunit; -namespace EntityGraphQL.Tests +namespace EntityGraphQL.Tests; + +public class TestExtensions { - public class TestExtensions + [Fact] + public void TestGetNonNullableOrEnumerableType() { - [Fact] - public void TestGetNonNullableOrEnumerableType() - { - Assert.Equal(typeof(int), typeof(int[]).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(double), typeof(List).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(int), typeof(int).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(DateTime), typeof(IEnumerable).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(DateTime), typeof(DateTime?).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(int), typeof(int?).GetNonNullableOrEnumerableType()); - Assert.Equal(typeof(string), typeof(string).GetNonNullableOrEnumerableType()); - } + Assert.Equal(typeof(int), typeof(int[]).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(double), typeof(List).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(int), typeof(int).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(DateTime), typeof(IEnumerable).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(DateTime), typeof(DateTime?).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(int), typeof(int?).GetNonNullableOrEnumerableType()); + Assert.Equal(typeof(string), typeof(string).GetNonNullableOrEnumerableType()); } } diff --git a/src/tests/EntityGraphQL.Tests/Util/ExpressionExtractorTests.cs b/src/tests/EntityGraphQL.Tests/Util/ExpressionExtractorTests.cs index 5c1529c2..9b33cc81 100644 --- a/src/tests/EntityGraphQL.Tests/Util/ExpressionExtractorTests.cs +++ b/src/tests/EntityGraphQL.Tests/Util/ExpressionExtractorTests.cs @@ -17,7 +17,7 @@ public void ExtractMemberExpression() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Single(extracted); - Assert.Equal("x_TotalPeople", extracted.First().Key); + Assert.Equal("egql__x_TotalPeople", extracted.First().Key); Assert.Equal(expression.Body, extracted.First().Value.First()); } @@ -30,7 +30,7 @@ public void ExtractMemberExpressionInMethod() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Single(extracted); - Assert.Equal("person_Birthday", extracted.First().Key); + Assert.Equal("egql__person_Birthday", extracted.First().Key); Assert.Equal(((MethodCallExpression)expression.Body).Arguments[0], extracted.First().Value.First()); } @@ -43,9 +43,9 @@ public void ExtractLongMemberExpressionSameNameInMethod() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Equal(2, extracted.Count); - Assert.Equal("p_Name", extracted.First().Key); + Assert.Equal("egql__p_Name", extracted.First().Key); Assert.Equal(((MethodCallExpression)expression.Body).Arguments[0], extracted.First().Value.First()); - Assert.Equal("p_Children_First___Name", extracted.ElementAt(1).Key); + Assert.Equal("egql__p_Children_First___Name", extracted.ElementAt(1).Key); Assert.Equal(((MethodCallExpression)expression.Body).Arguments[1], extracted.ElementAt(1).Value.First()); } @@ -58,7 +58,7 @@ public void ExtractExpressionInAsync() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Single(extracted); - Assert.Equal("ctx_Birthday", extracted.First().Key); + Assert.Equal("egql__ctx_Birthday", extracted.First().Key); Assert.Equal(((MethodCallExpression)((MethodCallExpression)((MethodCallExpression)expression.Body).Object!).Object!).Arguments[0], extracted.First().Value.First()); } @@ -71,7 +71,7 @@ public void ExtractExpressionConditional() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Single(extracted); - Assert.Equal("project_Updated", extracted.First().Key); + Assert.Equal("egql__project_Updated", extracted.First().Key); Assert.Equal(2, extracted.First().Value.Count); Assert.Equal(((BinaryExpression)((ConditionalExpression)expression.Body).Test).Left, extracted.First().Value.First()); Assert.Equal( @@ -89,7 +89,7 @@ public void ExtractExpressionNullableType() var extracted = extractor.Extract(expression.Body, expression.Parameters[0], false); Assert.NotNull(extracted); Assert.Single(extracted); - Assert.Equal("user_RelationId", extracted.First().Key); + Assert.Equal("egql__user_RelationId", extracted.First().Key); Assert.Equal(2, extracted.First().Value.Count); Assert.Equal(((MemberExpression)((ConditionalExpression)expression.Body).Test).Expression, extracted.First().Value.First()); Assert.Equal( diff --git a/src/tests/EntityGraphQL.Tests/Util/ExpressionUtilTests.cs b/src/tests/EntityGraphQL.Tests/Util/ExpressionUtilTests.cs index c3a5b71b..4ceaab72 100644 --- a/src/tests/EntityGraphQL.Tests/Util/ExpressionUtilTests.cs +++ b/src/tests/EntityGraphQL.Tests/Util/ExpressionUtilTests.cs @@ -1,32 +1,31 @@ using EntityGraphQL.Compiler.Util; using Xunit; -namespace EntityGraphQL.Tests.Util +namespace EntityGraphQL.Tests.Util; + +public class ExpressionUtilTests { - public class ExpressionUtilTests + [Fact] + public void TestMergeTypesObj1Null() { - [Fact] - public void TestMergeTypesObj1Null() - { - var obj2 = new { hi = "world" }; + var obj2 = new { hi = "world" }; - var result = ExpressionUtil.MergeTypes(null, obj2.GetType()); + var result = ExpressionUtil.MergeTypes(null, obj2.GetType()); - Assert.NotNull(result); - Assert.Single(result.GetProperties()); - } + Assert.NotNull(result); + Assert.Single(result.GetProperties()); + } - [Fact] - public void TestMergeTypes() - { - object obj1 = new { world = "hi" }; + [Fact] + public void TestMergeTypes() + { + object obj1 = new { world = "hi" }; - var obj2 = new { hi = "world" }; + var obj2 = new { hi = "world" }; - var result = ExpressionUtil.MergeTypes(obj1.GetType(), obj2.GetType()); + var result = ExpressionUtil.MergeTypes(obj1.GetType(), obj2.GetType()); - Assert.NotNull(result); - Assert.Equal(2, result.GetFields().Length); - } + Assert.NotNull(result); + Assert.Equal(2, result.GetFields().Length); } } diff --git a/src/tests/EntityGraphQL.Tests/Util/NullableReferenceTypeTests.cs b/src/tests/EntityGraphQL.Tests/Util/NullableReferenceTypeTests.cs index 08a566c1..07f799d5 100644 --- a/src/tests/EntityGraphQL.Tests/Util/NullableReferenceTypeTests.cs +++ b/src/tests/EntityGraphQL.Tests/Util/NullableReferenceTypeTests.cs @@ -104,7 +104,7 @@ public void TestNullableWithNullableRefEnabled() } } } - " + ", }; var res = schema.ExecuteRequestWithContext(gql, new WithNullableRefEnabled(), null, null); diff --git a/src/tests/EntityGraphQL.Tests/ValidationTests.cs b/src/tests/EntityGraphQL.Tests/ValidationTests.cs index 8a86553e..c3cbc0aa 100644 --- a/src/tests/EntityGraphQL.Tests/ValidationTests.cs +++ b/src/tests/EntityGraphQL.Tests/ValidationTests.cs @@ -119,7 +119,7 @@ public void TestGraphQLValidatorWithInlineArgs() @"mutation Mutate($arg: CastMemberArg) { updateCastMemberWithGraphQLValidator(arg: $arg) }", - Variables = new QueryVariables() { { "arg", new { Actor = "Neil", Character = "Barn" } } } + Variables = new QueryVariables() { { "arg", new { Actor = "Neil", Character = "Barn" } } }, }; var serviceCollection = new ServiceCollection(); @@ -479,7 +479,7 @@ internal class ValidationTestsMutations [GraphQLMutation] public static Expression> AddMovie(MovieArg movie) { - var newMovie = new Movie { Id = new Random().Next(), Title = movie.Title, }; + var newMovie = new Movie { Id = new Random().Next(), Title = movie.Title }; return c => c.Movies.SingleOrDefault(m => m.Id == newMovie.Id); } @@ -490,7 +490,7 @@ internal class ValidationTestsMutations [StringLength(5, ErrorMessage = "Rating must be less than 5 characters")] string rating ) { - var newMovie = new Movie { Id = new Random().Next(), Title = title, }; + var newMovie = new Movie { Id = new Random().Next(), Title = title }; return c => c.Movies.SingleOrDefault(m => m.Id == newMovie.Id); } diff --git a/src/tests/Test.App/Program.cs b/src/tests/Test.App/Program.cs new file mode 100644 index 00000000..a187b979 --- /dev/null +++ b/src/tests/Test.App/Program.cs @@ -0,0 +1,41 @@ +using EntityGraphQL; +using EntityGraphQL.AspNet; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddGraphQLValidator(); +builder.Services.AddGraphQLSchema(configure => +{ + configure.ConfigureSchema = schema => + { + schema + .Mutation() + .Add( + "mutationFail", + (TestQueryType db, IGraphQLValidator validator) => + { + validator.AddError("This is a test error"); + if (validator.HasErrors) + { + return null; + } + return db.Hello; + } + ) + .IsNullable(false); + }; +}); +builder.Services.AddScoped(); + +var app = builder.Build(); + +app.MapGraphQL(followSpec: true); + +app.Run(); + +public partial class Program; + +public class TestQueryType +{ + public string Hello { get; set; } = "world"; +} diff --git a/src/tests/Test.App/Test.App.csproj b/src/tests/Test.App/Test.App.csproj new file mode 100644 index 00000000..e304c0a1 --- /dev/null +++ b/src/tests/Test.App/Test.App.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/src/tests/Test.App/appsettings.Development.json b/src/tests/Test.App/appsettings.Development.json new file mode 100644 index 00000000..ff66ba6b --- /dev/null +++ b/src/tests/Test.App/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/tests/Test.App/appsettings.json b/src/tests/Test.App/appsettings.json new file mode 100644 index 00000000..4d566948 --- /dev/null +++ b/src/tests/Test.App/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}