Momentum.Extensions.SourceGenerators 0.0.2

dotnet add package Momentum.Extensions.SourceGenerators --version 0.0.2
                    
NuGet\Install-Package Momentum.Extensions.SourceGenerators -Version 0.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Momentum.Extensions.SourceGenerators" Version="0.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Momentum.Extensions.SourceGenerators" Version="0.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Momentum.Extensions.SourceGenerators" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Momentum.Extensions.SourceGenerators --version 0.0.2
                    
#r "nuget: Momentum.Extensions.SourceGenerators, 0.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Momentum.Extensions.SourceGenerators@0.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Momentum.Extensions.SourceGenerators&version=0.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Momentum.Extensions.SourceGenerators&version=0.0.2
                    
Install as a Cake Tool

Momentum.Extensions.SourceGenerators

Database Command Source Generators for the Momentum platform that automatically generate Dapper-based database access code, eliminating boilerplate and ensuring type-safe parameter mapping for your commands and queries.

Overview

The Momentum.Extensions.SourceGenerators package provides a powerful DbCommand source generator that analyzes your command and query classes marked with [DbCommand] attributes and automatically generates:

  • Parameter Providers: Type-safe ToDbParams() methods for database parameter mapping
  • Command Handlers: Complete database execution handlers with proper async patterns
  • Dapper Integration: Seamless integration with Dapper for high-performance data access
  • Multiple Database Patterns: Support for stored procedures, SQL queries, and database functions

Key Benefits:

  • Zero Runtime Overhead: All code generation happens at compile-time
  • Type Safety: Strongly-typed parameter mapping with compile-time validation
  • Reduced Boilerplate: Eliminates repetitive database access code
  • Consistent Patterns: Enforces consistent data access patterns across your application
  • IDE Support: Generated code appears in IntelliSense and debugging

Installation & Setup

Package Installation

Add the package to your project:

dotnet add package Momentum.Extensions.SourceGenerators

Required Dependencies

The source generator requires these companion packages:

<PackageReference Include="Momentum.Extensions.Abstractions" Version="1.0.0" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

Project Configuration

Add to your .csproj file:

<PropertyGroup>
  
  <DbCommandDefaultParamCase>None</DbCommandDefaultParamCase>

  
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>

  
  <MomentumGeneratorVerbose>false</MomentumGeneratorVerbose>
</PropertyGroup>

DbCommand Generator - Complete Guide

Basic Usage Pattern

The DbCommand generator follows this pattern:

  1. Define your command/query as a partial record implementing ICommand<T> or IQuery<T>
  2. Decorate with [DbCommand] specifying stored procedure, SQL, or function
  3. Generated code provides parameter mapping and database execution logic

1. Stored Procedure Commands

Basic Stored Procedure Example:

using Momentum.Extensions.Abstractions.Dapper;
using Momentum.Extensions.Abstractions.Messaging;

[DbCommand(sp: "create_user", nonQuery: true)]
public partial record CreateUserCommand(int UserId, string Name) : ICommand<int>;

Generated Parameter Provider:

sealed public partial record CreateUserCommand : IDbParamsProvider
{
    public object ToDbParams()
    {
        return this; // Uses record properties directly
    }
}

Generated Handler:

public static class CreateUserCommandHandler
{
    public static async Task<int> HandleAsync(
        CreateUserCommand command,
        DbDataSource datasource,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await datasource.OpenConnectionAsync(cancellationToken);
        var dbParams = command.ToDbParams();
        return await SqlMapper.ExecuteAsync(connection,
            new CommandDefinition("create_user", dbParams,
                commandType: CommandType.StoredProcedure,
                cancellationToken: cancellationToken));
    }
}

2. SQL Query Commands

SQL Query with Object Result:

[DbCommand(sql: "SELECT * FROM users WHERE id = @UserId")]
public partial record GetUserByIdQuery(int UserId) : ICommand<User>;

public record User(int Id, string Name, string Email);

Generated Handler for Single Object:

public static class GetUserByIdQueryHandler
{
    public static async Task<User> HandleAsync(
        GetUserByIdQuery command,
        DbDataSource datasource,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await datasource.OpenConnectionAsync(cancellationToken);
        var dbParams = command.ToDbParams();
        return await SqlMapper.QueryFirstOrDefaultAsync<User>(connection,
            new CommandDefinition("SELECT * FROM users WHERE id = @UserId", dbParams,
                commandType: CommandType.Text,
                cancellationToken: cancellationToken));
    }
}

SQL Query with Collection Result:

[DbCommand(sql: "SELECT * FROM users WHERE active = @Active")]
public partial record GetActiveUsersQuery(bool Active) : ICommand<IEnumerable<User>>;

Generated Handler for Collections:

public static class GetActiveUsersQueryHandler
{
    public static async Task<IEnumerable<User>> HandleAsync(
        GetActiveUsersQuery command,
        DbDataSource datasource,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await datasource.OpenConnectionAsync(cancellationToken);
        var dbParams = command.ToDbParams();
        return await SqlMapper.QueryAsync<User>(connection,
            new CommandDefinition("SELECT * FROM users WHERE active = @Active", dbParams,
                commandType: CommandType.Text,
                cancellationToken: cancellationToken));
    }
}

3. Database Function Commands

Function with Auto-Generated Parameters:

[DbCommand(fn: "select * from app_domain.invoices_get")]
public partial record GetInvoicesQuery(int Limit, int Offset, string Status) : IQuery<IEnumerable<Invoice>>;

public record Invoice(int Id, string Status, decimal Amount);

Generated SQL with Function Parameters:

public static class GetInvoicesQueryHandler
{
    public static async Task<IEnumerable<Invoice>> HandleAsync(
        GetInvoicesQuery command,
        DbDataSource datasource,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await datasource.OpenConnectionAsync(cancellationToken);
        var dbParams = command.ToDbParams();
        // Note: Function parameters are automatically appended
        return await SqlMapper.QueryAsync<Invoice>(connection,
            new CommandDefinition("select * from app_domain.invoices_get(@Limit, @Offset, @Status)",
                dbParams, commandType: CommandType.Text,
                cancellationToken: cancellationToken));
    }
}

4. Parameter Mapping Patterns

Default Parameter Mapping (Property Names As-Is):

[DbCommand(sp: "update_user")]
public partial record UpdateUserCommand(int UserId, string FirstName, string LastName) : ICommand<int>;

// Generated ToDbParams() returns: { UserId, FirstName, LastName }

Snake Case Parameter Mapping:

[DbCommand(sp: "update_user", paramsCase: DbParamsCase.SnakeCase)]
public partial record UpdateUserCommand(int UserId, string FirstName, string LastName) : ICommand<int>;

Generated with Snake Case:

public object ToDbParams()
{
    var p = new
    {
        user_id = this.UserId,
        first_name = this.FirstName,
        last_name = this.LastName
    };
    return p;
}

Custom Parameter Names with Column Attribute:

using System.ComponentModel.DataAnnotations.Schema;

[DbCommand(sp: "update_user", paramsCase: DbParamsCase.SnakeCase)]
public partial record UpdateUserCommand(
    int UserId,
    [Column("custom_name")] string FirstName,
    string LastName,
    [Column("email_address")] string EmailAddr
) : ICommand<int>;

Generated with Custom Names:

public object ToDbParams()
{
    var p = new
    {
        user_id = this.UserId,
        custom_name = this.FirstName,    // Uses Column attribute
        last_name = this.LastName,
        email_address = this.EmailAddr   // Uses Column attribute
    };
    return p;
}

Advanced Usage Scenarios

1. Scalar Return Types

Integer Results (Row Count or Scalar):

// Non-query: Returns rows affected
[DbCommand(sp: "delete_inactive_users", nonQuery: true)]
public partial record DeleteInactiveUsersCommand() : ICommand<int>;

// Scalar query: Returns actual count value
[DbCommand(sql: "SELECT COUNT(*) FROM users")]
public partial record GetUserCountQuery() : ICommand<int>;

Long Scalar Results:

[DbCommand(sql: "SELECT @@IDENTITY")]
public partial record GetLastInsertIdQuery() : ICommand<long>;

// Generated handler uses ExecuteScalarAsync<long>

2. Multiple Data Sources

Keyed Data Source Injection:

[DbCommand(sp: "get_report", dataSource: "ReportingDb")]
public partial record GetReportQuery(int ReportId) : ICommand<Report>;

public record Report(int Id, string Title, DateTime CreatedDate);

Generated Handler with Keyed Service:

public static async Task<Report> HandleAsync(
    GetReportQuery command,
    [FromKeyedServices("ReportingDb")] DbDataSource datasource,
    CancellationToken cancellationToken = default)
{
    // Implementation uses the keyed data source
}

3. Complex Parameter Scenarios

Commands with No Parameters:

[DbCommand(sp: "cleanup_temp_data")]
public partial record CleanupTempDataCommand() : ICommand<int>;

// Generated ToDbParams() returns 'this' (empty record)

Commands with Optional/Nullable Parameters:

[DbCommand(sql: "SELECT * FROM users WHERE (@Name IS NULL OR name LIKE @Name) AND (@MinAge IS NULL OR age >= @MinAge)")]
public partial record SearchUsersQuery(string? Name, int? MinAge) : ICommand<IEnumerable<User>>;

// Nullable parameters are handled automatically by Dapper

4. Global Configuration

MSBuild Configuration:

<PropertyGroup>
  
  <DbCommandDefaultParamCase>SnakeCase</DbCommandDefaultParamCase>

  
  <MomentumGeneratorVerbose>true</MomentumGeneratorVerbose>
</PropertyGroup>

Per-Command Override:

// This command uses None case despite global SnakeCase setting
[DbCommand(sp: "legacy_proc", paramsCase: DbParamsCase.None)]
public partial record LegacyCommand(int UserId) : ICommand<int>;

Integration Patterns

1. Dependency Injection Setup

Service Registration:

// Program.cs or Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Register DbDataSource
    services.AddNpgsqlDataSource(connectionString);

    // Register Wolverine for command handling
    services.AddWolverine(opts =>
    {
        opts.Discovery.DisableConventionalDiscovery();
        // Generated handlers are discovered automatically
    });
}

2. Usage in Application Services

Sending Commands through Message Bus:

public class UserService
{
    private readonly IMessageBus _messageBus;

    public UserService(IMessageBus messageBus)
    {
        _messageBus = messageBus;
    }

    public async Task<int> CreateUserAsync(string name)
    {
        var command = new CreateUserCommand(UserId: 0, Name: name);
        return await _messageBus.InvokeAsync(command);
    }

    public async Task<User> GetUserAsync(int userId)
    {
        var query = new GetUserByIdQuery(userId);
        return await _messageBus.InvokeAsync(query);
    }
}

Direct Handler Usage:

public class UserRepository
{
    private readonly DbDataSource _dataSource;

    public UserRepository(DbDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task<User> GetUserByIdAsync(int userId, CancellationToken cancellationToken = default)
    {
        var query = new GetUserByIdQuery(userId);
        return await GetUserByIdQueryHandler.HandleAsync(query, _dataSource, cancellationToken);
    }
}

3. Repository Pattern Integration

Generated Commands as Repository Methods:

public interface IUserRepository
{
    Task<User> GetByIdAsync(int userId);
    Task<IEnumerable<User>> GetActiveUsersAsync();
    Task<int> CreateAsync(string name);
    Task<int> UpdateAsync(int userId, string firstName, string lastName);
}

public class UserRepository : IUserRepository
{
    private readonly DbDataSource _dataSource;

    public UserRepository(DbDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task<User> GetByIdAsync(int userId)
    {
        var query = new GetUserByIdQuery(userId);
        return await GetUserByIdQueryHandler.HandleAsync(query, _dataSource);
    }

    public async Task<IEnumerable<User>> GetActiveUsersAsync()
    {
        var query = new GetActiveUsersQuery(true);
        return await GetActiveUsersQueryHandler.HandleAsync(query, _dataSource);
    }

    public async Task<int> CreateAsync(string name)
    {
        var command = new CreateUserCommand(0, name);
        return await CreateUserCommandHandler.HandleAsync(command, _dataSource);
    }

    public async Task<int> UpdateAsync(int userId, string firstName, string lastName)
    {
        var command = new UpdateUserCommand(userId, firstName, lastName);
        return await UpdateUserCommandHandler.HandleAsync(command, _dataSource);
    }
}

Configuration & Debugging

MSBuild Properties

<PropertyGroup>
  
  <DbCommandDefaultParamCase>None|SnakeCase</DbCommandDefaultParamCase>

  
  <MomentumGeneratorVerbose>true</MomentumGeneratorVerbose>

  
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

Viewing Generated Code

In Visual Studio:

  1. Solution ExplorerDependenciesAnalyzersMomentum.Extensions.SourceGenerators
  2. Expand to see generated .g.cs files

File System Location:

obj/Debug/net9.0/generated/Momentum.Extensions.SourceGenerators/
├── CreateUserCommand.DbExt.g.cs     # Parameter provider
├── CreateUserCommandHandler.g.cs    # Command handler
├── GetUserByIdQuery.DbExt.g.cs      # Parameter provider
└── GetUserByIdQueryHandler.g.cs     # Query handler

Enable File Output:

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

Debugging Generated Code

Enable Verbose Logging:

<PropertyGroup>
  <MomentumGeneratorVerbose>true</MomentumGeneratorVerbose>
</PropertyGroup>

Build with Diagnostic Output:

dotnet build -v diagnostic

Generator Debugging (Advanced):

# Enable generator debugging in environment
export DOTNET_EnableSourceGeneratorDebugging=1
dotnet build

Troubleshooting Guide

Common Issues & Solutions

1. Generator Not Running

// ❌ Problem: Generator not creating code
[DbCommand(sp: "test_proc")]
public record TestCommand(int Id) : ICommand<int>; // Missing 'partial'

// ✅ Solution: Add 'partial' keyword
[DbCommand(sp: "test_proc")]
public partial record TestCommand(int Id) : ICommand<int>;

2. Compilation Errors

# Error: IDbParamsProvider not found
# Solution: Add required package reference
dotnet add package Momentum.Extensions.Abstractions

3. Parameter Mapping Issues

// ❌ Problem: Parameter name mismatch
[DbCommand(sql: "SELECT * FROM users WHERE user_id = @UserId")]
public partial record GetUserQuery(int UserId) : ICommand<User>; // Expects @UserId, but DB uses user_id

// ✅ Solution: Use Column attribute or snake_case
[DbCommand(sql: "SELECT * FROM users WHERE user_id = @user_id", paramsCase: DbParamsCase.SnakeCase)]
public partial record GetUserQuery(int UserId) : ICommand<User>;

// OR use Column attribute
[DbCommand(sql: "SELECT * FROM users WHERE user_id = @user_id")]
public partial record GetUserQuery([Column("user_id")] int UserId) : ICommand<User>;

4. Missing Generated Files

# Check generator is referenced correctly
dotnet list package | grep SourceGenerators

# Force regeneration
dotnet clean && dotnet build

# Check for analyzer configuration
ls analyzers.globalconfig 2>/dev/null || echo "No global config found"

5. Handler Not Found in DI

// Generated handlers are static classes, not services
// ❌ Don't try to inject handlers
public class BadService
{
    public BadService(CreateUserCommandHandler handler) { } // Won't work
}

// ✅ Use message bus or call handlers directly
public class GoodService
{
    private readonly IMessageBus _messageBus;
    private readonly DbDataSource _dataSource;

    public async Task<int> CreateUser(string name)
    {
        var command = new CreateUserCommand(0, name);

        // Option 1: Via message bus
        return await _messageBus.InvokeAsync(command);

        // Option 2: Direct handler call
        return await CreateUserCommandHandler.HandleAsync(command, _dataSource);
    }
}

Performance Considerations

1. Parameter Object Creation

// Default case: No object allocation
[DbCommand(sp: "simple_proc")]
public partial record SimpleCommand(int Id, string Name) : ICommand<int>;
// Generated: return this; (no allocation)

// Snake case: Object allocation for parameter mapping
[DbCommand(sp: "simple_proc", paramsCase: DbParamsCase.SnakeCase)]
public partial record SimpleCommand(int Id, string Name) : ICommand<int>;
// Generated: return new { id = this.Id, name = this.Name }; (allocates anonymous object)

2. Connection Management

// Generated handlers properly manage connections
public static async Task<int> HandleAsync(...)
{
    await using var connection = await datasource.OpenConnectionAsync(cancellationToken);
    // Connection is properly disposed
}

3. Command Reuse

// Command records are immutable and safe to reuse
var getUserQuery = new GetUserByIdQuery(123);

// Can be called multiple times safely
var user1 = await GetUserByIdQueryHandler.HandleAsync(getUserQuery, dataSource);
var user2 = await GetUserByIdQueryHandler.HandleAsync(getUserQuery, dataSource);

Migration & Upgrade Guide

From Manual Dapper Code

Before (Manual Implementation):

public class UserRepository
{
    private readonly IDbConnection _connection;

    public async Task<User> GetByIdAsync(int userId)
    {
        const string sql = "SELECT * FROM users WHERE id = @Id";
        return await _connection.QueryFirstOrDefaultAsync<User>(sql, new { Id = userId });
    }

    public async Task<int> CreateAsync(string name)
    {
        const string sql = "INSERT INTO users (name) VALUES (@Name) RETURNING id";
        return await _connection.ExecuteScalarAsync<int>(sql, new { Name = name });
    }
}

After (Generated Implementation):

// Define commands/queries
[DbCommand(sql: "SELECT * FROM users WHERE id = @Id")]
public partial record GetUserByIdQuery(int Id) : ICommand<User>;

[DbCommand(sql: "INSERT INTO users (name) VALUES (@Name) RETURNING id")]
public partial record CreateUserCommand(string Name) : ICommand<int>;

// Repository uses generated handlers
public class UserRepository
{
    private readonly DbDataSource _dataSource;

    public UserRepository(DbDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task<User> GetByIdAsync(int userId)
    {
        var query = new GetUserByIdQuery(userId);
        return await GetUserByIdQueryHandler.HandleAsync(query, _dataSource);
    }

    public async Task<int> CreateAsync(string name)
    {
        var command = new CreateUserCommand(name);
        return await CreateUserCommandHandler.HandleAsync(command, _dataSource);
    }
}

Migration Benefits:

  • Type Safety: Compile-time validation of parameters
  • Consistency: Standardized patterns across all data access
  • Maintainability: Changes to commands automatically update all usage
  • Testing: Commands are value objects, easy to test
  • Performance: Generated code is optimized and allocation-efficient

Real-World Examples

E-Commerce Order Management

// Order creation with inventory check
[DbCommand(sp: "orders_create_with_inventory_check", nonQuery: true)]
public partial record CreateOrderCommand(
    Guid CustomerId,
    [Column("product_id")] Guid ProductId,
    int Quantity,
    decimal UnitPrice
) : ICommand<int>;

// Order status updates
[DbCommand(sql: "UPDATE orders SET status = @Status, updated_at = NOW() WHERE id = @OrderId")]
public partial record UpdateOrderStatusCommand(Guid OrderId, string Status) : ICommand<int>;

// Order queries with joins
[DbCommand(fn: "select * from orders_get_with_customer_details", paramsCase: DbParamsCase.SnakeCase)]
public partial record GetOrdersWithCustomerQuery(
    int Limit,
    int Offset,
    string? Status,
    DateTime? FromDate,
    DateTime? ToDate
) : IQuery<IEnumerable<OrderWithCustomer>>;

public record OrderWithCustomer(
    Guid OrderId,
    Guid CustomerId,
    string CustomerName,
    string Status,
    decimal TotalAmount,
    DateTime CreatedAt
);

User Authentication & Authorization

// User authentication
[DbCommand(sql: "SELECT id, email, password_hash, is_active FROM users WHERE email = @Email AND is_active = true")]
public partial record GetUserByEmailQuery(string Email) : IQuery<UserCredentials>;

// Password updates
[DbCommand(sp: "users_update_password", nonQuery: true)]
public partial record UpdateUserPasswordCommand(
    Guid UserId,
    string PasswordHash,
    DateTime UpdatedAt
) : ICommand<int>;

// Role assignments
[DbCommand(sql: "INSERT INTO user_roles (user_id, role_id) VALUES (@UserId, @RoleId) ON CONFLICT DO NOTHING")]
public partial record AssignUserRoleCommand(Guid UserId, Guid RoleId) : ICommand<int>;

// User permissions query
[DbCommand(fn: "select * from users_get_permissions")]
public partial record GetUserPermissionsQuery(Guid UserId) : IQuery<IEnumerable<string>>;

public record UserCredentials(Guid Id, string Email, string PasswordHash, bool IsActive);

Reporting & Analytics

// Daily sales report
[DbCommand(fn: "select * from reports_daily_sales", paramsCase: DbParamsCase.SnakeCase)]
public partial record GetDailySalesReportQuery(
    DateTime StartDate,
    DateTime EndDate,
    string? ProductCategory
) : IQuery<IEnumerable<DailySalesData>>;

// Customer analytics
[DbCommand(sql: """
    SELECT
        customer_id,
        COUNT(*) as order_count,
        SUM(total_amount) as total_spent,
        AVG(total_amount) as avg_order_value,
        MAX(created_at) as last_order_date
    FROM orders
    WHERE created_at >= @FromDate
        AND (@CustomerId IS NULL OR customer_id = @CustomerId)
    GROUP BY customer_id
    ORDER BY total_spent DESC
    """)]
public partial record GetCustomerAnalyticsQuery(
    DateTime FromDate,
    Guid? CustomerId
) : IQuery<IEnumerable<CustomerAnalytics>>;

public record DailySalesData(DateTime Date, decimal TotalRevenue, int OrderCount);
public record CustomerAnalytics(Guid CustomerId, int OrderCount, decimal TotalSpent, decimal AvgOrderValue, DateTime LastOrderDate);

Package Information

  • Target Framework: .NET Standard 2.1 (Generator Host)
  • Generated Code Target: Any .NET version supporting Dapper
  • Roslyn Version: 4.0+
  • Dependencies: Momentum.Extensions.Abstractions, Dapper
  • Package Type: DevelopmentDependency (Analyzer package)

License

This project is licensed under the MIT License. See the LICENSE file for details.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.2 139 9/9/2025
0.0.2-preview.2 113 9/9/2025
0.0.2-preview.1 120 9/5/2025
0.0.1 270 8/29/2025
0.0.1-preview.1 131 9/4/2025
0.0.1-pre.18 123 9/3/2025
0.0.1-pre.17 115 9/2/2025
0.0.1-pre.16 162 8/29/2025
0.0.1-pre.15 160 8/28/2025
0.0.1-pre.14 343 8/21/2025
0.0.1-pre.13 128 8/21/2025
0.0.1-pre.12 129 8/20/2025
0.0.1-pre.11 122 8/18/2025
0.0.1-pre.10 110 8/18/2025
0.0.1-pre.9 116 8/18/2025
0.0.1-pre.8 114 8/18/2025
0.0.1-pre.7 117 8/18/2025
0.0.1-pre.6 116 8/18/2025
0.0.1-pre.5 117 8/18/2025
0.0.1-pre.3 169 8/27/2025