eQuantic.Core.Domain
5.0.0
dotnet add package eQuantic.Core.Domain --version 5.0.0
NuGet\Install-Package eQuantic.Core.Domain -Version 5.0.0
<PackageReference Include="eQuantic.Core.Domain" Version="5.0.0" />
<PackageVersion Include="eQuantic.Core.Domain" Version="5.0.0" />
<PackageReference Include="eQuantic.Core.Domain" />
paket add eQuantic.Core.Domain --version 5.0.0
#r "nuget: eQuantic.Core.Domain, 5.0.0"
#:package eQuantic.Core.Domain@5.0.0
#addin nuget:?package=eQuantic.Core.Domain&version=5.0.0
#tool nuget:?package=eQuantic.Core.Domain&version=5.0.0
eQuantic Core DDD
A small, focused set of .NET building blocks for applications built with Domain-Driven Design (DDD) and Clean Architecture.
These packages were extracted from eQuantic/core-api into their own repository so that the DDD / Clean-Architecture layers can evolve and be versioned on their own. They give you the conventions — base entities, request/response contracts, layered exceptions, and Entity Framework Core model conventions — so your own aggregates stay thin and consistent.
| Target frameworks | net8.0, net10.0 (plus netstandard2.1 for eQuantic.Core.Exceptions) |
| Language | C# (latest, nullable + implicit usings enabled) |
| License | MIT |
| Versioning | Semantic Versioning via semantic-release (see Versioning & releases) |
Table of contents
- Why these packages?
- The packages at a glance
- How the layers fit together
- Installation
- Package deep-dive
- End-to-end walkthrough
- Versioning & releases
- Contributing
- License
Why these packages?
Clean Architecture asks you to keep a strict dependency direction: your domain knows nothing about the outside world, your application orchestrates the domain, and infrastructure (databases, HTTP) depends inward — never the reverse.
Infrastructure ─────► Application ─────► Domain
(EF Core, HTTP) (use cases) (entities, rules)
In practice, every service re-implements the same plumbing: an entity base class with an Id, a paged-list request that reads pageIndex/filterBy/orderBy from the query string, a set of exceptions that map cleanly to HTTP status codes, and a bag of EF Core conventions so tables are named consistently and audit columns are wired up automatically.
eQuantic Core DDD packages that plumbing once, so you don't. Each package targets one layer, and you only take the ones you need — the domain package has no database dependency, the persistence packages have no HTTP dependency, and so on.
The packages at a glance
| Package | What it gives you | Depends on |
|---|---|---|
eQuantic.Core.Domain |
Entity base types, audit interfaces, request/result contracts, query-string filtering & sorting | eQuantic.Core, eQuantic.Linq.Web |
eQuantic.Core.Application |
Application-layer conventions: IApplicationContext, service markers, an injectable clock |
eQuantic.Core.Domain |
eQuantic.Core.Exceptions |
A vocabulary of domain exceptions that map to HTTP semantics | — (stand-alone) |
eQuantic.Core.Persistence |
EF Core auditing conventions + JSON seed helper (provider-agnostic) | eQuantic.Core.Application, eQuantic.Core.DataModel |
eQuantic.Core.Persistence.Relational |
Table naming (pluralize + case), fully-qualified PKs, user-audit foreign keys | eQuantic.Core.Persistence |
eQuantic.Core.Persistence.PostgreSql |
PostgreSQL defaults (snake_case, UTC CreatedAt default) |
…Persistence.Relational |
eQuantic.Core.Persistence.MySql |
MySQL defaults (snake_case, UTC CreatedAt default) |
…Persistence.Relational |
eQuantic.Core.Persistence.SqlServer |
SQL Server defaults (PascalCase, UTC CreatedAt default) |
…Persistence.Relational |
eQuantic.Core.Persistence.MongoDb |
MongoDB collection/element naming conventions | …Persistence |
The persistence packages build on the companion package
eQuantic.Core.DataModel, which owns the user-audit contracts (IEntityOwned,IEntityTrack,IEntityHistory) and theEntityDataBasedata-model base type.eQuantic.Core.Domainowns the time-audit contracts (IEntityTimeMark,IEntityTimeTrack,IEntityTimeEnded). The conventions understand both families — see Persistence.
How the layers fit together
graph TD
subgraph Domain layer
D[eQuantic.Core.Domain]
end
subgraph Application layer
A[eQuantic.Core.Application]
end
subgraph Infrastructure layer
P[eQuantic.Core.Persistence]
R[eQuantic.Core.Persistence.Relational]
PG[eQuantic.Core.Persistence.PostgreSql]
MY[eQuantic.Core.Persistence.MySql]
MS[eQuantic.Core.Persistence.SqlServer]
MG[eQuantic.Core.Persistence.MongoDb]
end
X[eQuantic.Core.Exceptions]
A --> D
P --> A
R --> P
PG --> R
MY --> R
MS --> R
MG --> P
style D fill:#2d6a4f,color:#fff
style A fill:#40916c,color:#fff
style X fill:#9d4edd,color:#fff
The arrows are project references — and they only ever point inward (toward the domain), which is exactly what Clean Architecture requires. eQuantic.Core.Exceptions is deliberately stand-alone so any layer can throw its exceptions without dragging in a dependency.
Installation
Take only what your layer needs. A typical web API that talks to PostgreSQL uses three of them:
dotnet add package eQuantic.Core.Domain
dotnet add package eQuantic.Core.Exceptions
dotnet add package eQuantic.Core.Persistence.PostgreSql
Because the persistence packages reference each other transitively, adding eQuantic.Core.Persistence.PostgreSql also brings in …Relational, …Persistence, …Application and …Domain. Swap the last line for …MySql, …SqlServer or …MongoDb to target a different store.
Package deep-dive
eQuantic.Core.Domain
The heart of the domain layer: base types and contracts your entities and API requests build on. It references Microsoft.AspNetCore.App so the request types can carry MVC/Minimal-API binding attributes, but it has no database dependency.
Entities
IDomainEntity<TKey> is the minimal contract — an entity that can surface and set its key:
public interface IDomainEntity<TKey> : IDomainEntity
{
TKey GetKey();
void SetKey(TKey key);
}
EntityBase<TKey> implements it, and EntityBase is the convenient int-keyed shortcut. EntityDescriptionBase<TKey> adds a nullable Description.
using eQuantic.Core.Domain.Attributes;
using eQuantic.Core.Domain.Entities;
[Entity("Product")] // logical name, e.g. for auditing / messaging
public class Product : EntityBase<Guid>,
IEntityTimeMark, // CreatedAt (required)
IEntityTimeTrack, // UpdatedAt (nullable)
IEntityTimeEnded, // DeletedAt (nullable → soft delete)
IWithSlug
{
public string Name { get; set; } = string.Empty;
public decimal Total { get; set; }
public string Slug { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime? DeletedAt { get; set; }
}
The time-audit interfaces are opt-in marker interfaces. Implement only the ones you need; the persistence conventions detect them and configure the columns for you (see Persistence):
| Interface | Property | Meaning |
|---|---|---|
IEntityTimeMark |
DateTime CreatedAt |
Creation timestamp (required) |
IEntityTimeTrack |
DateTime? UpdatedAt |
Last-modified timestamp |
IEntityTimeEnded |
DateTime? DeletedAt |
Soft-delete timestamp |
IWithSlug marks entities that expose a URL-friendly Slug, and [Entity("name")] attaches a stable logical name to a class.
Request contracts
A family of request DTOs models the shapes an HTTP API repeatedly needs. They are pre-decorated with binding attributes ([FromRoute], [FromQuery], [FromBody]) so they bind directly in controllers and Minimal APIs.
| Request | Binds | Use for |
|---|---|---|
ItemRequest<TKey> |
Id from route |
"operate on one item by id" |
GetRequest<TKey> |
Id + IncludeFields (query) |
reads with optional field expansion |
CreateRequest<TBody> |
Body from body |
create |
UpdateRequest<TBody, TKey> |
Id (route) + Body (body) |
update |
PagedListRequest<TEntity> |
pageIndex, pageSize, filterBy, orderBy, includeFields |
list endpoints |
Each has a referenced variant (…<…, TReferenceKey>) implementing IReferencedRequest<TReferenceKey>, for nested/sub-resource routes such as /categories/{categoryId}/products/{id} — it carries the parent (categoryId) key alongside the item key:
// GET /categories/{categoryId}/products/{id}
var request = new GetRequest<Guid, Guid>(categoryId, productId);
Guid? parent = request.GetReferenceId(); // categoryId
Query-string filtering & sorting
PagedListRequest<TEntity> exposes FilterBy and OrderBy as typed, bindable collections powered by the eQuantic.Linq v3 syntax. Clients express filters and ordering in the URL and you get back a compiled predicate and typed sorts:
GET /v1/products?pageIndex=1&pageSize=20&filterBy=total:gt(100),name:ct(pro)&orderBy=total:desc,name
filterBy=total:gt(100),name:ct(pro)→total > 100 AND name contains "pro"orderBy=total:desc,name→ order bytotaldescending, thennameascending
public IReadOnlyList<Product> Query(PagedListRequest<Product> request, IQueryable<Product> source)
{
// Turn the query string into a real Expression<Func<Product, bool>> (null if no filter):
var predicate = request.GetFilterPredicate();
if (predicate is not null)
source = source.Where(predicate);
// Typed sorts you can translate into OrderBy/ThenBy:
IReadOnlyList<QuerySort<Product>> sorts = request.GetSorts();
var page = (request.PageIndex ?? 1);
var size = (request.PageSize ?? 20);
return source.Skip((page - 1) * size).Take(size).ToList();
}
FilteringCollection<TEntity> and SortingCollection<TEntity> also expose static TryParse (for MVC attribute binding) and BindAsync (for direct Minimal-API parameters), so they bind natively either way.
Results
PagedListResult<T> is the counterpart response — items plus paging metadata (PageIndex, PageSize, TotalCount). It can be built from raw values or from an IPagedEnumerable<T>:
return new PagedListResult<Product>(items, pageIndex: 1, pageSize: 20, total: 137);
eQuantic.Core.Application
Conventions for the application (use-case) layer. Small on purpose.
IApplicationContext/IApplicationContext<TUserKey>— an abstraction over "the current runtime": appVersion,LocalPath,LastUpdate, plus the current user (GetCurrentUserIdAsync,GetCurrentUserRolesAsync,CurrentUserIsInRoleAsync). Implement it once against your auth stack; your use cases depend on the interface, not onHttpContext.IApplicationService— a marker interface for application services (useful for assembly-scanning registrations).IDateTimeProviderService+DateTimeProviderService— an injectable clock (GetUtcNow,GetLocalNow,GetTimestamp). Onnet8.0it wraps the frameworkTimeProvider; elsewhere it falls back toDateTimeOffset. Injecting the clock instead of callingDateTime.UtcNowdirectly makes time deterministic in tests.
using eQuantic.Core.Application.Extensions;
// Program.cs — registers TimeProvider (on net8) and IDateTimeProviderService as singletons
builder.Services.AddDateTimeProviderService();
public class CreateOrder(IDateTimeProviderService clock)
{
public Order Execute(/* … */)
{
var order = new Order { CreatedAt = clock.GetUtcNow().UtcDateTime };
// …
return order;
}
}
eQuantic.Core.Exceptions
A vocabulary of exceptions that carry domain meaning, so the edge of your app can translate them into HTTP responses in one place. All are [Serializable] and message-localized via resources. This package is stand-alone (netstandard2.1;net8.0;net10.0).
| Exception | Meaning | Natural HTTP status |
|---|---|---|
EntityNotFoundException / EntityNotFoundException<TKey> |
The requested entity does not exist (carries the Id) |
404 Not Found |
NoDataFoundException |
A query returned no data for an entity type | 404 Not Found |
InvalidEntityReferenceException<TReferenceKey> |
A referenced parent/related entity is invalid | 400 Bad Request |
InvalidEntityRequestException |
Request validation failed (carries an Errors dictionary) |
422 Unprocessable Entity |
ForbiddenAccessException |
The caller may not perform the action | 403 Forbidden |
DependencyNotFoundException |
A required dependency/service was not resolved (carries the Type) |
500 Internal Server Error |
Map them to responses once, e.g. with the ASP.NET Core exception handler:
using eQuantic.Core.Exceptions;
using Microsoft.AspNetCore.Diagnostics;
app.UseExceptionHandler(handler => handler.Run(async context =>
{
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var (status, title) = ex switch
{
EntityNotFoundException => (StatusCodes.Status404NotFound, "Entity not found"),
NoDataFoundException => (StatusCodes.Status404NotFound, "No data found"),
InvalidEntityReferenceException => (StatusCodes.Status400BadRequest, "Invalid reference"),
InvalidEntityRequestException => (StatusCodes.Status422UnprocessableEntity,"Validation failed"),
ForbiddenAccessException => (StatusCodes.Status403Forbidden, "Forbidden"),
DependencyNotFoundException => (StatusCodes.Status500InternalServerError,"Dependency not found"),
_ => (StatusCodes.Status500InternalServerError,"Unexpected error"),
};
context.Response.StatusCode = status;
await context.Response.WriteAsJsonAsync(new { status, title, detail = ex?.Message });
}));
// Throwing from a use case then reads naturally:
var product = await repository.FindAsync(id)
?? throw new EntityNotFoundException<Guid>(id);
eQuantic.Core.Persistence
Provider-agnostic Entity Framework Core conventions. The star is a single ModelBuilder extension you call from OnModelCreating:
using eQuantic.Core.Persistence.Extensions;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyDataModelConventions(o => o.EnableEntityAuditing());
}
When entity auditing is enabled, the convention walks every entity type and configures its audit columns automatically, based on the interfaces the entity implements:
| Implements | Column configured | Nullability |
|---|---|---|
IEntityTimeMark (Domain) |
CreatedAt |
required |
IEntityTimeTrack (Domain) |
UpdatedAt |
nullable |
IEntityTimeEnded (Domain) |
DeletedAt |
nullable |
IEntityOwned<…> (DataModel) |
CreatedById |
required |
IEntityTrack<…> (DataModel) |
UpdatedById |
nullable |
IEntityHistory<…> (DataModel) |
DeletedById |
nullable |
So the time audit interfaces come from eQuantic.Core.Domain and the who did it (user) audit interfaces come from eQuantic.Core.DataModel; the convention understands both.
It also ships HasJsonData, a small helper to seed a table from an embedded JSON resource:
modelBuilder.Entity<Country>()
.HasJsonData<Country>("SeedData.countries.json", typeof(MyDbContext).Assembly);
DataModelConventionOptions currently exposes EnableEntityAuditing(bool), and the shared NamingCase enum (PascalCase, CamelCase, SnakeCase) used by the relational and document conventions below.
eQuantic.Core.Persistence.Relational
Everything in the base package, plus relational naming conventions. Call ApplyRelationalDataModelConventions and each entity gets a consistent table name and (optionally) fully-qualified primary-key columns and audit foreign keys:
using eQuantic.Core.Persistence.Relational.Extensions;
modelBuilder.ApplyRelationalDataModelConventions(o => o
.UseSnakeCase() // table & column casing
.UseFullyQualifiedPrimaryKeys() // Product.Id → product_id
.RemoveEntitySuffixFromTableName("Entity") // ProductEntity → "products"
.EnableEntityAuditing());
What it does for every entity:
- Table names are pluralized (via Humanizer) and cased per
NamingCase—Product→products(snake) /Products(pascal). RemoveEntitySuffixFromTableName("Data")strips a class-name suffix before pluralizing, soProductDatamaps toproducts.UseFullyQualifiedPrimaryKeys()renames theIdcolumn to{entity}_id(product_id), which many teams prefer for joins.- User-audit relationships — for entities implementing
IEntityOwned<TUser, TUserKey>/IEntityTrack<…>/IEntityHistory<…>, it wires theCreatedBy/UpdatedBy/DeletedBynavigations to the user entity as foreign keys withDeleteBehavior.NoAction.
There's also a DbContextOptionsBuilder helper to switch on the matching EFCore.NamingConventions provider:
options.UseDataModelNamingConvention(NamingCase.SnakeCase);
Why both a model convention and a
DbContextOptionsconvention? The model convention names your entities' tables/keys;EFCore.NamingConventionsadditionally cases the columns EF generates itself (shadow properties, join tables). Using both keeps the whole schema consistent.
Provider packages
Each provider package is a thin layer over the relational conventions that picks sensible defaults for that database and sets the right SQL default for CreatedAt. You call one method and get an idiomatic schema.
| Package | Default casing | Fully-qualified PKs | CreatedAt SQL default |
Entry point |
|---|---|---|---|---|
…PostgreSql |
snake_case | on | NOW() AT TIME ZONE 'UTC' |
ApplyPostgreSqlDataModelConventions() |
…MySql |
snake_case | on | UTC_TIMESTAMP() |
ApplyMySqlDataModelConventions() |
…SqlServer |
PascalCase | on | GETUTCDATE() |
ApplySqlServerDataModelConventions() |
…MongoDb |
(as configured) | n/a | n/a | ApplyMongoDbDataModelConventions() |
Every default is overridable through the options callback (the same fluent options as the relational layer). For example, on PostgreSQL:
using eQuantic.Core.Persistence.PostgreSql.Extensions;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Defaults: snake_case tables, product_id-style PKs, auditing on,
// and created_at DEFAULT (NOW() AT TIME ZONE 'UTC').
modelBuilder.ApplyPostgreSqlDataModelConventions();
}
The MongoDB package is document-oriented: instead of table/PK naming it applies collection and element naming (UseSnakeCase() / UseCamelCase() / UsePascalCase(), RemoveEntitySuffixFromCollectionName()), still honouring the audit conventions.
End-to-end walkthrough
Putting the layers together for a PostgreSQL-backed product API.
1. Domain entity (eQuantic.Core.Domain)
[Entity("Product")]
public class Product : EntityBase<Guid>, IEntityTimeMark, IEntityTimeTrack, IEntityTimeEnded
{
public string Name { get; set; } = string.Empty;
public decimal Total { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime? DeletedAt { get; set; }
}
2. DbContext with conventions (eQuantic.Core.Persistence.PostgreSql)
public class ShopDbContext(DbContextOptions<ShopDbContext> options) : DbContext(options)
{
public DbSet<Product> Products => Set<Product>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyPostgreSqlDataModelConventions();
// Products table: snake_case, product_id PK, created_at default = UTC now.
}
}
3. Composition root (Program.cs)
builder.Services.AddDbContext<ShopDbContext>(o => o
.UseNpgsql(connectionString)
.UsePostgreSqlDataModelNamingConvention());
builder.Services.AddDateTimeProviderService();
4. A paged, filterable endpoint (eQuantic.Core.Domain requests + results)
// GET /v1/products?pageIndex=1&pageSize=20&filterBy=total:gt(100)&orderBy=total:desc
app.MapGet("/v1/products", async (
[AsParameters] PagedListRequest<Product> request,
ShopDbContext db) =>
{
IQueryable<Product> query = db.Products;
var predicate = request.GetFilterPredicate();
if (predicate is not null)
query = query.Where(predicate);
var page = request.PageIndex ?? 1;
var size = request.PageSize ?? 20;
var total = await query.LongCountAsync();
var items = await query.Skip((page - 1) * size).Take(size).ToListAsync();
return Results.Ok(new PagedListResult<Product>(items, page, size, total));
});
5. Domain errors → HTTP (eQuantic.Core.Exceptions) — wire the exception handler shown above, then simply throw new EntityNotFoundException<Guid>(id) anywhere and the edge translates it to 404.
Versioning & releases
This repository follows Semantic Versioning and publishes to NuGet automatically with semantic-release. You never bump a version by hand — the version is computed from commit messages.
Baseline: v4.0.0
These packages were extracted from core-api at its v4.0.0 release, so this repo starts at 4.0.0. That baseline is recorded as a git tag v4.0.0, which is what semantic-release reads to decide the next number.
How the next version is chosen
On every push to main (or preview), semantic-release looks at the commits since the last tag and bumps accordingly:
| Commit type | Example | Resulting bump | From 4.0.0 → |
|---|---|---|---|
fix: / perf: |
🐛 fix: correct paging offset |
patch | 4.0.1 |
feat: |
✨ feat: add slug filtering |
minor | 4.1.0 |
feat!: / BREAKING CHANGE: |
✨ feat!: rename request contract |
major | 5.0.0 |
docs: / chore: / refactor: / test: / ci: |
📝 docs: expand README |
none | (no release) |
So the first feat: commit after the v4.0.0 tag releases 4.1.0 — exactly the intended next version. docs/chore/ci commits ship no release, which is why writing this README does not itself bump the version.
When a release fires, the pipeline (.github/workflows/release.yml) runs the test matrix, then semantic-release:
- computes the version and updates
CHANGELOG.md+src/Directory.Build.props, - packs all
src/**projects with that version, - pushes the
.nupkg+.snupkgsymbols to NuGet.org, - creates the
v{version}git tag and GitHub release, - commits the changelog/version bump back with
[skip ci].
Commit message format
Commits use the emoji type: description house style (the leading gitmoji is optional to the analyzer):
✨ feat: add product slug filtering
🐛 fix: correct paging offset in list endpoint
📝 docs: document persistence conventions
♻️ refactor: simplify request binding
✅ test: cover snake_case table naming
🔧 chore: bump EF Core to 10.0.3
Contributing
- Branch off
main. - Build and test both target frameworks:
dotnet test eQuantic.Core.DDD.sln -c Release - Commit using the conventional format above — the commit type is what drives the released version.
- Open a pull request against
main. CI (.github/workflows/ci.yml) builds and tests on Linux and Windows and smoke-packs the packages.
License
Released under the MIT License. © eQuantic Tech.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- eQuantic.Core (>= 1.8.4)
- eQuantic.Core.DomainEvents (>= 0.0.2)
- eQuantic.Linq.Web (>= 3.7.1)
-
net8.0
- eQuantic.Core (>= 1.8.4)
- eQuantic.Core.DomainEvents (>= 0.0.2)
- eQuantic.Linq.Web (>= 3.7.1)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on eQuantic.Core.Domain:
| Package | Downloads |
|---|---|
|
eQuantic.Core.Data
eQuantic Core Data Class Library |
|
|
eQuantic.Core.Application
eQuantic Application Library |
|
|
eQuantic.Core.Application.Crud
eQuantic Application CRUD Library |
|
|
eQuantic.Core.DataModel
eQuantic Data Model Library |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.0 | 38 | 7/24/2026 |
| 4.0.0 | 524 | 7/22/2026 |
| 3.0.0 | 346 | 7/21/2026 |
| 2.1.0 | 251 | 7/18/2026 |
| 2.0.0 | 269 | 7/18/2026 |
| 1.9.3 | 354 | 3/2/2026 |
| 1.9.2 | 1,150 | 6/22/2025 |
| 1.9.1 | 469 | 6/22/2025 |
| 1.9.0 | 408 | 6/22/2025 |
| 1.8.2 | 336 | 6/21/2025 |
| 1.8.1 | 449 | 6/17/2025 |
| 1.8.0 | 421 | 5/30/2025 |
| 1.7.23 | 358 | 5/10/2025 |
| 1.7.22 | 236 | 4/19/2025 |
| 1.7.21 | 241 | 4/19/2025 |
| 1.7.20 | 253 | 2/27/2025 |
| 1.7.19 | 286 | 2/23/2025 |
| 1.7.18 | 274 | 2/18/2025 |
| 1.7.17 | 341 | 1/21/2025 |
| 1.7.16 | 485 | 1/10/2025 |
Generic domain for applications