AdaskoTheBeAsT.WkHtmlToX
13.0.0
dotnet add package AdaskoTheBeAsT.WkHtmlToX --version 13.0.0
NuGet\Install-Package AdaskoTheBeAsT.WkHtmlToX -Version 13.0.0
<PackageReference Include="AdaskoTheBeAsT.WkHtmlToX" Version="13.0.0" />
<PackageVersion Include="AdaskoTheBeAsT.WkHtmlToX" Version="13.0.0" />
<PackageReference Include="AdaskoTheBeAsT.WkHtmlToX" />
paket add AdaskoTheBeAsT.WkHtmlToX --version 13.0.0
#r "nuget: AdaskoTheBeAsT.WkHtmlToX, 13.0.0"
#:package AdaskoTheBeAsT.WkHtmlToX@13.0.0
#addin nuget:?package=AdaskoTheBeAsT.WkHtmlToX&version=13.0.0
#tool nuget:?package=AdaskoTheBeAsT.WkHtmlToX&version=13.0.0
WkHtmlToX
A C# wrapper for wkhtmltopdf, with one dedicated native execution thread for HTML-to-PDF and HTML-to-image conversion.
Why this major release is better
Version 13.0.0 (unreleased) makes ownership and failure handling explicit:
- A second native engine fails fast instead of allowing concurrent access to process-global Qt state.
- Application callback exceptions cannot escape into native code. Results distinguish invalid input, rendering, stream I/O, callback, and native failures.
- Submitted settings, dictionaries, paper sizes, and byte arrays are snapshotted. Later changes cannot silently alter a queued conversion.
- Slow input reads and destination writes no longer occupy the native thread. Request admission and aggregate managed input/output buffering have explicit limits.
- Shutdown coordinates the entire pipeline, not just the native queue. Host deadlines stop waiting without prematurely releasing streams or native ownership.
- Rejected native settings are reported rather than silently ignored.
- Async lifecycle and health APIs use the corrected Interop.Execution 2.x worker. Cleanup failures are observable; incomplete native teardown requires a process restart.
- Native loading uses one pinned library binding, not an unload/reload promise that conflicts with cached P/Invoke addresses.
See the migration guide and CHANGELOG. No throughput improvement is claimed without benchmarks.
Installation
dotnet add package AdaskoTheBeAsT.WkHtmlToX
# Choose the native binary for the actual process architecture:
dotnet add package AdaskoTheBeAsT.WkHtmlToX.native.win.x64 --version 0.12.6
# Optional integrations:
dotnet add package AdaskoTheBeAsT.WkHtmlToX.DependencyInjection
dotnet add package AdaskoTheBeAsT.WkHtmlToX.Hosting
Keep the three managed packages on the same release. The core package does not include a renderer binary. Never render untrusted HTML in your application process: upstream explicitly warns against it. A child process is useful for crash containment but is not itself a security sandbox.
Console example
This complete example is compiled and run by scripts/verify-packages.ps1.
Call ConsoleExample.RunAsync(cancellationToken) from your application.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AdaskoTheBeAsT.WkHtmlToX.Documents;
using AdaskoTheBeAsT.WkHtmlToX.Engine;
using AdaskoTheBeAsT.WkHtmlToX.Settings;
using AdaskoTheBeAsT.WkHtmlToX.Utils;
public static class ConsoleExample
{
public static async Task RunAsync(CancellationToken cancellationToken)
{
var configuration = new WkHtmlToXConfiguration
{
WorkerOptions = new WkHtmlToXWorkerOptions
{
MaxOperationsPerSession = 500,
DisposeTimeout = TimeSpan.FromSeconds(30),
ShutdownMode = WkHtmlToXShutdownMode.Drain,
},
RequestOptions = new WkHtmlToXRequestOptions
{
MaxConcurrentRequests = 16,
MaxInputBytes = 8 * 1024 * 1024,
MaxBufferedInputBytes = 32 * 1024 * 1024,
MaxOutputBytes = 16 * 1024 * 1024,
MaxBufferedOutputBytes = 64 * 1024 * 1024,
},
};
await using var engine = new WkHtmlToXEngine(configuration);
await engine.InitializeAsync(cancellationToken);
var document = new HtmlToPdfDocument
{
GlobalSettings = new PdfGlobalSettings { PaperSize = PaperKind.A4 },
ObjectSettings =
{
new PdfObjectSettings { HtmlContent = "<html><body>Hello!</body></html>" },
},
};
using var output = new MemoryStream();
var result = await engine.ConvertPdfAsync(document, _ => output, cancellationToken);
if (!result.Success || output.Length == 0)
{
throw new InvalidOperationException("PDF conversion failed: " + result.FailureKind);
}
// The stream remains yours. Rewind before reading or sending it.
output.Position = 0;
}
}
ConvertImageAsync uses HtmlToImageDocument.ImageSettings.In (URL or file path).
For headers and footers, use SectionSettings.Left, Center, Right, or
HtmlUrl, not a nonexistent HtmlContent setting.
Generic host
The hosting package initializes the engine and shuts down its whole conversion
pipeline before stopping the native worker. The engine and
worker are singletons; IPdfConverter and IImageConverter are transient
facades sharing that engine. IWkHtmlToXEngine and IWkHtmlToXAsyncEngine
resolve to the same singleton.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AdaskoTheBeAsT.WkHtmlToX.Documents;
using AdaskoTheBeAsT.WkHtmlToX.Engine;
using AdaskoTheBeAsT.WkHtmlToX.Hosting;
using AdaskoTheBeAsT.WkHtmlToX.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public static class HostedExample
{
public static async Task RunAsync(CancellationToken cancellationToken)
{
using var host = new HostBuilder()
.ConfigureServices(services => services.AddWkHtmlToXHostedService(
new WkHtmlToXConfiguration
{
WorkerOptions = new WkHtmlToXWorkerOptions { MaxOperationsPerSession = 500 },
}))
.Build();
await host.StartAsync(cancellationToken);
var engine = host.Services.GetRequiredService<IWkHtmlToXAsyncEngine>();
using var output = new MemoryStream();
var result = await engine.ConvertImageAsync(
new HtmlToImageDocument
{
ImageSettings = new ImageSettings { In = "about:blank", Format = "png" },
},
_ => output,
cancellationToken);
if (!result.Success || output.Length == 0)
{
throw new InvalidOperationException("Image conversion failed: " + result.FailureKind);
}
await host.StopAsync(cancellationToken);
}
}
For ASP.NET Core, call the same registration extension on builder.Services.
Return a completed, rewound stream to the HTTP response, rather than assuming
the native renderer can be interrupted by HttpContext.RequestAborted.
Plain dependency injection
Use this without a generic host. Initialize the async engine at startup. Dispose the service provider at application shutdown; the shared engine coordinates pipeline shutdown and worker disposal, regardless of container disposal order. Do not dispose an injected engine per request.
using System.Threading;
using System.Threading.Tasks;
using AdaskoTheBeAsT.WkHtmlToX.DependencyInjection;
using AdaskoTheBeAsT.WkHtmlToX.Engine;
using Microsoft.Extensions.DependencyInjection;
public static class DiExample
{
public static async Task RunAsync(CancellationToken cancellationToken)
{
var services = new ServiceCollection();
services.AddWkHtmlToX(new WkHtmlToXConfiguration());
await using var provider = services.BuildServiceProvider();
var engine = provider.GetRequiredService<IWkHtmlToXAsyncEngine>();
await engine.InitializeAsync(cancellationToken);
// At application shutdown. Drain is the default; CancelPending skips
// requests not yet in native execution, without aborting active work.
await engine.ShutdownAsync(cancellationToken);
}
}
Request ownership and resource limits
Admission and input
- Do not mutate a document while calling the conversion method. Once it returns its task, built-in settings, object collections, byte arrays, encodings, and dictionaries have been copied. Settings subclasses are rejected because the library cannot guarantee ownership of arbitrary additional state.
- PDF objects require exactly one input: nonempty
HtmlContent,HtmlContentByteArray,HtmlContentStream,Page, orXsl. A PDF requires at least one non-null object. Image requests requireIn. Standard input ("-") and native file/stdout output (Out) are rejected. - HTML streams are borrowed, must be readable and seekable, and are read from their current position. Do not read, seek, mutate, or dispose them until the conversion task is terminal. Non-seekable input is rejected before native initialization; buffer it yourself with an application-defined limit.
- Stream reads use managed async I/O before native admission. Requests reach the native queue when input is ready; concurrent requests need not retain submission order. The native worker itself still executes one conversion at a time.
- Configuration and callback delegates are snapshotted at engine construction, or at DI registration. Changing the original configuration does not reconfigure a running engine. Captured state inside application delegates is still yours.
Output and callbacks
- Native output-pointer copying stays on the owning thread, before converter destruction. Only a detached managed buffer crosses the native boundary.
- The destination factory runs on managed thread-pool execution after native conversion and destruction. Writes and flushes are awaited outside the worker. The library never disposes the destination stream.
- Task completion includes actual input processing and output delivery. After success, cancellation, or failure, the library no longer uses the borrowed streams. A failed or canceled delivery can leave partial output.
- Native progress/phase/finished/warning/error callbacks run on the native thread. Keep them short. Do not reenter the engine, initialize/dispose it, or wait for another conversion. Reentry is rejected and callback exceptions are contained. The callback document is the request snapshot, not the caller's original object. Do not block waiting for engine disposal from a destination factory either.
FinishedActiondescribes native completion, not completion of output delivery. Await the conversion task for that guarantee.
Default limits (WkHtmlToXConfiguration.RequestOptions):
| Option | Default | Scope |
|---|---|---|
MaxConcurrentRequests |
32 | All admitted requests, including input and slow output |
MaxInputBytes |
32 MiB | Sum of inline HTML bytes across one PDF request |
MaxBufferedInputBytes |
128 MiB | Inline input reservations across the engine |
MaxOutputBytes |
64 MiB | One detached managed output |
MaxBufferedOutputBytes |
128 MiB | Detached output across the engine |
Admission rejects immediately with Overloaded; there is no extra unbounded
backlog of requests waiting for a slot. Per-request input limit violations return
InvalidInput; aggregate input or output budget violations return ResourceLimit.
These are non-recycling failures. All limits must be positive; each shared budget
must cover its corresponding per-request maximum.
The engine reserves the complete PDF input before cloning byte arrays or
allocating stream buffers. It counts encoded HtmlContent bytes, byte-array
lengths, and remaining stream lengths. Page/Xsl and image paths reserve no inline
payload. Reservations remain held through output delivery and are released on
success, failure, or cancellation. Input streams that grow beyond the reservation
fail during preparation rather than exceeding the budget.
No temporary-file spooling is used, so spool usage is zero. These limits do
not measure total managed heap usage or cap memory already allocated inside
wkhtmltox, caller-held objects, UTF-16/settings strings, temporary marshalling
copies/pools, remote resources, images, files, or network traffic. Native output is
size-checked after rendering, before managed allocation. Budget native memory
separately and use supervised processes for hostile or memory-intensive input.
The optional advanced Interop QueueCapacity only bounds the native queue; the
wrapper's request limit also covers input and delivery.
Worker configuration
Use WkHtmlToXConfiguration.WorkerOptions for standalone, DI, and hosted engines.
It uses WkHtmlToX-owned types, without requiring an Interop namespace:
| Option | Default | Contract |
|---|---|---|
Name |
WkHtmlToX Engine Worker |
Diagnostics name, never document data or credentials |
MaxOperationsPerSession |
0 | Nonnegative periodic recycling interval; 0 disables it |
DisposeTimeout |
Infinite | Whole-pipeline synchronous disposal wait, from zero through int.MaxValue milliseconds, or Timeout.InfiniteTimeSpan |
ShutdownMode |
WkHtmlToXShutdownMode.Drain |
Default pipeline policy; CancelPending skips requests before native start |
These values are validated and copied before acquiring native ownership or registering services. Later mutations do not reconfigure the worker. The native worker uses STA on Windows; this does not supply a Qt message pump.
The existing DI/hosting configureWorker delegate remains available for advanced
Interop settings such as diagnostics or native queue capacity. It runs after
the wrapper settings and may override them. Prefer RequestOptions for admission
limits because those cover the whole pipeline and return structured overload
results. The public constructor and existing shutdown overloads remain compatible.
Cancellation, shutdown, and health
- A pre-canceled request never starts native work. Managed reads/writes and queued execution observe the request token. An active synchronous native conversion cannot be interrupted; its task remains pending until the native call exits and borrowed-resource use ends.
- Canceling
InitializeAsynccancels that caller's wait, not shared startup. ShutdownAsynccloses admission immediately.Drainfinishes all admitted input preparation, native conversions, and output delivery, then tears down the worker. A successfully completed host stop has the same guarantee.CancelPendingskips requests that have not crossed the native-start gate. Active stream reads finish before their requests become canceled. Queued tasks may wait for the active native delegate to exit before becoming canceled. Already-started conversions and their output delivery finish normally unless their own request token is canceled.- Configure the default policy with
WorkerOptions.ShutdownMode, or useShutdownAsync(ExecutionShutdownMode.CancelPending, cancellationToken)explicitly. This explicit overload uses the Interop enum and requiresusing AdaskoTheBeAsT.Interop.Execution;. The first shutdown/disposal call selects the policy; later calls only join it. - Shutdown and host-stop tokens limit waiting, not cleanup ownership. Even an already-canceled token starts shutdown. After a canceled/timed-out wait, retain conversion tasks and borrowed streams, then join shutdown again.
DisposeAsyncjoins actual pipeline completion. Synchronous disposal usesWorkerOptions.DisposeTimeout(infinite by default) for the whole pipeline and can return early. Later container/worker disposal cannot bypass that pipeline. Do not unload native code or block on shutdown from a stream method/factory belonging to an active request.- Inspect
IsFaultedandFaultfor terminal worker/session failure.QueueDepthis native queue depth, not total managed requests. A normal failed conversion does not necessarily fault the engine.
Prefer one engine for the entire process lifetime, not one per conversion.
On Windows x64/wkhtmltox 0.12.6, replacing engines on new threads reproduces
Qt QObject::startTimer and QApplication warnings in both the previous and
current implementations, even though every worker is STA. STA configures COM,
not Qt's thread/event dispatcher. Controlled probes using one engine, including
session recycling on that same thread, did not emit the warnings.
Successful output does not establish that skipped timers are harmless.
See the Qt comparison in astra_plan.md.
ConversionResult includes FailureKind, HttpErrorCode, bounded Warnings,
Exception, and CallbackException. Only NativeRuntimeError triggers
failure-based session recycling. A rejected setting, native conversion returning
false, or application stream/callback exception does not automatically recycle.
Warnings and exceptions can contain application data: redact before logging.
Hard deadlines, safe parallel rendering, crash recovery, and untrusted content require separately supervised renderer processes with filesystem/network/privilege restrictions. They are not implemented by this in-process package.
Native deployment and supported targets
Managed targets: net472, net48, net481, net8.0, net9.0, net10.0.
There is no netstandard2.0 or net462 asset in this release.
| Native environment | Status |
|---|---|
| Windows x64 | Real-native unit/integration and packaged-consumer validation target |
| Windows x86 | Loader retained; not validated in the current release checks |
| Linux x64/x86 | Loader retained; distribution/system dependencies require deployment validation |
| macOS x64 | Loader retained; not validated in the current release checks |
| ARM/ARM64 process | Rejected; bitness alone does not identify a compatible binary |
| .NET Framework outside Windows | Not supported |
Use the parameterless configuration for OS detection. Linux needs either a
legacy WkHtmlToXRuntimeIdentifier matching its native package or an explicit
NativeLibraryPath. The legacy distribution names are package layout identifiers,
not a promise of support for obsolete distributions.
NativeLibraryPath must be an absolute path to a trusted binary compatible
with the process architecture. Without it, loaders look under
AppContext.BaseDirectory/runtimes/<rid>/native/ and then the application base
directory. They do not search the current working directory or /usr/lib.
Missing explicit paths do not fall back silently.
On .NET 8+, the assembly's DllImport resolver routes all wkhtmltox imports to the
selected handle. On .NET Framework, Windows preloading requires wkhtmltox.dll
and rejects a module already loaded outside this wrapper. The selected library
remains loaded for the process lifetime; changing the path requires a restart.
Session initialization/termination and recycling remain thread-owned.
For single-file deployment, keep the native binary and its dependencies beside
the application or supply NativeLibraryPath; automatic extraction paths are
not inferred. Trimming and Native AOT are not supported (settings use reflection).
Do not load multiple copies of this wrapper or wkhtmltox through different
assembly load contexts/AppDomains to circumvent ownership checks.
Migration to 13.0.0
This is a major release. Upgrade the core, DI, and Hosting packages together, then follow these steps:
- Use supported TFMs and dependencies. Retarget older applications to at
least .NET Framework 4.7.2 or .NET 8. Interop.Execution dependencies are
constrained to
[2.1.0,3.0.0)and Interop.Unmanaged to[3.0.0,4.0.0). - Keep one engine per process. Reuse the DI singleton across PDF and image facades. A second engine now throws, including while the first is recycling or still shutting down. Failed native teardown requires a process restart.
- Move to
IWkHtmlToXAsyncEngine. UseInitializeAsync,ConvertPdfAsync,ConvertImageAsync, andDisposeAsync, as in the compiled examples above. The publicnew WkHtmlToXEngine(configuration)constructor still exists. Native sessions, loaders, and worker injection constructors are internal. - Stop constructing work items.
AddConvertWorkItem, public work-item classes, visitor interfaces, and caller-writable completion sources are obsolete migration shims, planned for removal in the next major release. They still work with deprecation warnings. The visitor API is not a supported plugin mechanism.PdfConverter/ImageConverterand theirTask<bool>APIs remain available and use the direct async engine when possible. - Handle structured results.
falsefrom native conversion becomesConversionError, with HTTP status and warnings. UseOverloadedandResourceLimitfor explicit overload handling; do not blindly retry forever. Legacy bool converters return false for these outcomes and rethrow retained exceptions where applicable. Cancellation still produces a canceled task. - Choose one input form and use built-in settings. Remove ambiguous inputs,
null PDF objects, stdin, native
Out, and custom settings subclasses. Invalid combinations are rejected before native startup. Check return-code failures for setting names that your native build does not support. - Respect stream lifetimes and execution changes. Settings and buffers
become snapshots, but streams remain borrowed until task completion. Factories
now run off the native thread;
FinishedActionprecedes output delivery. Replace reference-equality comparisons against callback documents. Do not dispose streams merely because a separate timeout stopped waiting. - Set explicit capacity budgets. Previously unbounded workloads can now
receive overload/size failures. Tune
RequestOptionsbefore construction or registration, including the newMaxBufferedInputBytesaggregate budget. Slow output retains input reservations as well as admission/output capacity, not the native worker. No temporary files are created. - Remove
RecycleSessionOnFailurefrom worker-option examples. It is not anExecutionWorkerOptionsproperty. This wrapper classifies recovery internally;WorkerOptions.MaxOperationsPerSessionis the periodic-recycling option. ConfigureWorkerOptionsfor the same policy in standalone and hosted engines; existing advanced Interop configuration delegates still work and take precedence. - Review native deployment. Configure a trusted absolute path when needed,
deploy the correct architecture, and stop relying on ambient OS search paths,
Assembly.Location, or unloading/replacing the native DLL during runtime. - Update lifecycle expectations. Cleanup waits and fault reporting use the
corrected Interop 2.x contracts. Host stop now coordinates input and delivery
before native teardown. Choose drain or cancel-pending deliberately; host
deadlines bound waiting, not running work. Resolve the async engine interface
for
InitializeAsyncandShutdownAsync. Prefer asynchronous provider disposal.
For older v10.x applications, DI and Hosting are now separate packages; remove
duplicate hand-written converter registrations. Converters are transient, not
singleton registrations. ProgressChangedEventArgs includes numeric Progress
and its constructor takes (document, progress, description).
Development and release checks
- The repository enables analyzers and warnings-as-errors.
packages.lock.jsonrecords exact restore graphs; CI uses locked restore. Update lock files deliberately with dependency changes, then verify withdotnet restore AdaskoTheBeAsT.WkHtmlToX.slnx --locked-mode.- The SDK and test runner are selected by
global.json. Run a targeted suite withdotnet run --project <test.csproj> --framework net10.0 -- --progress off. This invokes the xUnit v3 Microsoft.Testing.Platform runner directly. After dependency upgrades, a .NET FrameworkFileLoadExceptioncan indicate stale generated binding redirects. Rebuild the affected test target withdotnet build <test.csproj> --framework net481 --no-restore -t:Rebuild(substitute the affected framework), then rerun it. scripts/verify-packages.ps1packs all three libraries, extracts every C# code block in this README, compiles them against the local packages in a fresh consumer, and runs the examples on Windows x64. It also checks a single-file .NET 10 consumer. It never publishes packages. If local executable scanning prevents that check,-SkipSingleFileexplicitly skips it while retaining all six normal and explicit-native-path consumer checks. The skip is reported and does not establish single-file runtime support.- Architecture decisions record implementation status. astra_plan.md records validation results and remaining limits.
License and acknowledgments
See LICENSE. Built on DinkToPdf and wkhtmltopdf.
| 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 is compatible. 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. |
| .NET Framework | net472 is compatible. net48 is compatible. net481 is compatible. |
-
.NETFramework 4.7.2
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
-
.NETFramework 4.8
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
-
.NETFramework 4.8.1
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
-
net10.0
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
-
net8.0
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
-
net9.0
- AdaskoTheBeAsT.Interop.Execution (>= 2.1.0 && < 3.0.0)
- AdaskoTheBeAsT.Interop.Unmanaged (>= 3.0.0 && < 4.0.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on AdaskoTheBeAsT.WkHtmlToX:
| Package | Downloads |
|---|---|
|
AdaskoTheBeAsT.WkHtmlToX.Hosting
Microsoft.Extensions.Hosting integration for AdaskoTheBeAsT.WkHtmlToX: AddWkHtmlToXHostedService() registers an IHostedService that drives the WkHtmlToX engine worker lifecycle through the generic host. |
|
|
AdaskoTheBeAsT.WkHtmlToX.DependencyInjection
Microsoft.Extensions.DependencyInjection helpers for AdaskoTheBeAsT.WkHtmlToX: AddWkHtmlToX() registers the engine, converters and the underlying execution worker as singletons without hooking into IHostedService. |
|
|
AdaskoTheBeAsT.WkHtmlToX.NativeMemoryProbe
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 13.0.0 | 5 | 9/9/2026 |
| 12.0.0 | 162 | 8/2/2026 |
| 11.1.0 | 198 | 4/25/2026 |
| 11.0.0 | 157 | 4/25/2026 |
| 10.0.0 | 132 | 4/4/2026 |
| 9.0.0 | 314 | 11/16/2025 |
| 8.0.0 | 1,063 | 1/5/2025 |
| 7.1.0 | 5,206 | 12/27/2023 |
| 7.0.0 | 268 | 12/5/2023 |
| 6.0.0 | 640 | 11/13/2022 |
| 5.0.0 | 708 | 2/23/2021 |
| 4.0.3 | 647 | 1/16/2021 |
| 4.0.1 | 671 | 12/3/2020 |
| 4.0.0 | 703 | 7/8/2020 |
| 3.0.3 | 755 | 4/22/2020 |
| 3.0.2 | 736 | 4/19/2020 |
| 3.0.1 | 697 | 4/19/2020 |
| 3.0.0 | 770 | 3/29/2020 |
| 2.0.0 | 777 | 3/28/2020 |
| 1.1.0 | 855 | 12/15/2019 |