Skip to content

Releases: xshaheen/headless-framework

0.11.0

Choose a tag to compare

@github-actions github-actions released this 08 Jul 08:30
2594ecf

This release carries the post-0.10.0 public API cleanup program, the blob and SMS contract redesigns, named-client registration across more provider families, TUS Azure conformance work, and the package-quality/readiness fixes needed before publishing.

⚠️ Pre-1.0 software: breaking changes land in minor versions. Review the Breaking Changes section before upgrading.

⚠️ Breaking Changes

Public API compatibility pass across the framework (#623)

0.11.0 applies the repo-wide public API compatibility review while the framework is still pre-v1.0. The biggest migration theme is namespace and package ownership:

  • Headless.Api.Abstractions now owns its namesake namespace; IProblemDetailsCreator and the new IAbsoluteUrlFactory live there.
  • Feature packages now keep their registration surface in their own package/family namespace instead of foreign framework namespaces. Update using statements to the package namespace, for example Headless.Api, Headless.Api.Idempotency, Headless.Sms, Headless.Tus, and provider family roots.
  • BCL-colliding holder names were renamed to Headless* or package-specific names, including HTTP helpers and antiforgery registration holders.
  • Headless.Messaging.Internal is gone; cross-package SPI moved to Headless.Messaging.Runtime and the rest was internalized.
  • Headless.Urls, Headless.Primitives, and Headless.Permissions.Testing are now separate packages. Headless.Extensions still references the extracted primitives/URL surface transitively.

Several contract-shape fixes also landed in this program: Jobs source-generator tuple ABI moved to named structs/contexts, IConnectionStringChecker returns ConnectionCheckResult, several collection-returning APIs moved to read-only shapes, behavior-selecting enums now have explicit values, and Paymob money/ID fields use safer numeric types.

Migration: expect compile-time fixes: add the correct package/family using, replace renamed holder/types, and consume the new named DTO/record return types where tuple or loose helper contracts were removed.

Blob storage operations use BlobLocation, token paging, and explicit capabilities (#550)

The IBlobStorage operational contract was redesigned to make provider behavior consistent across AWS, Azure, Cloudflare R2, FileSystem, Redis, and SFTP:

  • string[] container plus blobName is replaced by BlobLocation(Container, Path).
  • Listing is token-based: ListAsync(BlobQuery) -> BlobPage(Items, ContinuationToken?); streaming is available through GetBlobsAsync.
  • RenameAsync is now MoveAsync; DeleteAllAsync(container, glob) is now DeleteAllAsync(BlobQuery) with prefix-based provider deletes and client-side glob support.
  • Container management moved out of IBlobStorage to IBlobContainerManager, resolved as an optional DI capability. Uploads no longer auto-create missing containers.
  • Bulk operations return IReadOnlyList<BlobBulkResult>, metadata values are non-null strings, and IPresignedUrlBlobStorage takes BlobLocation.

Migration: construct BlobLocation/BlobQuery at call sites, resolve IBlobContainerManager only for providers that support bucket/container management, and update custom blob providers to the new operation signatures.

SMS is single-recipient by default, with bulk and named clients as explicit capabilities (#549, #587, #589)

SMS was split into a clean single-recipient core plus optional bulk and named-client capabilities:

  • ISmsSender.SendAsync now sends to exactly one SmsRequestDestination; SendSingleSmsRequest.Destinations and IsBatch are gone.
  • Bulk-capable providers implement IBulkSmsSender.SendBulkAsync(SendBulkSmsRequest) with per-recipient SmsRecipientResult values. AWS SNS and Twilio intentionally do not register IBulkSmsSender.
  • Registration moved to the new Headless.Sms.Core package. Headless.Sms.Abstractions is now contracts-only.
  • ISmsProviderOptionsExtension was removed. Providers register through AddHeadlessSms(setup => setup.Use*(...)) and named instances use setup.AddNamed("name", i => i.Use*(...)).
  • UseDev was renamed to UseDevelopment for Emails parity.

Migration: use one Destination for single sends, inject IBulkSmsSender only where bulk is required, add Headless.Sms.Core, and move provider setup calls to the new default/named builder form.

Named-DI registration is unified across provider families (#589, #593)

The named-client model now covers Emails, SMS, Caching, Blobs, Captcha, and Push Notifications with consistent provider factories and RegisteredNames where applicable.

  • Emails and SMS now allow named-only hosts: zero or one default provider plus any number of named providers. The unkeyed sender is not registered when no default is configured.
  • IEmailSenderProvider, ISmsSenderProvider, and ICacheProvider expose RegisteredNames for validation and discovery.
  • Captcha registration moved to the new Headless.Captcha.Core package; Abstractions is contracts-only.
  • Push Notifications moved setup/factory machinery into Headless.PushNotifications.Core, added IPushNotificationServiceProvider, and supports default plus named Firebase/Noop services.

Migration: add the relevant .Core package when using registration APIs, move named/default registration to the setup builder, and validate externally supplied names with RegisteredNames rather than probing by exception.

API primitives and request contracts were renamed or tightened (#596, #597, #623)

  • CollectionEnvelop, DataEnvelop, IdEnvelop, MessageEnvelop, Operations*Envelop, and ValueEnvelop are now *Envelope.
  • IProblemDetailsCreator.UnprocessableEntity now accepts IReadOnlyDictionary<string, IReadOnlyList<ErrorDescriptor>>.
  • JWT creation now uses JwtTokenRequest instead of long parameter lists.
  • HttpCurrentUser no longer reports principal-derived values when unauthenticated.
  • API idempotency and service-default types moved into their package-owned namespaces.

Migration: rename envelope types, update JWT call sites to construct JwtTokenRequest, and add package namespaces for API idempotency/service-default helpers.

✨ Features

Provider registration and package surfaces

  • New Headless.PushNotifications.Core with default and named Firebase/Noop registration plus IPushNotificationServiceProvider (#593)
  • New Headless.Captcha.Core, Headless.Sms.Core, Headless.Urls, Headless.Primitives, Headless.Permissions.Testing, and Headless.Sql.Core package surfaces (#589, #623)
  • RegisteredNames support on sender/cache providers for external name validation (#589)

Blobs

  • BlobLocation, BlobQuery, BlobPage, BlobBulkResult, and IBlobContainerManager as first-class contracts (#550)
  • Universal metadata support for FileSystem and SFTP via hidden sidecar files, plus provider-conformance coverage for metadata and token paging (#550)

SMS

  • IBulkSmsSender, SendBulkSmsRequest, SendBulkSmsResponse, and SmsRecipientResult for providers with true or aggregate bulk sends (#549)
  • Named SMS clients across AWS SNS, Twilio, Cequens, Connekio, Infobip, VictoryLink, Vodafone, Dev, and Noop (#587)
  • Shared SMS failure classification for transport, resilience, and provider-specific error contracts (#549)

TUS and uploads

  • AddTusAzureStore DI registration with the standard configuration/action overload trio (#586)
  • DeletePartialFilesOnConcat, TUS CORS defaults, expired-upload cleanup service, and a runnable demo/Headless.Tus.Demo with a Vite/React client (#586)

API and validation

  • IAbsoluteUrlFactory, JwtTokenRequest, DataProtection key-ring startup validation, and DataProtection health checks (#623)
  • FluentValidation package coverage for API contracts and a broader Headless.FluentValidation validator suite (#564, #623)

Maintenance tooling

  • Bounded NuGet advisory/deprecation audit tooling: scripts/audit-nuget-advisories.sh, make nuget-advisory-audit, and make dependency-security-audit PROJECT=<path> (#636)

⚡ Performance

  • Delayed messaging waits until the next due message instead of polling every 50ms (#635)
  • Monthly count helpers try database-side grouping/counting before falling back to client aggregation (#635)
  • Kafka publishing reuses whole-array message bodies where possible (#635)
  • PostgreSQL advisory-lock key hashing avoids transient byte arrays and caps the process-wide hash cache (#635)
  • Messaging/cache/API/extensions hot paths were reduced across the repository review and follow-up passes (#599, #609, #617)

🐛 Fixes

Caching

  • Hybrid and Redis caching reliability fixes: eager-refresh fail-closed behavior, recovery-drain signaling, bounded cold-read fan-out, expiration validation, Redis marker and numeric semantics, NUL-byte key rejection, and auto-recovery warnings (#548, #540)
  • Hybrid invalidation consumer registration is now order-independent when a backplane bus is present (#511, #599)

Messaging and Jobs

  • Redis Streams reject handling now requeues before acknowledging, stale pending entries recover through XAUTOCLAIM, and startup pause failures clean up failed clients (#638)
  • Jobs execution is fenced to rows still owned by the current node; active async executions count against scheduler concurrency and drain behavior (#637)
  • Messaging consumer durability, retry behavior, NATS/ASB/AWS provider tests, and dashboard testing gaps were hardened (#610, #614, #616, #621)

TUS Azure

  • Deferred-length uploads, completed-upload expiration, byte-for-byte Upload-Metadata echo, non-ASCII metadata, checksum-trailer rollback, duplicate partial concatenation, and ValidateId enforcement now align with tus 1.0.0/reference-store behavior (#586)
  • BlobMaxChunkSize default is lower to avoid 100 MB per concurrent large upload buffering (#586)

Payments, API, ORM, and core utilities

  • Paymob CashOut/CashIn now deserialize unknown...
Read more

0.10.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 08:25
b95013c

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")] IEmailSender

The IEmailSender send contract is unchanged. Custom-provider authors: IEmailProviderOptionsExtension is dropped (use the captured-Action<IServiceCollection> contribution model); MailkitSmtpOptions properties changed initset.

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.ReCaptchaHeadless.Captcha.ReCaptcha; new Headless.Captcha.Turnstile (Cloudflare).
  • Removed (no shim): AddReCaptchaV2 / AddReCaptchaV3, IReCaptchaSiteVerifyV2 / IReCaptchaSiteVerifyV3.
  • Call sites should depend on ICaptchaVerifier (pass/fail); the reCAPTCHA v3 numeric score stays on IReCaptchaV3Verifier. At most one default provider plus any number of named providers resolved via ICaptchaProvider.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-generic ValueObject base is replaced by the IValueObject marker for heterogeneous references.
  • Entities that overrode EqualityComponents() move to the two hooks. Entity<TId> / GetKeys() and EF key mapping are unchanged (the composite-key Entity<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 IOutputCacheStore adapter (#451)
  • BCL IDistributedCache adapter (#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)
  • ForMessage registration 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)
  • OnNodeDeath consumer input API + per-job policy and lease/status vocabulary alignment (#454, #456)
  • Optional IDistributedLock guard 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 ReadNodeLivenessAsync SPI (#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 in CollectionItemJsonConverter (#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); MessageOptions rename (#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

Contributors: @xshaheen, with dependency updates from @dependabot.

Full Changelog: 0.5.3...0.10.0

0.5.3

Choose a tag to compare

@xshaheen xshaheen released this 23 May 23:32
a007173

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

Choose a tag to compare

@github-actions github-actions released this 20 May 00:45
10ab060
  • 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

Contributors

@dependabot[bot], @xshaheen and dependabot[bot]

0.4.17

Choose a tag to compare

@github-actions github-actions released this 18 May 00:40
  • No changes

Contributors

No contributors

0.4.16

Choose a tag to compare

@xshaheen xshaheen released this 18 May 00:27

Full Changelog: 0.4.15...0.4.16

0.4.15

Choose a tag to compare

@github-actions github-actions released this 18 May 00:09
  • No changes

Contributors

No contributors

0.4.14

Choose a tag to compare

@xshaheen xshaheen released this 17 May 23:46
  • feat(messaging): add retry policy contract by @xshaheen (#254)
  • refactor(api): split Headless.Api into Core + ServiceDefaults by @xshaheen (#261)
  • feat: add headless tenancy configuration by @xshaheen (#245)

Contributors

@xshaheen

0.4.13

Choose a tag to compare

@xshaheen xshaheen released this 12 May 15:53

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

Choose a tag to compare

@github-actions github-actions released this 06 Oct 11:30

🧰 Maintenance

👨🏼‍💻 Contributors

@xshaheen