Compare commits

...

9 Commits
JP ... dev

Author SHA1 Message Date
Mathias Beaulieu-Duncan a05ebad7fc Fix CS8601 in generated proto→command list mappings
Publish NuGets / build (release) Successful in 28s
Generated CommandServiceImpl.g.cs had warnings like:
    Slug = request.Slug?.ToList(),   // CS8601 if Slug is non-nullable List<T>

The ?. was over-defensive: proto3 repeated fields are emitted as
RepeatedField<T> in C# and are NEVER null. The conditional access
made the result List<T>? which then triggered CS8601 when assigned
to a non-nullable target on the command POCO.

Dropped ?. in 4 emission sites in GrpcGenerator.cs covering:
- Top-level primitive list mapping (line 872)
- Top-level Guid list mapping (line 861)
- Nested primitive list mapping in NestedPropertyAssignment (line 1083)
- Complex list .Select chain in GenerateComplexListMapping (line 974,
  conditional: kept ?. for value-type collections where source.Items is
  read off a possibly-null wrapper message)

Real fix in the generator instead of CS8601 NoWarn suppression in
consumer csprojs. Consumers can drop the suppression after bumping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:42:17 -04:00
Mathias Beaulieu-Duncan ee3ad866d9 Use InvariantCulture for decimal.Parse in generated gRPC mappers
Generated code was using locale-dependent parsing for decimal values.
On systems with comma decimal separator (e.g., French locale), parsing
"0.95" would throw FormatException because the system expected "0,95".

Switched all 4 decimal.Parse() call sites in the generated proto→domain
mappers to pass System.Globalization.CultureInfo.InvariantCulture for
consistent behavior across locales.

Inspired by JP's commit 599204d on feat/grpc-generator-improvements
(applied manually since cherry-pick had heavy context conflicts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:33:27 -04:00
mathias 55f1324286 Merge pull request 'feat/claude-code-harness' (#2) from feat/claude-code-harness into main
Publish NuGets / build (release) Successful in 34s
Reviewed-on: #2
2026-03-12 06:44:11 -04:00
Mathias Beaulieu-Duncan b34bf874b4 Remove Claude harness — replaced by claude-cqrs-plugin
The in-repo .claude/ harness (rules, skills, settings) is superseded by
the standalone claude-cqrs-plugin which provides the same guidance as a
reusable plugin across all Svrnty.CQRS projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 06:42:50 -04:00
Mathias Beaulieu-Duncan c6de10b98b Move UseSvrntyCqrs() from MinimalApi to core Svrnty.CQRS package
gRPC-only projects couldn't call app.UseSvrntyCqrs() without adding the
MinimalApi package. The method only calls ExecuteMappingCallbacks() which
is already in core — it had no MinimalApi dependency. Adds ASP.NET Core
FrameworkReference to the core package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 06:38:26 -04:00
Mathias Beaulieu-Duncan 3945c1a158 Add project-init agent for scaffolding new CQRS projects
Scaffolds a complete Svrnty.CQRS project from a natural language
description — creates solution, web project, DAL with PostgreSQL,
entities, Program.cs, first feature, proto file, and .editorconfig.
Defaults to gRPC-only; MinimalApi added only on request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 06:38:26 -04:00
mathias 7614f68512 Merge pull request 'feat/claude-code-harness' (#1) from feat/claude-code-harness into main
Publish NuGets / build (release) Successful in 32s
Reviewed-on: #1
2026-03-12 03:35:26 -04:00
Mathias Beaulieu-Duncan fdee02c960 Apply dotnet format with new editorconfig rules
Automated formatting: BOM removal, using sort order, final newlines,
whitespace normalization across all projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 03:30:50 -04:00
Mathias Beaulieu-Duncan a4525bad6a Add Claude Code harness: rules, skills, hooks, and editorconfig
- Add path-specific rules for commands/queries, dynamic queries, validation, and gRPC
- Add /add-command, /add-query, /add-dynamic-query scaffolding skills
- Add project settings with post-edit formatting, proto validation, and build-gate hooks
- Add .editorconfig codifying existing code style conventions
- Trim CLAUDE.md from 414 to 130 lines (domain details moved to rules)
- Add .harness-version tracking for the shared claude-harness repo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 03:30:27 -04:00
42 changed files with 3498 additions and 3448 deletions
-50
View File
@@ -1,50 +0,0 @@
{
"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": []
}
}
+96
View File
@@ -0,0 +1,96 @@
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
@@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Attributes; namespace Svrnty.CQRS.Abstractions.Attributes;
@@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Attributes; namespace Svrnty.CQRS.Abstractions.Attributes;
@@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Svrnty.CQRS.Abstractions.Attributes; using Svrnty.CQRS.Abstractions.Attributes;
@@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Discovery; namespace Svrnty.CQRS.Abstractions.Discovery;
@@ -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;
@@ -1,4 +1,4 @@
using System; using System;
namespace Svrnty.CQRS.Abstractions.Discovery; namespace Svrnty.CQRS.Abstractions.Discovery;
@@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Svrnty.CQRS.Abstractions.Attributes; using Svrnty.CQRS.Abstractions.Attributes;
+1 -1
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;
+1 -1
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;
@@ -1,4 +1,4 @@
namespace Svrnty.CQRS.Abstractions.Security; namespace Svrnty.CQRS.Abstractions.Security;
public enum AuthorizationResult public enum AuthorizationResult
{ {
@@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -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;
@@ -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;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -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;
@@ -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;
@@ -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
@@ -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,6 +7,7 @@ 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;
@@ -14,7 +15,6 @@ 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;
@@ -1,4 +1,4 @@
using System; using System;
using Pluralize.NET; using Pluralize.NET;
using Svrnty.CQRS.Abstractions.Discovery; using Svrnty.CQRS.Abstractions.Discovery;
+1 -1
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;
@@ -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;
@@ -1,10 +1,10 @@
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;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
@@ -6,9 +6,9 @@ using System.Linq;
using System.Reflection; 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;
@@ -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;
@@ -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>
@@ -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;
+20 -16
View File
@@ -1,16 +1,16 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Svrnty.CQRS.Grpc.Generators.Helpers; using Svrnty.CQRS.Grpc.Generators.Helpers;
using Svrnty.CQRS.Grpc.Generators.Models; using Svrnty.CQRS.Grpc.Generators.Models;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svrnty.CQRS.Grpc.Generators namespace Svrnty.CQRS.Grpc.Generators;
[Generator]
public class GrpcGenerator : IIncrementalGenerator
{ {
[Generator]
public class GrpcGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
// Find all types that might be commands or queries from source // Find all types that might be commands or queries from source
@@ -858,7 +858,8 @@ namespace Svrnty.CQRS.Grpc.Generators
var constructorType = prop.FullyQualifiedType.TrimEnd('?'); var constructorType = prop.FullyQualifiedType.TrimEnd('?');
return $"{indent}{prop.Name} = new {constructorType}({source}?.Items?.Select(x => System.Guid.Parse(x)).ToArray() ?? System.Array.Empty<System.Guid>()),"; return $"{indent}{prop.Name} = new {constructorType}({source}?.Items?.Select(x => System.Guid.Parse(x)).ToArray() ?? System.Array.Empty<System.Guid>()),";
} }
return $"{indent}{prop.Name} = {source}?.Select(x => System.Guid.Parse(x)).ToList(),"; // proto repeated fields are never null — drop ?. to avoid CS8601 on assignment to non-nullable target
return $"{indent}{prop.Name} = {source}.Select(x => System.Guid.Parse(x)).ToList(),";
} }
else if (prop.IsValueTypeCollection) else if (prop.IsValueTypeCollection)
{ {
@@ -869,7 +870,8 @@ namespace Svrnty.CQRS.Grpc.Generators
else else
{ {
// Primitive list: just ToList() // Primitive list: just ToList()
return $"{indent}{prop.Name} = {source}?.ToList(),"; // proto repeated fields are never null — drop ?. to avoid CS8601 on assignment to non-nullable target
return $"{indent}{prop.Name} = {source}.ToList(),";
} }
} }
@@ -884,11 +886,11 @@ namespace Svrnty.CQRS.Grpc.Generators
{ {
if (prop.IsNullable) if (prop.IsNullable)
{ {
return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : decimal.Parse({source}),"; return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : decimal.Parse({source}, System.Globalization.CultureInfo.InvariantCulture),";
} }
else else
{ {
return $"{indent}{prop.Name} = decimal.Parse({source}),"; return $"{indent}{prop.Name} = decimal.Parse({source}, System.Globalization.CultureInfo.InvariantCulture),";
} }
} }
@@ -969,7 +971,9 @@ namespace Svrnty.CQRS.Grpc.Generators
var sb = new StringBuilder(); var sb = new StringBuilder();
// For value type collections, the proto message has an Items field containing the repeated elements // For value type collections, the proto message has an Items field containing the repeated elements
var itemsSource = prop.IsValueTypeCollection ? $"{source}?.Items" : source; var itemsSource = prop.IsValueTypeCollection ? $"{source}?.Items" : source;
sb.AppendLine($"{indent}{prop.Name} = {itemsSource}?.Select(x => new {prop.ElementType}"); // Value-type wrapper messages can be null (?.Items needs ?.). Plain proto repeated is never null.
var selectAccess = prop.IsValueTypeCollection ? "?." : ".";
sb.AppendLine($"{indent}{prop.Name} = {itemsSource}{selectAccess}Select(x => new {prop.ElementType}");
sb.AppendLine($"{indent}{{"); sb.AppendLine($"{indent}{{");
foreach (var nestedProp in prop.ElementNestedProperties!) foreach (var nestedProp in prop.ElementNestedProperties!)
@@ -1031,11 +1035,11 @@ namespace Svrnty.CQRS.Grpc.Generators
{ {
if (prop.IsNullable) if (prop.IsNullable)
{ {
return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : decimal.Parse({source}),"; return $"{indent}{prop.Name} = string.IsNullOrEmpty({source}) ? null : decimal.Parse({source}, System.Globalization.CultureInfo.InvariantCulture),";
} }
else else
{ {
return $"{indent}{prop.Name} = decimal.Parse({source}),"; return $"{indent}{prop.Name} = decimal.Parse({source}, System.Globalization.CultureInfo.InvariantCulture),";
} }
} }
@@ -1078,7 +1082,8 @@ namespace Svrnty.CQRS.Grpc.Generators
var constructorType = prop.FullyQualifiedType.TrimEnd('?'); var constructorType = prop.FullyQualifiedType.TrimEnd('?');
return $"{indent}{prop.Name} = new {constructorType}({source}?.ToArray() ?? System.Array.Empty<{prop.ElementType ?? "object"}>()),"; return $"{indent}{prop.Name} = new {constructorType}({source}?.ToArray() ?? System.Array.Empty<{prop.ElementType ?? "object"}>()),";
} }
return $"{indent}{prop.Name} = {source}?.ToList(),"; // proto repeated fields are never null — drop ?. to avoid CS8601
return $"{indent}{prop.Name} = {source}.ToList(),";
} }
// Handle complex types // Handle complex types
@@ -3358,5 +3363,4 @@ namespace Svrnty.CQRS.Grpc.Generators
return string.Join("", parts.Select(p => return string.Join("", parts.Select(p =>
p.Length > 0 ? char.ToUpperInvariant(p[0]) + p.Substring(1).ToLowerInvariant() : "")); p.Length > 0 ? char.ToUpperInvariant(p[0]) + p.Substring(1).ToLowerInvariant() : ""));
} }
}
} }
@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Grpc.Generators.Helpers namespace Svrnty.CQRS.Grpc.Generators.Helpers;
internal static class ProtoTypeMapper
{ {
internal static class ProtoTypeMapper
{
private static readonly Dictionary<string, string> TypeMap = new Dictionary<string, string> private static readonly Dictionary<string, string> TypeMap = new Dictionary<string, string>
{ {
// Primitives // Primitives
@@ -98,5 +98,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Helpers
return csharpType.Replace("?", ""); return csharpType.Replace("?", "");
} }
}
} }
@@ -1,10 +1,10 @@
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 class CommandInfo
{
public string Name { get; set; } public string Name { get; set; }
public string FullyQualifiedName { get; set; } public string FullyQualifiedName { get; set; }
public string Namespace { get; set; } public string Namespace { get; set; }
@@ -26,10 +26,10 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
ResultProperties = new List<PropertyInfo>(); ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false; IsResultPrimitiveType = false;
} }
} }
public class PropertyInfo public class PropertyInfo
{ {
public string Name { get; set; } public string Name { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string FullyQualifiedType { get; set; } public string FullyQualifiedType { get; set; }
@@ -79,5 +79,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
IsElementComplexType = false; IsElementComplexType = false;
IsElementGuid = false; IsElementGuid = false;
} }
}
} }
@@ -1,7 +1,7 @@
namespace Svrnty.CQRS.Grpc.Generators.Models namespace Svrnty.CQRS.Grpc.Generators.Models;
public class DynamicQueryInfo
{ {
public class DynamicQueryInfo
{
public string Name { get; set; } public string Name { get; set; }
public string SourceType { get; set; } public string SourceType { get; set; }
public string SourceTypeFullyQualified { get; set; } public string SourceTypeFullyQualified { get; set; }
@@ -24,5 +24,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
QueryInterfaceName = string.Empty; QueryInterfaceName = string.Empty;
HasParams = false; HasParams = false;
} }
}
} }
@@ -1,12 +1,12 @@
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>
/// Represents a discovered streaming notification type for proto/gRPC generation.
/// </summary>
public class NotificationInfo
{
/// <summary> /// <summary>
/// The notification type name (e.g., "InventoryChangeNotification"). /// The notification type name (e.g., "InventoryChangeNotification").
/// </summary> /// </summary>
@@ -46,5 +46,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
SubscriptionKeyInfo = new PropertyInfo(); SubscriptionKeyInfo = new PropertyInfo();
Properties = new List<PropertyInfo>(); Properties = new List<PropertyInfo>();
} }
}
} }
@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Svrnty.CQRS.Grpc.Generators.Models namespace Svrnty.CQRS.Grpc.Generators.Models;
public class QueryInfo
{ {
public class QueryInfo
{
public string Name { get; set; } public string Name { get; set; }
public string FullyQualifiedName { get; set; } public string FullyQualifiedName { get; set; }
public string Namespace { get; set; } public string Namespace { get; set; }
@@ -26,5 +26,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
ResultProperties = new List<PropertyInfo>(); ResultProperties = new List<PropertyInfo>();
IsResultPrimitiveType = false; IsResultPrimitiveType = false;
} }
}
} }
+3 -2
View File
@@ -1,3 +1,4 @@
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;
@@ -43,7 +44,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, TCommandHandler>() public CqrsBuilder AddCommand<TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>()
where TCommand : class where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand> where TCommandHandler : class, ICommandHandler<TCommand>
{ {
@@ -54,7 +55,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, TCommandHandler>() public CqrsBuilder AddCommand<TCommand, TResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>()
where TCommand : class where TCommand : class
where TCommandHandler : class, ICommandHandler<TCommand, TResult> where TCommandHandler : class, ICommandHandler<TCommand, TResult>
{ {
+1 -1
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;
+1 -1
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;
+4
View File
@@ -26,6 +26,10 @@
<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>
@@ -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.MinimalApi; namespace Svrnty.CQRS;
public static class WebApplicationExtensions public static class WebApplicationExtensions
{ {
+3 -3
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.Sample;
using Svrnty.CQRS.MinimalApi; using Svrnty.CQRS.MinimalApi;
using Svrnty.CQRS.DynamicQuery; using Svrnty.Sample;
using Svrnty.CQRS.Abstractions;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
+1 -1
View File
@@ -1,5 +1,5 @@
using PoweredSoft.Data.Core;
using System.Linq.Expressions; using System.Linq.Expressions;
using PoweredSoft.Data.Core;
namespace Svrnty.Sample; namespace Svrnty.Sample;