Releases: xshaheen/headless-framework
0.10.0
This release consolidates everything since 0.5.3 — a large span covering the messaging consumer model, the distributed-locks and coordination substrates, the caching resilience program, the new commit-coordination primitive, and a broad hot-path performance pass. The headline change in 0.10.0 itself is the buffer-first ISerializer redesign.
⚠️ Pre-1.0 software: breaking changes land in minor versions. Review the Breaking Changes section before upgrading.
⚠️ Breaking Changes
Serializer is now buffer-first (#534)
ISerializer no longer takes or returns Stream. The core contract is buffer-based:
// before (Stream-first)
void Serialize<T>(T value, Stream output);
T? Deserialize<T>(Stream data);
// after (buffer-first)
void Serialize<T>(T value, IBufferWriter<byte> output);
T? Deserialize<T>(ReadOnlyMemory<byte> data);
T? Deserialize<T>(in ReadOnlySequence<byte> data);Migration: most call sites are unaffected — byte[], string, and Stream helpers (SerializeToBytes, SerializeToString, Deserialize<T>(byte[]), Deserialize<T>(string?), Serialize<T>(T, Stream), Deserialize<T>(Stream)) live on as SerializerExtensions (C# 14 extension members). Only custom ISerializer implementations must be rewritten to the buffer overloads. Bonus: SerializeToBytes no longer pays a MemoryStream + ToArray() copy, and byte-array reads happen in place.
Emails: unified provider builder + named senders (#504, #516)
The four bespoke registration extensions are removed in favor of the framework's unified setup-builder grammar (matching Caching / Coordination / Settings / …), and a new Azure Communication Services provider is added:
// before — one bespoke extension per provider
services.AddMailKitEmailSender(config);
services.AddAwsSesEmailSender(awsOptions);
services.AddDevEmailSender("emails.txt");
// after — one entry, exactly one default provider per call
services.AddHeadlessEmails(setup => setup.UseMailkit(config));
services.AddHeadlessEmails(setup => setup.UseAwsSes(awsOptions));
services.AddHeadlessEmails(setup => setup.UseDevelopment("emails.txt"));
services.AddHeadlessEmails(setup => setup.UseAzure(o => o.ConnectionString = "…")); // new ACS provider| Removed | Replacement |
|---|---|
AddMailKitEmailSender |
setup.UseMailkit |
AddAwsSesEmailSender |
setup.UseAwsSes |
AddDevEmailSender |
setup.UseDevelopment |
AddNoopEmailSender |
setup.UseNoop |
Zero, multiple, or repeated AddHeadlessEmails on the same IServiceCollection now throws InvalidOperationException at registration (a host resolves exactly one default IEmailSender). Named senders are supported alongside the required default and resolved via the new IEmailSenderProvider:
services.AddHeadlessEmails(setup =>
{
setup.UseAwsSes(awsOptions); // default (required)
setup.AddNamed("marketing", i => i.UseMailkit(smtpSection)); // named, keyed
});
// resolve: IEmailSenderProvider.GetSender("marketing") or [FromKeyedServices("marketing")] IEmailSenderThe IEmailSender send contract is unchanged. Custom-provider authors: IEmailProviderOptionsExtension is dropped (use the captured-Action<IServiceCollection> contribution model); MailkitSmtpOptions properties changed init → set.
Captcha: provider abstraction + package rename (#503)
The single hard-wired Headless.ReCaptcha package is retired and split into an abstraction + providers:
// before (package: Headless.ReCaptcha)
services.AddReCaptchaV3(o => { … });
// inject IReCaptchaSiteVerifyV3
// after (packages: Headless.Captcha.Abstractions + Headless.Captcha.ReCaptcha [+ .Turnstile])
services.AddHeadlessCaptcha(b => b.UseReCaptchaV3(o => { … }));
// inject IReCaptchaV3Verifier (score) or ICaptchaVerifier (pass/fail)- Package id
Headless.ReCaptcha→Headless.Captcha.ReCaptcha; newHeadless.Captcha.Turnstile(Cloudflare). - Removed (no shim):
AddReCaptchaV2/AddReCaptchaV3,IReCaptchaSiteVerifyV2/IReCaptchaSiteVerifyV3. - Call sites should depend on
ICaptchaVerifier(pass/fail); the reCAPTCHA v3 numeric score stays onIReCaptchaV3Verifier. At most one default provider plus any number of named providers resolved viaICaptchaProvider.GetVerifier(name).
Domain: non-boxing equality hooks on EqualityBase (#517)
The boxing EqualityComponents() contract is replaced by two typed hooks — removing per-probe boxing and enumerator allocations on every dictionary / HashSet / EF change-tracker key compare:
// before
protected abstract IEnumerable<object?> EqualityComponents();
// after
protected abstract bool EqualityComponentsEqual(T other); // typed compare, early-exit, no box
protected abstract void BuildHashCode(ref HashCode hash); // typed hash, no box- Value objects now derive from
ValueObject<TSelf>(CRTP — no cast in overrides):sealed class Money : ValueObject<Money>. The non-genericValueObjectbase is replaced by theIValueObjectmarker for heterogeneous references. - Entities that overrode
EqualityComponents()move to the two hooks.Entity<TId>/GetKeys()and EF key mapping are unchanged (the composite-keyEntity<TId>de-box is a tracked follow-up).
✨ Features
Caching
- Resilience program: timeouts, sliding expiration, eager + conditional refresh, tagging, distributed stampede protection, named caches, auto-recovery (#437)
- Distributed, tag-aware
IOutputCacheStoreadapter (#451) - BCL
IDistributedCacheadapter (#446) - Serve stale values on factory failure (#408)
- Redis cache entry envelope (#398) and cache entry options envelope (#389)
Messaging
- Split bus and queue delivery intents (#340)
ForMessageregistration API (#362) and configure scanned consumers (#387)- Universal provider knobs — Cluster 0.4 (#410)
- Monitored retry lock leases (#430); recover retry rows from dead coordination owners (#421); dead-only reclaim + shared coordination recovery bridge (#449)
Distributed Locks
- Fencing tokens + Redis semaphores (#368) and Redis reader/writer locks (#342)
- Providers: PostgreSQL advisory (#391), in-memory + Postgres with shared conformance harness (#393), SQL Server (#406)
- Distinguish safety-deadline stall from contention in non-blocking acquire (#444)
Coordination & Commit Coordination
- Provider-agnostic commit coordinator, replacing the ambient-transaction approach (#428)
- Membership substrate (#416); migrate Jobs node membership & dead-node recovery onto
Headless.Coordination(#422)
Jobs
- Atomic job enqueue via commit coordination (#455)
OnNodeDeathconsumer input API + per-job policy and lease/status vocabulary alignment (#454, #456)- Optional
IDistributedLockguard for cron-seed migration (#468)
Blobs & Storage
- Multiple named blob storages in one DI container (#495)
- Cloudflare R2 backend + presigned URLs (#476)
- Unified storage provider setup (#354)
⚡ Performance
- Buffer-first serializer removes intermediate
MemoryStream/ToArray()copies on serialize and reads byte arrays in place (#534) - Replace dynamic SQL IN-lists with TVPs + messaging hot-path allocations (#352 via #448)
- Coordination: targeted single-node liveness via
ReadNodeLivenessAsyncSPI (#447); co-locate incarnation to drop O(N) Redis GETs (#425) - Messaging: drop per-publish payload copy (ASB) and LINQ attribute dict (SQS/SNS) (#529)
serializer-json: cache derived options inCollectionItemJsonConverter(#531)- Assorted hot-path allocation fixes across packages (#513, #518, #530, #533)
🐛 Fixes
- Caching: review-driven reliability, perf & correctness hardening (#512)
- Jobs: honest renewal-loop signals — membership skip + fenced-success flag (#470)
- Messaging: reject more than one storage provider at bootstrap (#450); publish callback response values (#395); hourly monitoring bugs (#423); integration-test reliability (#363)
- Coordination: retry SQL Server membership deadlocks (#435)
- Postgres: harden advisory savepoint detection (#432)
- Blobs/TUS: Azure blob & TUS review fixes (#496)
- Dashboards: harden SPA build automation (#494); vue-tsc 3 import fix (#497)
♻️ Refactoring
- ORM/EF: internalize context runtime (#433); two-tier event dispatch — domain-event bus + integration-event outbox bridge (#407)
- Messaging: broker-neutral message names (#361); order-independent consumer registration (#420);
MessageOptionsrename (#394) - Core: backend-keyed storage GUIDs (#414)
- Distributed locks: builder-only setup API (#415)
- Review hardening pass across Blobs, NATS, PostgreSQL messaging storage, Emails (#506)
🧪 Tests & 📚 Docs
- Distributed-locks provider test plan + fencing-token / race-condition regression coverage (#413, #412, #452, #409)
- Messaging consume-dispatch benchmark harness (#514)
- Conform
docs/llms+ package READMEs to the authoring shape; add provider tables + end-to-end examples (#532)
🔧 Build & CI
- Route hooks and CI through Make (#457); pre-push build/format guardrails + CI format gate (#434)
- Automate dashboard SPA build + npm dependency updates (#479)
- Prune prerelease packages workflow; CodeQL on PRs only
⬆️ Dependencies & Security
- Patch shell-quote (GHSA-w7jw-789q-3m8p) and MessagePack (GHSA-hv8m-jj95-wg3x) (#443)
- Patch 11 Dependabot npm alerts in dashboards (#477); axios + brace-expansion advisories (#401)
- Update packages within-major + tools; fix NU1903 SQLitePCLRaw (#475)
- Routine dependency bumps: dotnet-sdk, @microsoft/signalr, vue-tsc, npm-minor-patch group, GitHub Actions (#453, #486, #488, #491, #485, #482, #483, #400)
Contributors: @xshaheen, with dependency updates from @dependabot.
Full Changelog: 0.5.3...0.10.0
0.5.3
What's Changed
- fix(caching-memory): align InMemoryCache set key-type predicate by @xshaheen in #325
- docs(orm): document HeadlessDbContext is intentionally not poolable by @xshaheen in #326
- feat(distributed-locks): improve lock acquisition ergonomics by @xshaheen in #322
- feat(management): configure EF storage schema by @xshaheen in #327
- feat(distributed-locks): add lease monitoring by @xshaheen in #330
Full Changelog: 0.5.2...0.5.3
0.4.18
- feat(idempotency): rewrite as Stripe-style replay in new Headless.Api.Idempotency package by @xshaheen (#286)
- feat(messaging): replace filters with typed middleware pipeline (+ distributed-locks docs) by @xshaheen (#285)
- refactor(api): move tenant enforcement to authorization by @xshaheen (#281)
- docs(messaging): refresh Phase 1 LLM doc + provider capability matrix by @xshaheen (#277)
- feat(messaging-otel): add IActivityTagEnricher tag-enrichment pipeline by @xshaheen (#275)
🧰 Maintenance
- build(deps): bump dotnet-sdk from 10.0.203 to 10.0.300 by @dependabot[bot] (#301)
Contributors
@dependabot[bot], @xshaheen and dependabot[bot]
0.4.17
- No changes
Contributors
No contributors
0.4.16
Full Changelog: 0.4.15...0.4.16
0.4.15
- No changes
Contributors
No contributors
0.4.14
0.4.13
What's Changed
- ci(deps): bump release-drafter/release-drafter from 7.2.1 to 7.3.0 in the actions-minor-patch group by @dependabot[bot] in #247
- build(deps): bump dotnet-sdk from 10.0.100 to 10.0.203 by @dependabot[bot] in #246
- refactor(orm): simplify headless save pipeline by @xshaheen in #248
Full Changelog: 0.4.12...0.4.13
0.0.90
0.0.7
🧰 Maintenance
- Build(deps): Bump actions/setup-dotnet from 4.2.0 to 4.3.0 by @dependabot[bot] (#95)
👨🏼💻 Contributors
@dependabot[bot], @xshaheen and dependabot[bot]