Digitall.Dataverse.Testing
1.1.0-beta.5
See the version list below for details.
dotnet add package Digitall.Dataverse.Testing --version 1.1.0-beta.5
NuGet\Install-Package Digitall.Dataverse.Testing -Version 1.1.0-beta.5
<PackageReference Include="Digitall.Dataverse.Testing" Version="1.1.0-beta.5" />
<PackageVersion Include="Digitall.Dataverse.Testing" Version="1.1.0-beta.5" />
<PackageReference Include="Digitall.Dataverse.Testing" />
paket add Digitall.Dataverse.Testing --version 1.1.0-beta.5
#r "nuget: Digitall.Dataverse.Testing, 1.1.0-beta.5"
#:package Digitall.Dataverse.Testing@1.1.0-beta.5
#addin nuget:?package=Digitall.Dataverse.Testing&version=1.1.0-beta.5&prerelease
#tool nuget:?package=Digitall.Dataverse.Testing&version=1.1.0-beta.5&prerelease
Digitall.Dataverse.Testing
A comprehensive, in-memory testing framework for Microsoft Dataverse / Power Platform. It provides a lightweight IOrganizationService (and IOrganizationServiceAsync2) implementation that enables fast, deterministic unit tests without any connection to a live Dataverse environment.
Table of Contents
- Features
- Prerequisites
- Installation
- Quick Start
- Architecture Overview
- Core Components
- Query Support
- Organization Request Fakes
- Relationship Management
- Metadata Support
- Plugin Testing
- Configuration
- Project Structure
- Build & Test
- CI/CD & Release
- Contributing
- License
Features
| Category | Capability |
|---|---|
| CRUD Operations | Full Create, Retrieve, Update, Delete with real Dataverse error codes |
| Async Support | IOrganizationServiceAsync2 implementation with CancellationToken support |
| Query Engine | QueryExpression, QueryByAttribute, FetchXml (including aggregation) |
| LINQ | CreateQuery<T>() support for early-bound and late-bound LINQ queries |
| Relationships | 1:N, N:1, and N:N relationship association/disassociation |
| Plugin Testing | Fluent PluginExecutionContextBuilder for IPluginExecutionContext7 |
| Early-Bound | Auto-discovery of [ProxyTypesAssembly] types with reflection caching |
| Request Extensibility | Pluggable IOrganizationRequestFake architecture |
| Spy Support | SpyOrganizationRequestFake<TReq, TRes> for recording and verifying calls |
| Time Abstraction | TimeProvider injection for deterministic time-based tests |
| Error Fidelity | Authentic FaultException<OrganizationServiceFault> with real error codes |
| Performance | FastCloner deep cloning, cached reflection lookups |
Prerequisites
- .NET SDK 10.0+ (see
global.jsonfor exact version) - IDE: JetBrains Rider, Visual Studio 2022+, or VS Code with C# Dev Kit
Installation
Install from NuGet:
dotnet add package Digitall.Dataverse.Testing
Or add to your .csproj:
<PackageReference Include="Digitall.Dataverse.Testing" Version="1.0.0-beta.*" />
Quick Start
Basic CRUD Test
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Digitall.Dataverse.Testing;
public class AccountTests
{
[Test]
public void Should_CreateAndRetrieveEntity()
{
// Arrange — use the builder for a fully configured service
var service = new FakeDataverseBuilder().GetOrganizationService();
var account = new Entity("account") { ["name"] = "Contoso Ltd" };
// Act
var id = service.Create(account);
var retrieved = service.Retrieve("account", id, new ColumnSet("name"));
// Assert
Assert.That(retrieved["name"], Is.EqualTo("Contoso Ltd"));
}
}
Using Early-Bound Entities
Ensure your test assembly has the ProxyTypesAssembly attribute:
[assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssembly]
Then use typed entities directly:
var service = new FakeDataverseBuilder().GetOrganizationService();
var account = new Account { Name = "Contoso Ltd" };
var id = service.Create(account);
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Your Test Code │
├─────────────────────────────────────────────────────────────┤
│ FakeDataverseBuilder / FakePluginContextBuilder (Fluent) │
├─────────────────────────────────────────────────────────────┤
│ FakeOrganizationServiceAsync (IOrganizationServiceAsync2) │
│ └── FakeOrganizationService (IOrganizationService) │
│ ├── RequestFakeRegistry → IOrganizationRequestFake │
│ ├── EntityTypeResolver (reflection + caching) │
│ ├── FakeOrganizationServiceState (entity store) │
│ └── MetadataService (entity/relationship metadata) │
├─────────────────────────────────────────────────────────────┤
│ Logic Layer │
│ ├── QueryProcessor (orchestrates query execution) │
│ ├── ExpressionProcessor (filter/condition evaluation) │
│ ├── LinkedEntitiesProcessor (JOIN logic) │
│ ├── FetchProcessor (FetchXml → QueryExpression) │
│ ├── ConditionParser (50+ ConditionOperator types) │
│ └── FetchAggregation (COUNT, SUM, AVG, MIN, MAX, grouping) │
└─────────────────────────────────────────────────────────────┘
Core Components
FakeOrganizationService
The central class implementing IOrganizationService. It provides:
- CRUD:
Create,Retrieve,Update,Deletewith proper error handling - Queries:
RetrieveMultiplesupporting all three query types - Relationships:
Associate/Disassociatefor 1:N and N:N - Execute: Dispatches
OrganizationRequestto registeredIOrganizationRequestFakehandlers - Alternate Keys:
RetrievewithKeyAttributeCollectionsupport
var service = new FakeOrganizationService();
service.AddDefaultRequests(); // registers built-in request fakes
FakeOrganizationServiceAsync
Extends FakeOrganizationService with IOrganizationServiceAsync2, wrapping all operations in Task-returning methods with CancellationToken support. Ideal for testing code that uses IOrganizationServiceAsync or ServiceClient.
var service = new FakeOrganizationServiceAsync();
var id = await service.CreateAsync(entity, cancellationToken);
FakeDataverseBuilder
A fluent builder that creates a fully configured FakeOrganizationServiceAsync with default request fakes pre-registered:
var service = new FakeDataverseBuilder()
.AddData(account, contact) // pre-seed entities
.AddEntityMetadata(accountMetadata) // register metadata
.AddRelationships(m2mRelationship) // register relationships
.LoadMetadata("./metadata/") // load from XML files
.GetOrganizationService();
Builder extension methods:
.AddData(params Entity[])— pre-seed the in-memory store.AddOrganizationRequests(params IOrganizationRequestFake[])— register custom fakes.AddEntityMetadata(params EntityMetadata[])— register entity metadata.AddRelationships(params RelationshipMetadataBase[])— register relationships.LoadMetadata(string path)— loadEntityMetadatafrom XML (file or directory).AddConfig(key, defaultValue, value?)— add Dataverse environment variable definition/value entities.WithUserId(Guid)— set the current user ID.WithBusinessUnitId(Guid)— set the current business unit ID.WithMaxRetrieveCount(int)— set the max records per page.WithFiscalYearStart(DateOnly)— set the fiscal year start date
PluginExecutionContextBuilder
Fluent builder for creating IServiceProvider instances configured for plugin testing:
var serviceProvider = new PluginExecutionContextBuilder(organizationService)
.WithMessageName("Update")
.WithStage(40) // Post-operation
.WithMode(0) // Synchronous
.WithTarget(targetEntity)
.WithPreEntityImage(preImage)
.WithPostEntityImage(postImage)
.WithInputParameter("Target", entity)
.WithOutputParameter("id", resultId)
.WithSharedVariable("key", "value")
.WithInitiatingUserId(userId)
.WithDepth(1)
.WithCorrelationId(correlationId)
.BuildServiceProvider();
// Resolve plugin services
var context = (IPluginExecutionContext7)serviceProvider.GetService(typeof(IPluginExecutionContext7));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
FakePluginContextBuilder
Combines PluginExecutionContextBuilder with an auto-configured FakeOrganizationService, implementing IFakeDataverseBuilder<FakeOrganizationService>. Perfect for end-to-end plugin tests:
var builder = new FakePluginContextBuilder();
// Access the underlying service for data setup
var service = builder.GetOrganizationService();
service.Create(new Entity("account") { ["name"] = "Test" });
// Build the plugin context
var serviceProvider = builder
.WithMessageName("Create")
.WithTarget(entity)
.BuildServiceProvider();
SpyOrganizationRequestFake
A generic spy that records all Execute calls and allows configuring return values:
// Create a spy for a custom request
var spy = new SpyOrganizationRequestFake<MyCustomRequest, MyCustomResponse>(
(request, service) => new MyCustomResponse { Result = "OK" }
);
service.AddRequest(spy);
// Execute the request
service.Execute(new MyCustomRequest());
// Verify
Assert.That(spy.ReceivedRequests, Has.Count.EqualTo(1));
Query Support
QueryExpression
Full support for QueryExpression including:
- ColumnSet projection (specific columns or
AllColumns) - FilterExpression with nested
And/Orlogical operators - 50+ ConditionOperators (Equal, NotEqual, Like, In, Between, Null, fiscal year operators, etc.)
- LinkEntity joins (Inner, LeftOuter) with nested link entities
- OrderExpression (ascending/descending on multiple attributes)
- Paging via
PageInfowith properMoreRecordsandPagingCookiesupport - TopCount limiting
- Distinct result filtering
var query = new QueryExpression("contact")
{
ColumnSet = new ColumnSet("firstname", "lastname"),
Criteria = new FilterExpression(LogicalOperator.And)
{
Conditions =
{
new ConditionExpression("statecode", ConditionOperator.Equal, 0),
new ConditionExpression("lastname", ConditionOperator.Like, "Smith%")
}
},
LinkEntities =
{
new LinkEntity("contact", "account", "parentcustomerid", "accountid", JoinOperator.Inner)
{
EntityAlias = "acc",
Columns = new ColumnSet("name")
}
},
Orders = { new OrderExpression("lastname", OrderType.Ascending) },
PageInfo = new PagingInfo { Count = 50, PageNumber = 1, ReturnTotalRecordCount = true }
};
var result = service.RetrieveMultiple(query);
QueryByAttribute
Simplified attribute-based queries (internally converted to QueryExpression):
var query = new QueryByAttribute("account")
{
ColumnSet = new ColumnSet("name", "revenue")
};
query.AddAttributeValue("statecode", 0);
query.AddAttributeValue("ownerid", userId);
var result = service.RetrieveMultiple(query);
FetchXml
Full FetchXml parsing with conversion to QueryExpression:
var fetchXml = @"
<fetch top='10'>
<entity name='account'>
<attribute name='name' />
<attribute name='revenue' />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
<order attribute='name' />
</entity>
</fetch>";
var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
FetchXml Aggregation
Supports aggregate queries with grouping:
var fetchXml = @"
<fetch aggregate='true'>
<entity name='opportunity'>
<attribute name='estimatedvalue' alias='total_value' aggregate='sum' />
<attribute name='ownerid' alias='owner' groupby='true' />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</entity>
</fetch>";
var result = service.RetrieveMultiple(new FetchExpression(fetchXml));
Supported aggregates: count, countcolumn, sum, avg, min, max
Grouping: By attribute value, by date (day, week, month, quarter, year, fiscal period/year)
LINQ Queries
// Early-bound
var accounts = service.CreateQuery<Account>()
.Where(a => a.Name.StartsWith("Contoso"))
.ToList();
// Late-bound
var contacts = service.CreateQuery("contact")
.Where(c => (string)c["lastname"] == "Smith")
.ToList();
Organization Request Fakes
Built-in fakes for common Dataverse operations:
| Request Type | Fake Class | Description |
|---|---|---|
CreateRequest |
CreateFake |
Entity creation with duplicate detection |
RetrieveRequest |
RetrieveFake |
Entity retrieval with column projection |
RetrieveMultipleRequest |
RetrieveMultipleFake |
Query execution pipeline |
UpdateRequest |
UpdateFake |
Entity updates with existence validation |
DeleteRequest |
DeleteFake |
Entity deletion |
UpsertRequest |
UpsertFake |
Create-or-update semantics |
AssociateRequest |
AssociateFake |
Relationship association |
DisassociateRequest |
DisassociateFake |
Relationship disassociation |
SetStateRequest |
SetStateFake |
Entity state/status changes |
AssignRequest |
AssignRequestFake |
Record ownership assignment |
WhoAmIRequest |
WhoAmIFake |
Current user identity |
RetrieveEntityRequest |
RetrieveEntityFake |
Entity metadata retrieval |
RetrieveAllEntitiesRequest |
RetrieveAllEntitiesFake |
Retrieve metadata for all known entities |
QueryExpressionToFetchXmlRequest |
QueryExpressionToFetchXmlFake |
Convert a QueryExpression to FetchXml |
FetchXmlToQueryExpressionRequest |
FetchXmlToQueryExpressionFake |
Convert FetchXml to a QueryExpression |
ExecuteTransactionRequest |
ExecuteTransactionFake |
Batch transaction execution |
BulkDeleteRequest |
BulkDeleteFake |
Bulk delete operations |
Custom Request Fakes
Implement IOrganizationRequestFake or extend OrganizationRequestFake<TReq, TRes>:
public class MyCustomRequestFake : OrganizationRequestFake<MyCustomRequest, MyCustomResponse>
{
public override MyCustomResponse Execute(MyCustomRequest request, FakeOrganizationService service)
{
// Custom logic
return new MyCustomResponse { /* ... */ };
}
}
// Register
service.AddRequest(new MyCustomRequestFake());
Relationship Management
1:N Relationships
// Register relationship metadata
service.AddRelationship(new OneToManyRelationshipMetadata
{
SchemaName = "account_contacts",
ReferencedEntity = "account",
ReferencedAttribute = "accountid",
ReferencingEntity = "contact",
ReferencingAttribute = "parentcustomerid"
});
// Associate contacts to an account
service.Associate("account", accountId,
new Relationship("account_contacts"),
new EntityReferenceCollection
{
new EntityReference("contact", contactId1),
new EntityReference("contact", contactId2)
});
N:N Relationships
// Register M2M relationship
service.AddRelationship(new ManyToManyRelationshipMetadata
{
SchemaName = "systemuser_account",
Entity1LogicalName = "systemuser",
Entity1IntersectAttribute = "systemuserid",
Entity2LogicalName = "account",
Entity2IntersectAttribute = "accountid",
IntersectEntityName = "systemuser_account"
});
// Associate
service.Associate("account", accountId,
new Relationship("systemuser_account"),
new EntityReferenceCollection { new EntityReference("systemuser", userId) });
// Disassociate
service.Disassociate("account", accountId,
new Relationship("systemuser_account"),
new EntityReferenceCollection { new EntityReference("systemuser", userId) });
Metadata Support
Entity metadata enables type validation and relationship processing:
// Programmatic metadata registration
service.AddMetadata(new EntityMetadata
{
LogicalName = "account",
// ... attributes, relationships
});
// Load from serialized XML files (DataContractSerializer format)
var service = new FakeDataverseBuilder()
.LoadMetadata("./metadata/account.xml") // single file
.LoadMetadata("./metadata/") // all *.xml in directory
.GetOrganizationService();
Plugin Testing
End-to-End Plugin Test
[Test]
public void MyPlugin_OnAccountUpdate_ShouldSetModifiedFlag()
{
// Arrange
var builder = new FakePluginContextBuilder();
var service = builder.GetOrganizationService();
var account = new Entity("account") { Id = Guid.NewGuid(), ["name"] = "Old Name" };
service.Create(account);
var target = new Entity("account") { Id = account.Id, ["name"] = "New Name" };
var serviceProvider = builder
.WithMessageName("Update")
.WithStage(40)
.WithTarget(target)
.WithPreEntityImage(account, "PreImage")
.BuildServiceProvider();
// Act
var plugin = new MyPlugin();
plugin.Execute(serviceProvider);
// Assert
var updated = service.Retrieve("account", account.Id, new ColumnSet(true));
Assert.That(updated["modifiedflag"], Is.True);
}
Testing with TimeProvider
using Microsoft.Extensions.Time.Testing;
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero));
var service = new FakeOrganizationService(fakeTime);
// Advance time in tests
fakeTime.Advance(TimeSpan.FromDays(30));
Configuration
The FakeOrganizationService exposes a FakeDataverseOptions instance via the Options property for per-test configuration. This avoids process-global state (such as environment variables) and is safe for parallel test execution.
| Option | Type | Description | Default |
|---|---|---|---|
UserId |
Guid |
Current user ID (used in WhoAmI, EqualUserId filters, and default record ownership) |
Guid.Empty |
BusinessUnitId |
Guid |
Current business unit ID (used in WhoAmI and EqualBusinessId filters) |
Guid.Empty |
FiscalYearStart |
DateOnly? |
Start date for fiscal year calculations | null (defaults to Jan 1) |
MaxRetrieveCount |
int |
Maximum records returned by RetrieveMultiple per page |
5000 |
Configure via the builder:
var service = new FakeDataverseBuilder()
.WithUserId(userId)
.WithBusinessUnitId(businessUnitId)
.WithMaxRetrieveCount(100)
.WithFiscalYearStart(new DateOnly(2025, 4, 1))
.GetOrganizationService();
Or set directly on the service:
var service = new FakeOrganizationService();
service.Options.UserId = userId;
service.Options.MaxRetrieveCount = 100;
Project Structure
DigitallTesting/
├── src/Digitall.Dataverse.Testing/ # Core library (NuGet package)
│ ├── FakeOrganizationService.cs # IOrganizationService implementation
│ ├── FakeOrganizationServiceAsync.cs # IOrganizationServiceAsync2 implementation
│ ├── FakeOrganizationServiceState.cs # Internal entity store
│ ├── FakeDataverseOptions.cs # Per-instance configuration options
│ ├── FakeDataverseBuilder.cs # Fluent builder for service setup
│ ├── FakePluginContextBuilder.cs # Combined plugin + service builder
│ ├── IFakeDataverseBuilder.cs # Builder interface
│ ├── PluginExecutionContextBuilder.cs # Plugin context configuration
│ ├── EntityTypeResolver.cs # Early-bound type discovery & caching
│ ├── SpyOrganizationRequestFake.cs # Generic spy for request verification
│ ├── Extensions/ # Extension methods
│ │ ├── EntityExtensions.cs # Entity cloning, projection, joins
│ │ ├── FakeDataverseBuilderExtensions.cs # Builder fluent API
│ │ ├── FakeOrganizationServiceStateExtensions.cs # State helper extensions
│ │ ├── PluginExecutionContextBuilderExtensions.cs
│ │ ├── QueryExpressionExtensions.cs # Query helpers
│ │ ├── DateTimeExtensions.cs # Fiscal year/date utilities
│ │ ├── DeepCloneExtensions.cs # FastCloner integration
│ │ ├── TypeExtensions.cs # Reflection helpers
│ │ └── XDocumentExtensions.cs # FetchXml parsing
│ ├── Logic/ # Query processing engine
│ │ ├── Queries/
│ │ │ ├── QueryProcessor.cs # Query orchestration
│ │ │ ├── ExpressionProcessor.cs # Filter evaluation
│ │ │ ├── ConditionParser.cs # 50+ condition operators
│ │ │ ├── LinkedEntitiesProcessor.cs # JOIN logic
│ │ │ ├── FetchProcessor.cs # FetchXml → QueryExpression
│ │ │ ├── QueryExpressionExtensions.cs # Query-specific extensions
│ │ │ ├── TypedConditionExpression.cs # Typed condition wrapper
│ │ │ ├── Validators.cs # Type/attribute validation
│ │ │ └── FetchAggregation/ # Aggregate functions
│ │ │ ├── CountAggregate.cs
│ │ │ ├── CountColumnAggregate.cs
│ │ │ ├── CountDistinctAggregate.cs
│ │ │ ├── SumAggregate.cs
│ │ │ ├── AvgAggregate.cs
│ │ │ ├── MinAggregate.cs
│ │ │ ├── MaxAggregate.cs
│ │ │ └── ... # Grouping support
│ │ └── XrmOrderByAttributeComparer.cs # Sorting logic
│ ├── OrganizationRequests/ # Built-in request fakes
│ │ ├── IOrganizationRequestFake.cs # Extension interface
│ │ ├── OrganizationRequestFake.cs # Typed base class
│ │ ├── CreateFake.cs
│ │ ├── RetrieveFake.cs
│ │ ├── RetrieveMultipleFake.cs
│ │ ├── UpdateFake.cs
│ │ ├── DeleteFake.cs
│ │ ├── UpsertFake.cs
│ │ ├── AssociateFake.cs
│ │ ├── DisassociateFake.cs
│ │ ├── SetStateFake.cs
│ │ ├── AssignRequestFake.cs
│ │ ├── WhoAmIFake.cs
│ │ ├── RetrieveEntityFake.cs
│ │ ├── RetrieveAllEntitiesFake.cs
│ │ ├── QueryExpressionToFetchXmlFake.cs
│ │ ├── FetchXmlToQueryExpressionFake.cs
│ │ ├── ExecuteTransactionFake.cs
│ │ └── BulkDeleteFake.cs
│ ├── Model/ # Internal models
│ │ └── Target.cs # Plugin target wrapper
│ └── Errors/ # Error infrastructure
│ ├── ErrorCodes.cs # Dataverse error code constants
│ └── ErrorFactory.cs # FaultException factory
├── tests/Digitall.Dataverse.Testing.Tests/ # Unit tests
│ ├── FakeOrganizationServiceTests.cs
│ ├── FakeDataverseBuilderTests.cs
│ ├── FakePluginContextBuilderTests.cs
│ ├── PluginExecutionContextBuilderTests.cs
│ ├── Extensions/ # Extension method tests
│ ├── Logic/ # Query processor tests
│ ├── OrganizationRequests/ # Request fake tests
│ └── Fixtures/ # Test data and helpers
├── .github/workflows/ # CI/CD
│ ├── build.yml # PR build + test
│ ├── release.yml # Semantic release + NuGet publish
│ ├── checks.yml # Additional checks
│ ├── codeql.yml # Security scanning
│ └── qodana_code_quality.yml # JetBrains Qodana analysis
├── Directory.Build.props # Shared MSBuild properties & versioning
├── global.json # .NET SDK version pinning
├── package.json # semantic-release & commitlint config
└── qodana.yaml # Qodana configuration
Build & Test
# Restore dependencies
dotnet restore
# Build the solution
dotnet build --configuration Release
# Run all tests
dotnet test --configuration Release
# Run specific tests by filter
dotnet test --filter "Name~QueryProcessor"
# Run tests with detailed output
dotnet test -- --output Detailed
CI/CD & Release
This project uses semantic-release for automated versioning and publishing:
- Build workflow (
build.yml): Runs on all non-release branches; builds and tests the solution. - Release workflow (
release.yml): Runs onmainandbetabranches; performs semantic versioning, NuGet publish, changelog generation, and GitHub release creation. - Commit conventions: Conventional Commits enforced via commitlint and Husky git hooks.
Commit Message Format
type(scope): description
feat: add FetchXml aggregate support → minor version bump
fix: correct paging cookie generation → patch version bump
feat!: remove deprecated API → major version bump
Contributing
- Fork the repository
- Create a feature branch (
feat/my-feature) - Write tests for your changes
- Ensure all tests pass (
dotnet test) - Commit using Conventional Commits
- Open a Pull Request
License
This project is licensed under the Microsoft Reciprocal License (MS-RL). See LICENSE.md for details.
© 2024 DIGITALL Nature GmbH
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FastCloner (>= 3.5.5)
- FastCloner.SourceGenerator (>= 1.2.1)
- Microsoft.Extensions.TimeProvider.Testing (>= 10.6.0)
- Microsoft.PowerPlatform.Dataverse.Client (>= 1.2.10)
- System.Security.Cryptography.Xml (>= 10.0.8)
- TUnit.Mocks (>= 1.45.22)
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 |
|---|---|---|
| 1.1.0-beta.6 | 42 | 5/22/2026 |
| 1.1.0-beta.5 | 48 | 5/22/2026 |
| 1.1.0-beta.4 | 47 | 5/22/2026 |
| 1.1.0-beta.3 | 33 | 5/22/2026 |
| 1.1.0-beta.2 | 37 | 5/22/2026 |
| 1.1.0-beta.1 | 34 | 5/22/2026 |
| 1.0.0 | 42 | 5/21/2026 |
| 1.0.0-beta.20 | 31 | 5/21/2026 |