Compare commits

..

No commits in common. "main" and "10.1.1-rc1" have entirely different histories.

46 changed files with 3435 additions and 3596 deletions

View File

@ -0,0 +1,50 @@
{
"permissions": {
"allow": [
"Bash(dotnet clean:*)",
"Bash(dotnet run)",
"Bash(dotnet add:*)",
"Bash(timeout 5 dotnet run:*)",
"Bash(dotnet remove:*)",
"Bash(netstat:*)",
"Bash(findstr:*)",
"Bash(cat:*)",
"Bash(taskkill:*)",
"WebSearch",
"Bash(dotnet tool install:*)",
"Bash(protogen:*)",
"Bash(timeout 15 dotnet run:*)",
"Bash(where:*)",
"Bash(timeout 30 dotnet run:*)",
"Bash(timeout 60 dotnet run:*)",
"Bash(timeout 120 dotnet run:*)",
"Bash(git add:*)",
"Bash(curl:*)",
"Bash(timeout 3 cmd:*)",
"Bash(timeout:*)",
"Bash(tasklist:*)",
"Bash(dotnet build:*)",
"Bash(dotnet --list-sdks:*)",
"Bash(dotnet sln:*)",
"Bash(pkill:*)",
"Bash(python3:*)",
"Bash(grpcurl:*)",
"Bash(lsof:*)",
"Bash(xargs kill -9)",
"Bash(dotnet run:*)",
"Bash(find:*)",
"Bash(dotnet pack:*)",
"Bash(unzip:*)",
"WebFetch(domain:andrewlock.net)",
"WebFetch(domain:github.com)",
"WebFetch(domain:stackoverflow.com)",
"WebFetch(domain:www.kenmuse.com)",
"WebFetch(domain:blog.rsuter.com)",
"WebFetch(domain:natemcmaster.com)",
"WebFetch(domain:www.nuget.org)",
"Bash(mkdir:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -1,96 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{csproj,props,targets,xml}]
indent_size = 2
[*.{json,yml,yaml}]
indent_size = 2
[*.proto]
indent_size = 2
[*.cs]
# Namespace
csharp_style_namespace_declarations = file_scoped:warning
# Braces — Allman style
csharp_new_line_before_open_brace = all
# Usings
dotnet_sort_system_directives_first = true
csharp_using_directive_placement = outside_namespace:warning
# var preferences — use var when type is apparent
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Expression bodies — prefer for simple members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null checking
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences — exclude interface members (netstandard2.1 compat)
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
# Field naming — _camelCase for private fields
dotnet_naming_rule.private_fields_should_be_camel_case.severity = warning
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_underscore
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, private_protected
dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_style.camel_case_underscore.required_prefix = _
dotnet_naming_style.camel_case_underscore.capitalization = camel_case
# Constants — PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = pascal_case
dotnet_naming_symbols.constants.applicable_kinds = field
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.pascal_case.capitalization = pascal_case
# Interfaces — I prefix
dotnet_naming_rule.interfaces_should_begin_with_i.severity = warning
dotnet_naming_rule.interfaces_should_begin_with_i.symbols = interfaces
dotnet_naming_rule.interfaces_should_begin_with_i.style = begins_with_i
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case
# Async methods — Async suffix
dotnet_naming_rule.async_methods_should_end_with_async.severity = suggestion
dotnet_naming_rule.async_methods_should_end_with_async.symbols = async_methods
dotnet_naming_rule.async_methods_should_end_with_async.style = ends_with_async
dotnet_naming_symbols.async_methods.applicable_kinds = method
dotnet_naming_symbols.async_methods.required_modifiers = async
dotnet_naming_style.ends_with_async.required_suffix = Async
dotnet_naming_style.ends_with_async.capitalization = pascal_case

View File

@ -1,6 +1,6 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to AI agents when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview ## Project Overview

View File

@ -4,15 +4,6 @@
Our implementation of query and command responsibility segregation (CQRS). Our implementation of query and command responsibility segregation (CQRS).
## Where This Fits
This is a backend framework of the [Svrnty Agent System](../README.md).
**Layer**: Framework
**Depends on**: Nothing (standalone .NET framework)
**Depended on by**: a-gent-app (backend services), flutter_cqrs_datasource (client)
**Git**: [git.openharbor.io/svrnty/dotnet-cqrs](https://git.openharbor.io/svrnty/dotnet-cqrs)
## Getting Started ## Getting Started
> Install nuget package to your awesome project. > Install nuget package to your awesome project.

View File

@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Attributes; namespace Svrnty.CQRS.Abstractions.Attributes;

View File

@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Attributes; namespace Svrnty.CQRS.Abstractions.Attributes;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Svrnty.CQRS.Abstractions.Attributes; using Svrnty.CQRS.Abstractions.Attributes;

View File

@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Discovery; namespace Svrnty.CQRS.Abstractions.Discovery;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Abstractions.Discovery; namespace Svrnty.CQRS.Abstractions.Discovery;

View File

@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Discovery; namespace Svrnty.CQRS.Abstractions.Discovery;
@ -10,4 +10,4 @@ public interface IQueryMeta
Type QueryResultType { get; } Type QueryResultType { get; }
string Category { get; } string Category { get; }
string LowerCamelCaseName { get; } string LowerCamelCaseName { get; }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Svrnty.CQRS.Abstractions.Attributes; using Svrnty.CQRS.Abstractions.Attributes;

View File

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Svrnty.CQRS.Abstractions; namespace Svrnty.CQRS.Abstractions;
@ -13,4 +13,4 @@ public interface ICommandHandler<in TCommand, TCommandResult>
where TCommand : class where TCommand : class
{ {
Task<TCommandResult> HandleAsync(TCommand command, CancellationToken cancellationToken = default); Task<TCommandResult> HandleAsync(TCommand command, CancellationToken cancellationToken = default);
} }

View File

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Svrnty.CQRS.Abstractions; namespace Svrnty.CQRS.Abstractions;
@ -7,4 +7,4 @@ public interface IQueryHandler<in TQuery, TQueryResult>
where TQuery : class where TQuery : class
{ {
Task<TQueryResult> HandleAsync(TQuery query, CancellationToken cancellationToken = default); Task<TQueryResult> HandleAsync(TQuery query, CancellationToken cancellationToken = default);
} }

View File

@ -1,8 +1,8 @@
namespace Svrnty.CQRS.Abstractions.Security; namespace Svrnty.CQRS.Abstractions.Security;
public enum AuthorizationResult public enum AuthorizationResult
{ {
Unauthorized, Unauthorized,
Forbidden, Forbidden,
Allowed Allowed
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,4 +7,4 @@ namespace Svrnty.CQRS.Abstractions.Security;
public interface ICommandAuthorizationService public interface ICommandAuthorizationService
{ {
Task<AuthorizationResult> IsAllowedAsync(Type commandType, CancellationToken cancellationToken = default); Task<AuthorizationResult> IsAllowedAsync(Type commandType, CancellationToken cancellationToken = default);
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,4 +7,4 @@ namespace Svrnty.CQRS.Abstractions.Security;
public interface IQueryAuthorizationService public interface IQueryAuthorizationService
{ {
Task<AuthorizationResult> IsAllowedAsync(Type queryType, CancellationToken cancellationToken = default); Task<AuthorizationResult> IsAllowedAsync(Type queryType, CancellationToken cancellationToken = default);
} }

View File

@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;
@ -47,4 +47,4 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.DynamicQuery.Abstractions; namespace Svrnty.CQRS.DynamicQuery.Abstractions;

View File

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,4 +13,4 @@ public interface IAlterQueryableService<TSource, TDestination, in TParams>
where TParams : class where TParams : class
{ {
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQueryParams<TParams> dynamicQuery, CancellationToken cancellationToken = default); Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQueryParams<TParams> dynamicQuery, CancellationToken cancellationToken = default);
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
namespace Svrnty.CQRS.DynamicQuery.Abstractions; namespace Svrnty.CQRS.DynamicQuery.Abstractions;
@ -15,7 +15,7 @@ public interface IDynamicQuery<TSource, TDestination, out TParams> : IDynamicQue
where TDestination : class where TDestination : class
where TParams : class where TParams : class
{ {
} }
public interface IDynamicQuery public interface IDynamicQuery
@ -26,4 +26,4 @@ public interface IDynamicQuery
List<IAggregate> GetAggregates(); List<IAggregate> GetAggregates();
int? GetPage(); int? GetPage();
int? GetPageSize(); int? GetPageSize();
} }

View File

@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.DynamicQuery.Abstractions; namespace Svrnty.CQRS.DynamicQuery.Abstractions;
public interface IDynamicQueryInterceptorProvider<TSource, TDestination> public interface IDynamicQueryInterceptorProvider<TSource, TDestination>
{ {
IEnumerable<Type> GetInterceptorsTypes(); IEnumerable<Type> GetInterceptorsTypes();
} }

View File

@ -1,4 +1,4 @@
namespace Svrnty.CQRS.DynamicQuery.Abstractions; namespace Svrnty.CQRS.DynamicQuery.Abstractions;
public interface IDynamicQueryParams<out TParams> public interface IDynamicQueryParams<out TParams>
where TParams : class where TParams : class

View File

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,4 +7,4 @@ namespace Svrnty.CQRS.DynamicQuery.Abstractions;
public interface IQueryableProvider<TSource> public interface IQueryableProvider<TSource>
{ {
Task<IQueryable<TSource>> GetQueryableAsync(object query, CancellationToken cancellationToken = default); Task<IQueryable<TSource>> GetQueryableAsync(object query, CancellationToken cancellationToken = default);
} }

View File

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.DynamicQuery.Core;
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.Abstractions.Attributes; using Svrnty.CQRS.Abstractions.Attributes;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;
@ -15,6 +14,7 @@ using Svrnty.CQRS.Abstractions.Security;
using Svrnty.CQRS.DynamicQuery; using Svrnty.CQRS.DynamicQuery;
using Svrnty.CQRS.DynamicQuery.Abstractions; using Svrnty.CQRS.DynamicQuery.Abstractions;
using Svrnty.CQRS.DynamicQuery.Discover; using Svrnty.CQRS.DynamicQuery.Discover;
using PoweredSoft.DynamicQuery.Core;
namespace Svrnty.CQRS.DynamicQuery.MinimalApi; namespace Svrnty.CQRS.DynamicQuery.MinimalApi;

View File

@ -1,4 +1,4 @@
using System; using System;
using Pluralize.NET; using Pluralize.NET;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;
@ -7,7 +7,7 @@ namespace Svrnty.CQRS.DynamicQuery.Discover;
public class DynamicQueryMeta(Type queryType, Type serviceType, Type queryResultType) public class DynamicQueryMeta(Type queryType, Type serviceType, Type queryResultType)
: QueryMeta(queryType, serviceType, queryResultType) : QueryMeta(queryType, serviceType, queryResultType)
{ {
public Type SourceType => QueryType.GetGenericArguments()[0]; public Type SourceType => QueryType.GetGenericArguments()[0];
public Type DestinationType => QueryType.GetGenericArguments()[1]; public Type DestinationType => QueryType.GetGenericArguments()[1];
public override string Category => "DynamicQuery"; public override string Category => "DynamicQuery";
public override string Name public override string Name

View File

@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Svrnty.CQRS.DynamicQuery.Abstractions;
using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using Svrnty.CQRS.DynamicQuery.Abstractions;
namespace Svrnty.CQRS.DynamicQuery; namespace Svrnty.CQRS.DynamicQuery;

View File

@ -1,6 +1,6 @@
using System;
using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using System;
namespace Svrnty.CQRS.DynamicQuery; namespace Svrnty.CQRS.DynamicQuery;

View File

@ -1,23 +1,23 @@
using Svrnty.CQRS.DynamicQuery.Abstractions;
using PoweredSoft.DynamicQuery.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using PoweredSoft.DynamicQuery.Core;
using Svrnty.CQRS.DynamicQuery.Abstractions;
namespace Svrnty.CQRS.DynamicQuery; namespace Svrnty.CQRS.DynamicQuery;
public class DynamicQueryHandler<TSource, TDestination> public class DynamicQueryHandler<TSource, TDestination>
: DynamicQueryHandlerBase<TSource, TDestination>, : DynamicQueryHandlerBase<TSource, TDestination>,
Svrnty.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>> Svrnty.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>>
where TSource : class where TSource : class
where TDestination : class where TDestination : class
{ {
public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync, public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync,
IEnumerable<IQueryableProvider<TSource>> queryableProviders, IEnumerable<IQueryableProvider<TSource>> queryableProviders,
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices, IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders, IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider) IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider)
{ {
} }
@ -29,7 +29,7 @@ public class DynamicQueryHandler<TSource, TDestination>
} }
public class DynamicQueryHandler<TSource, TDestination, TParams> public class DynamicQueryHandler<TSource, TDestination, TParams>
: DynamicQueryHandlerBase<TSource, TDestination>, : DynamicQueryHandlerBase<TSource, TDestination>,
Svrnty.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>> Svrnty.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>>
where TSource : class where TSource : class
where TDestination : class where TDestination : class
@ -37,10 +37,10 @@ public class DynamicQueryHandler<TSource, TDestination, TParams>
{ {
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams; private readonly IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams;
public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync, public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync,
IEnumerable<IQueryableProvider<TSource>> queryableProviders, IEnumerable<IQueryableProvider<TSource>> queryableProviders,
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices, IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams, IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams,
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders, IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider) IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider)
{ {
@ -49,7 +49,7 @@ public class DynamicQueryHandler<TSource, TDestination, TParams>
protected override async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken) protected override async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
{ {
source = await base.AlterSourceAsync(source, query, cancellationToken); source = await base.AlterSourceAsync(source, query, cancellationToken);
if (query is IDynamicQueryParams<TParams> withParams) if (query is IDynamicQueryParams<TParams> withParams)
{ {

View File

@ -1,14 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Svrnty.CQRS.DynamicQuery.Abstractions;
using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using Svrnty.CQRS.DynamicQuery.Abstractions;
namespace Svrnty.CQRS.DynamicQuery; namespace Svrnty.CQRS.DynamicQuery;
@ -19,13 +16,10 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
private readonly IQueryHandlerAsync _queryHandlerAsync; private readonly IQueryHandlerAsync _queryHandlerAsync;
private readonly IEnumerable<IQueryableProvider<TSource>> _queryableProviders; private readonly IEnumerable<IQueryableProvider<TSource>> _queryableProviders;
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination>> _alterQueryableServices; private readonly IEnumerable<IAlterQueryableService<TSource, TDestination>> _alterQueryableServices;
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> _dynamicQueryInterceptorProviders;
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>>
_dynamicQueryInterceptorProviders;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync, public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync,
IEnumerable<IQueryableProvider<TSource>> queryableProviders, IEnumerable<IQueryableProvider<TSource>> queryableProviders,
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices, IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders, IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
@ -38,8 +32,7 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default)
{ {
if (_queryableProviders.Any()) if (_queryableProviders.Any())
{ {
@ -63,8 +56,7 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
yield return _serviceProvider.GetService(type) as IQueryInterceptor; yield return _serviceProvider.GetService(type) as IQueryInterceptor;
} }
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query, protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default)
{ {
var source = await GetQueryableAsync(query, cancellationToken); var source = await GetQueryableAsync(query, cancellationToken);
source = await AlterSourceAsync(source, query, cancellationToken); source = await AlterSourceAsync(source, query, cancellationToken);
@ -75,13 +67,11 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
_queryHandlerAsync.AddInterceptor(interceptor); _queryHandlerAsync.AddInterceptor(interceptor);
var criteria = CreateCriteriaFromQuery(query); var criteria = CreateCriteriaFromQuery(query);
var result = var result = await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
return result; return result;
} }
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
CancellationToken cancellationToken)
{ {
foreach (var t in _alterQueryableServices) foreach (var t in _alterQueryableServices)
source = await t.AlterQueryableAsync(source, query, cancellationToken); source = await t.AlterQueryableAsync(source, query, cancellationToken);
@ -91,94 +81,16 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query) protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query)
{ {
var filters = query?.GetFilters() ?? new List<IFilter>();
ConvertFilterValuesToPropertyTypes(filters);
var criteria = new QueryCriteria var criteria = new QueryCriteria
{ {
Page = query?.GetPage(), Page = query?.GetPage(),
PageSize = query?.GetPageSize(), PageSize = query?.GetPageSize(),
Filters = filters, Filters = query?.GetFilters() ?? new List<IFilter>(),
Sorts = query?.GetSorts() ?? new List<ISort>(), Sorts = query?.GetSorts() ?? new List<ISort>(),
Groups = query?.GetGroups() ?? new List<IGroup>(), Groups = query?.GetGroups() ?? new List<IGroup>(),
Aggregates = query?.GetAggregates() ?? new List<IAggregate>() Aggregates = query?.GetAggregates() ?? new List<IAggregate>()
}; };
return criteria; return criteria;
} }
/// <summary>
/// Converts string filter values to the correct CLR type based on TSource property types.
/// This handles the case where transport layers (e.g. gRPC) pass all values as strings,
/// but PoweredSoft.DynamicLinq needs the actual type to build LINQ expressions.
/// </summary>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2087",
Justification = "TSource properties are preserved by EF Core and DynamicLinq usage")]
private static void ConvertFilterValuesToPropertyTypes(List<IFilter> filters)
{
for (var i = filters.Count - 1; i >= 0; i--)
{
var filter = filters[i];
if (filter is SimpleFilter simpleFilter)
{
if (simpleFilter.Value == null)
{
filters.RemoveAt(i);
continue;
}
if (simpleFilter.Value is string strValue && !string.IsNullOrEmpty(strValue))
{
var propertyType = ResolvePropertyType(typeof(TSource), simpleFilter.Path);
if (propertyType == null)
continue;
var targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
if (targetType == typeof(DateTime))
{
if (DateTime.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal,
out var dt))
{
simpleFilter.Value = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
}
}
else if (targetType == typeof(DateTimeOffset))
{
if (DateTimeOffset.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.None,
out var dto))
{
simpleFilter.Value = dto;
}
}
}
}
else if (filter is CompositeFilter compositeFilter && compositeFilter.Filters != null)
{
ConvertFilterValuesToPropertyTypes(compositeFilter.Filters);
}
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070",
Justification = "Property types are preserved by EF Core and DynamicLinq usage")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075",
Justification = "Nested property type resolution is inherently dynamic")]
static Type? ResolvePropertyType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, string? path)
{
if (string.IsNullOrEmpty(path))
return null;
var currentType = type;
foreach (var part in path.Split('.'))
{
var property = currentType.GetProperty(part,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
return null;
currentType = property.PropertyType;
}
return currentType;
}
}
} }

View File

@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using PoweredSoft.Data.Core; using PoweredSoft.Data.Core;
@ -91,10 +91,10 @@ public static class ServiceCollectionExtensions
where TParams : class where TParams : class
=> AddDynamicQueryWithParams<TSourceAndDestination, TSourceAndDestination, TParams>(services, name: name); => AddDynamicQueryWithParams<TSourceAndDestination, TSourceAndDestination, TParams>(services, name: name);
public static IServiceCollection AddDynamicQueryWithParams<TSource, TDestination, TParams>(this IServiceCollection services, string name = null) public static IServiceCollection AddDynamicQueryWithParams<TSource, TDestination, TParams>(this IServiceCollection services, string name = null)
where TSource : class where TSource : class
where TDestination : class where TDestination : class
where TParams : class where TParams : class
{ {
// add query handler. // add query handler.
services.AddTransient<IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>>, DynamicQueryHandler<TSource, TDestination, TParams>>(); services.AddTransient<IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>>, DynamicQueryHandler<TSource, TDestination, TParams>>();
@ -133,7 +133,7 @@ public static class ServiceCollectionExtensions
where TParams : class where TParams : class
where TService : class, IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams> where TService : class, IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams>
{ {
return services.AddTransient<IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams>, TService>(); return services.AddTransient<IAlterQueryableService< TSourceAndTDestination, TSourceAndTDestination, TParams>, TService>();
} }
public static IServiceCollection AddAlterQueryableWithParams<TSource, TDestination, TParams, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService> public static IServiceCollection AddAlterQueryableWithParams<TSource, TDestination, TParams, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>

View File

@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using FluentValidation; using FluentValidation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
@ -39,7 +39,7 @@ public static class ServiceCollectionExtensions
{ {
services.AddQuery<TQuery, TQueryResult, TQueryHandler>() services.AddQuery<TQuery, TQueryResult, TQueryHandler>()
.AddFluentValidator<TQuery, TValidator>(); .AddFluentValidator<TQuery, TValidator>();
return services; return services;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +1,102 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Grpc.Generators.Helpers; namespace Svrnty.CQRS.Grpc.Generators.Helpers
internal static class ProtoTypeMapper
{ {
private static readonly Dictionary<string, string> TypeMap = new Dictionary<string, string> internal static class ProtoTypeMapper
{ {
// Primitives private static readonly Dictionary<string, string> TypeMap = new Dictionary<string, string>
{ "System.String", "string" },
{ "System.Boolean", "bool" },
{ "System.Int32", "int32" },
{ "System.Int64", "int64" },
{ "System.UInt32", "uint32" },
{ "System.UInt64", "uint64" },
{ "System.Single", "float" },
{ "System.Double", "double" },
{ "System.Byte", "uint32" },
{ "System.SByte", "int32" },
{ "System.Int16", "int32" },
{ "System.UInt16", "uint32" },
{ "System.Decimal", "string" }, // Decimal as string to preserve precision
{ "System.DateTime", "int64" }, // Unix timestamp
{ "System.DateTimeOffset", "int64" }, // Unix timestamp
{ "System.Guid", "string" },
{ "System.TimeSpan", "int64" }, // Ticks
// Nullable variants
{ "System.Boolean?", "bool" },
{ "System.Int32?", "int32" },
{ "System.Int64?", "int64" },
{ "System.UInt32?", "uint32" },
{ "System.UInt64?", "uint64" },
{ "System.Single?", "float" },
{ "System.Double?", "double" },
{ "System.Byte?", "uint32" },
{ "System.SByte?", "int32" },
{ "System.Int16?", "int32" },
{ "System.UInt16?", "uint32" },
{ "System.Decimal?", "string" },
{ "System.DateTime?", "int64" },
{ "System.DateTimeOffset?", "int64" },
{ "System.Guid?", "string" },
{ "System.TimeSpan?", "int64" },
};
public static string MapToProtoType(string csharpType, out bool isRepeated, out bool isOptional)
{
isRepeated = false;
isOptional = false;
// Handle byte[] as bytes proto type (NOT repeated uint32)
if (csharpType == "System.Byte[]" || csharpType == "byte[]" || csharpType == "Byte[]")
{ {
return "bytes"; // Primitives
} { "System.String", "string" },
{ "System.Boolean", "bool" },
{ "System.Int32", "int32" },
{ "System.Int64", "int64" },
{ "System.UInt32", "uint32" },
{ "System.UInt64", "uint64" },
{ "System.Single", "float" },
{ "System.Double", "double" },
{ "System.Byte", "uint32" },
{ "System.SByte", "int32" },
{ "System.Int16", "int32" },
{ "System.UInt16", "uint32" },
{ "System.Decimal", "string" }, // Decimal as string to preserve precision
{ "System.DateTime", "int64" }, // Unix timestamp
{ "System.DateTimeOffset", "int64" }, // Unix timestamp
{ "System.Guid", "string" },
{ "System.TimeSpan", "int64" }, // Ticks
// Handle arrays // Nullable variants
if (csharpType.EndsWith("[]")) { "System.Boolean?", "bool" },
{ "System.Int32?", "int32" },
{ "System.Int64?", "int64" },
{ "System.UInt32?", "uint32" },
{ "System.UInt64?", "uint64" },
{ "System.Single?", "float" },
{ "System.Double?", "double" },
{ "System.Byte?", "uint32" },
{ "System.SByte?", "int32" },
{ "System.Int16?", "int32" },
{ "System.UInt16?", "uint32" },
{ "System.Decimal?", "string" },
{ "System.DateTime?", "int64" },
{ "System.DateTimeOffset?", "int64" },
{ "System.Guid?", "string" },
{ "System.TimeSpan?", "int64" },
};
public static string MapToProtoType(string csharpType, out bool isRepeated, out bool isOptional)
{ {
isRepeated = true; isRepeated = false;
var elementType = csharpType.Substring(0, csharpType.Length - 2); isOptional = false;
return MapToProtoType(elementType, out _, out _);
}
// Handle generic collections // Handle byte[] as bytes proto type (NOT repeated uint32)
if (csharpType.StartsWith("System.Collections.Generic.List<") || if (csharpType == "System.Byte[]" || csharpType == "byte[]" || csharpType == "Byte[]")
csharpType.StartsWith("System.Collections.Generic.IList<") || {
csharpType.StartsWith("System.Collections.Generic.IEnumerable<") || return "bytes";
csharpType.StartsWith("System.Collections.Generic.ICollection<")) }
{
isRepeated = true;
var startIndex = csharpType.IndexOf('<') + 1;
var endIndex = csharpType.LastIndexOf('>');
var elementType = csharpType.Substring(startIndex, endIndex - startIndex);
return MapToProtoType(elementType, out _, out _);
}
// Handle nullable value types // Handle arrays
if (csharpType.EndsWith("?")) if (csharpType.EndsWith("[]"))
{ {
isOptional = true; isRepeated = true;
} var elementType = csharpType.Substring(0, csharpType.Length - 2);
return MapToProtoType(elementType, out _, out _);
}
// Check if it's a known primitive type // Handle generic collections
if (TypeMap.TryGetValue(csharpType, out var protoType)) if (csharpType.StartsWith("System.Collections.Generic.List<") ||
{ csharpType.StartsWith("System.Collections.Generic.IList<") ||
return protoType; csharpType.StartsWith("System.Collections.Generic.IEnumerable<") ||
} csharpType.StartsWith("System.Collections.Generic.ICollection<"))
{
isRepeated = true;
var startIndex = csharpType.IndexOf('<') + 1;
var endIndex = csharpType.LastIndexOf('>');
var elementType = csharpType.Substring(startIndex, endIndex - startIndex);
return MapToProtoType(elementType, out _, out _);
}
// For unknown types, assume it's a custom message type // Handle nullable value types
// Extract just the type name without namespace if (csharpType.EndsWith("?"))
var lastDot = csharpType.LastIndexOf('.'); {
if (lastDot >= 0) isOptional = true;
{ }
return csharpType.Substring(lastDot + 1).Replace("?", "");
}
return csharpType.Replace("?", ""); // Check if it's a known primitive type
if (TypeMap.TryGetValue(csharpType, out var protoType))
{
return protoType;
}
// For unknown types, assume it's a custom message type
// Extract just the type name without namespace
var lastDot = csharpType.LastIndexOf('.');
if (lastDot >= 0)
{
return csharpType.Substring(lastDot + 1).Replace("?", "");
}
return csharpType.Replace("?", "");
}
} }
} }

View File

@ -1,82 +1,83 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
namespace Svrnty.CQRS.Grpc.Generators.Models; namespace Svrnty.CQRS.Grpc.Generators.Models
public class CommandInfo
{ {
public string Name { get; set; } public class CommandInfo
public string FullyQualifiedName { get; set; }
public string Namespace { get; set; }
public List<PropertyInfo> Properties { get; set; }
public string? ResultType { get; set; }
public string? ResultFullyQualifiedName { get; set; }
public bool HasResult => ResultType != null;
public string HandlerInterfaceName { get; set; }
public List<PropertyInfo> ResultProperties { get; set; }
public bool IsResultPrimitiveType { get; set; }
public CommandInfo()
{ {
Name = string.Empty; public string Name { get; set; }
FullyQualifiedName = string.Empty; public string FullyQualifiedName { get; set; }
Namespace = string.Empty; public string Namespace { get; set; }
Properties = new List<PropertyInfo>(); public List<PropertyInfo> Properties { get; set; }
HandlerInterfaceName = string.Empty; public string? ResultType { get; set; }
ResultProperties = new List<PropertyInfo>(); public string? ResultFullyQualifiedName { get; set; }
IsResultPrimitiveType = false; public bool HasResult => ResultType != null;
} public string HandlerInterfaceName { get; set; }
} public List<PropertyInfo> ResultProperties { get; set; }
public bool IsResultPrimitiveType { get; set; }
public class PropertyInfo
{ public CommandInfo()
public string Name { get; set; } {
public string Type { get; set; } Name = string.Empty;
public string FullyQualifiedType { get; set; } FullyQualifiedName = string.Empty;
public string ProtoType { get; set; } Namespace = string.Empty;
public int FieldNumber { get; set; } Properties = new List<PropertyInfo>();
public bool IsComplexType { get; set; } HandlerInterfaceName = string.Empty;
public List<PropertyInfo> NestedProperties { get; set; } ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false;
// Type conversion metadata }
public bool IsEnum { get; set; } }
public bool IsList { get; set; }
public bool IsNullable { get; set; } public class PropertyInfo
public bool IsDecimal { get; set; } {
public bool IsDateTime { get; set; } public string Name { get; set; }
public bool IsDateTimeOffset { get; set; } public string Type { get; set; }
public bool IsGuid { get; set; } public string FullyQualifiedType { get; set; }
public bool IsJsonElement { get; set; } public string ProtoType { get; set; }
public bool IsBinaryType { get; set; } // Stream, byte[], MemoryStream public int FieldNumber { get; set; }
public bool IsStream { get; set; } // Specifically Stream types (not byte[]) public bool IsComplexType { get; set; }
public bool IsReadOnly { get; set; } // Read-only/computed properties should be skipped public List<PropertyInfo> NestedProperties { get; set; }
public bool IsValueTypeCollection { get; set; } // Value types that implement IList<T> (like NpgsqlPolygon)
public string? ElementType { get; set; } // Type conversion metadata
public bool IsElementComplexType { get; set; } public bool IsEnum { get; set; }
public bool IsElementGuid { get; set; } public bool IsList { get; set; }
public List<PropertyInfo>? ElementNestedProperties { get; set; } public bool IsNullable { get; set; }
public bool IsDecimal { get; set; }
public PropertyInfo() public bool IsDateTime { get; set; }
{ public bool IsDateTimeOffset { get; set; }
Name = string.Empty; public bool IsGuid { get; set; }
Type = string.Empty; public bool IsJsonElement { get; set; }
FullyQualifiedType = string.Empty; public bool IsBinaryType { get; set; } // Stream, byte[], MemoryStream
ProtoType = string.Empty; public bool IsStream { get; set; } // Specifically Stream types (not byte[])
IsComplexType = false; public bool IsReadOnly { get; set; } // Read-only/computed properties should be skipped
NestedProperties = new List<PropertyInfo>(); public bool IsValueTypeCollection { get; set; } // Value types that implement IList<T> (like NpgsqlPolygon)
IsEnum = false; public string? ElementType { get; set; }
IsList = false; public bool IsElementComplexType { get; set; }
IsNullable = false; public bool IsElementGuid { get; set; }
IsDecimal = false; public List<PropertyInfo>? ElementNestedProperties { get; set; }
IsDateTime = false;
IsDateTimeOffset = false; public PropertyInfo()
IsGuid = false; {
IsJsonElement = false; Name = string.Empty;
IsBinaryType = false; Type = string.Empty;
IsStream = false; FullyQualifiedType = string.Empty;
IsReadOnly = false; ProtoType = string.Empty;
IsValueTypeCollection = false; IsComplexType = false;
IsElementComplexType = false; NestedProperties = new List<PropertyInfo>();
IsElementGuid = false; IsEnum = false;
IsList = false;
IsNullable = false;
IsDecimal = false;
IsDateTime = false;
IsDateTimeOffset = false;
IsGuid = false;
IsJsonElement = false;
IsBinaryType = false;
IsStream = false;
IsReadOnly = false;
IsValueTypeCollection = false;
IsElementComplexType = false;
IsElementGuid = false;
}
} }
} }

View File

@ -1,27 +1,28 @@
namespace Svrnty.CQRS.Grpc.Generators.Models; namespace Svrnty.CQRS.Grpc.Generators.Models
public class DynamicQueryInfo
{ {
public string Name { get; set; } public class DynamicQueryInfo
public string SourceType { get; set; }
public string SourceTypeFullyQualified { get; set; }
public string DestinationType { get; set; }
public string DestinationTypeFullyQualified { get; set; }
public string? ParamsType { get; set; }
public string? ParamsTypeFullyQualified { get; set; }
public string HandlerInterfaceName { get; set; }
public string QueryInterfaceName { get; set; }
public bool HasParams { get; set; }
public DynamicQueryInfo()
{ {
Name = string.Empty; public string Name { get; set; }
SourceType = string.Empty; public string SourceType { get; set; }
SourceTypeFullyQualified = string.Empty; public string SourceTypeFullyQualified { get; set; }
DestinationType = string.Empty; public string DestinationType { get; set; }
DestinationTypeFullyQualified = string.Empty; public string DestinationTypeFullyQualified { get; set; }
HandlerInterfaceName = string.Empty; public string? ParamsType { get; set; }
QueryInterfaceName = string.Empty; public string? ParamsTypeFullyQualified { get; set; }
HasParams = false; public string HandlerInterfaceName { get; set; }
public string QueryInterfaceName { get; set; }
public bool HasParams { get; set; }
public DynamicQueryInfo()
{
Name = string.Empty;
SourceType = string.Empty;
SourceTypeFullyQualified = string.Empty;
DestinationType = string.Empty;
DestinationTypeFullyQualified = string.Empty;
HandlerInterfaceName = string.Empty;
QueryInterfaceName = string.Empty;
HasParams = false;
}
} }
} }

View File

@ -1,49 +1,50 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Grpc.Generators.Models; namespace Svrnty.CQRS.Grpc.Generators.Models
/// <summary>
/// Represents a discovered streaming notification type for proto/gRPC generation.
/// </summary>
public class NotificationInfo
{ {
/// <summary> /// <summary>
/// The notification type name (e.g., "InventoryChangeNotification"). /// Represents a discovered streaming notification type for proto/gRPC generation.
/// </summary> /// </summary>
public string Name { get; set; } public class NotificationInfo
/// <summary>
/// The fully qualified type name including namespace.
/// </summary>
public string FullyQualifiedName { get; set; }
/// <summary>
/// The namespace of the notification type.
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// The property name used as the subscription key (from [StreamingNotification] attribute).
/// </summary>
public string SubscriptionKeyProperty { get; set; }
/// <summary>
/// The subscription key property info.
/// </summary>
public PropertyInfo SubscriptionKeyInfo { get; set; }
/// <summary>
/// All properties of the notification type.
/// </summary>
public List<PropertyInfo> Properties { get; set; }
public NotificationInfo()
{ {
Name = string.Empty; /// <summary>
FullyQualifiedName = string.Empty; /// The notification type name (e.g., "InventoryChangeNotification").
Namespace = string.Empty; /// </summary>
SubscriptionKeyProperty = string.Empty; public string Name { get; set; }
SubscriptionKeyInfo = new PropertyInfo();
Properties = new List<PropertyInfo>(); /// <summary>
/// The fully qualified type name including namespace.
/// </summary>
public string FullyQualifiedName { get; set; }
/// <summary>
/// The namespace of the notification type.
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// The property name used as the subscription key (from [StreamingNotification] attribute).
/// </summary>
public string SubscriptionKeyProperty { get; set; }
/// <summary>
/// The subscription key property info.
/// </summary>
public PropertyInfo SubscriptionKeyInfo { get; set; }
/// <summary>
/// All properties of the notification type.
/// </summary>
public List<PropertyInfo> Properties { get; set; }
public NotificationInfo()
{
Name = string.Empty;
FullyQualifiedName = string.Empty;
Namespace = string.Empty;
SubscriptionKeyProperty = string.Empty;
SubscriptionKeyInfo = new PropertyInfo();
Properties = new List<PropertyInfo>();
}
} }
} }

View File

@ -1,29 +1,30 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Grpc.Generators.Models; namespace Svrnty.CQRS.Grpc.Generators.Models
public class QueryInfo
{ {
public string Name { get; set; } public class QueryInfo
public string FullyQualifiedName { get; set; }
public string Namespace { get; set; }
public List<PropertyInfo> Properties { get; set; }
public string ResultType { get; set; }
public string ResultFullyQualifiedName { get; set; }
public string HandlerInterfaceName { get; set; }
public List<PropertyInfo> ResultProperties { get; set; }
public bool IsResultPrimitiveType { get; set; }
public QueryInfo()
{ {
Name = string.Empty; public string Name { get; set; }
FullyQualifiedName = string.Empty; public string FullyQualifiedName { get; set; }
Namespace = string.Empty; public string Namespace { get; set; }
Properties = new List<PropertyInfo>(); public List<PropertyInfo> Properties { get; set; }
ResultType = string.Empty; public string ResultType { get; set; }
ResultFullyQualifiedName = string.Empty; public string ResultFullyQualifiedName { get; set; }
HandlerInterfaceName = string.Empty; public string HandlerInterfaceName { get; set; }
ResultProperties = new List<PropertyInfo>(); public List<PropertyInfo> ResultProperties { get; set; }
IsResultPrimitiveType = false; public bool IsResultPrimitiveType { get; set; }
public QueryInfo()
{
Name = string.Empty;
FullyQualifiedName = string.Empty;
Namespace = string.Empty;
Properties = new List<PropertyInfo>();
ResultType = string.Empty;
ResultFullyQualifiedName = string.Empty;
HandlerInterfaceName = string.Empty;
ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false;
}
} }
} }

View File

@ -413,21 +413,17 @@ internal class ProtoFileGenerator
private void GenerateComplexTypeMessage(INamedTypeSymbol? type) private void GenerateComplexTypeMessage(INamedTypeSymbol? type)
{ {
if (type == null) if (type == null || _generatedMessages.Contains(type.Name))
return;
var messageName = ProtoFileTypeMapper.GetProtoMessageName(type);
if (_generatedMessages.Contains(messageName))
return; return;
// Don't generate messages for system types or primitives // Don't generate messages for system types or primitives
if (type.ContainingNamespace?.ToString().StartsWith("System") == true) if (type.ContainingNamespace?.ToString().StartsWith("System") == true)
return; return;
_generatedMessages.Add(messageName); _generatedMessages.Add(type.Name);
_messagesBuilder.AppendLine($"// {messageName} entity"); _messagesBuilder.AppendLine($"// {type.Name} entity");
_messagesBuilder.AppendLine($"message {messageName} {{"); _messagesBuilder.AppendLine($"message {type.Name} {{");
// Collect nested complex types to generate after closing this message // Collect nested complex types to generate after closing this message
var nestedComplexTypes = new List<INamedTypeSymbol>(); var nestedComplexTypes = new List<INamedTypeSymbol>();

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
namespace Svrnty.CQRS.Grpc.Generators; namespace Svrnty.CQRS.Grpc.Generators;
@ -152,27 +151,13 @@ internal static class ProtoFileTypeMapper
// Complex types (classes/records) become message types // Complex types (classes/records) become message types
if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Struct) if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Struct)
{ {
return GetProtoMessageName(typeSymbol); // Reference the message type by name (handles generics) return typeName; // Reference the message type by name
} }
// Fallback // Fallback
return "string"; // Default to string for unknown types return "string"; // Default to string for unknown types
} }
/// <summary>
/// Gets the proto message name for a type, handling generic types by qualifying
/// with type arguments. e.g. Translation&lt;FaqTranslationQueryItem&gt; becomes TranslationOfFaqTranslationQueryItem.
/// </summary>
public static string GetProtoMessageName(ITypeSymbol typeSymbol)
{
if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.TypeArguments.Length > 0)
{
var typeArgs = string.Join("And", namedType.TypeArguments.Select(t => GetProtoMessageName(t)));
return $"{namedType.Name}Of{typeArgs}";
}
return typeSymbol.Name;
}
/// <summary> /// <summary>
/// Converts C# PascalCase property name to proto snake_case field name. /// Converts C# PascalCase property name to proto snake_case field name.
/// Uses simple conversion: add underscore before each uppercase letter (except first). /// Uses simple conversion: add underscore before each uppercase letter (except first).

View File

@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Svrnty.CQRS.Configuration; using Svrnty.CQRS.Configuration;
namespace Svrnty.CQRS; namespace Svrnty.CQRS.MinimalApi;
public static class WebApplicationExtensions public static class WebApplicationExtensions
{ {

View File

@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.Discovery; using Svrnty.CQRS.Discovery;
@ -44,7 +43,7 @@ public class CqrsBuilder
/// <summary> /// <summary>
/// Adds a command handler to the CQRS pipeline /// Adds a command handler to the CQRS pipeline
/// </summary> /// </summary>
public CqrsBuilder AddCommand<TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>() public CqrsBuilder AddCommand<TCommand, TCommandHandler>()
where TCommand : class where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand> where TCommandHandler : class, ICommandHandler<TCommand>
{ {
@ -55,7 +54,7 @@ public class CqrsBuilder
/// <summary> /// <summary>
/// Adds a command handler with result to the CQRS pipeline /// Adds a command handler with result to the CQRS pipeline
/// </summary> /// </summary>
public CqrsBuilder AddCommand<TCommand, TResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>() public CqrsBuilder AddCommand<TCommand, TResult, TCommandHandler>()
where TCommand : class where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand, TResult> where TCommandHandler : class, ICommandHandler<TCommand, TResult>
{ {

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;

View File

@ -26,10 +26,6 @@
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" /> <None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" /> <ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Svrnty.CQRS; using Svrnty.CQRS;
using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.DynamicQuery;
using Svrnty.CQRS.FluentValidation; using Svrnty.CQRS.FluentValidation;
using Svrnty.CQRS.Grpc; using Svrnty.CQRS.Grpc;
using Svrnty.CQRS.MinimalApi;
using Svrnty.Sample; using Svrnty.Sample;
using Svrnty.CQRS.MinimalApi;
using Svrnty.CQRS.DynamicQuery;
using Svrnty.CQRS.Abstractions;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);

View File

@ -1,5 +1,5 @@
using System.Linq.Expressions;
using PoweredSoft.Data.Core; using PoweredSoft.Data.Core;
using System.Linq.Expressions;
namespace Svrnty.Sample; namespace Svrnty.Sample;