Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,37 @@ A version's heading reads `## <version> — Unreleased` until the release is pre
the release date. The release workflow refuses a tag whose section still says `Unreleased`, and the
section is the GitHub release's notes.

## 2.3.0 — Unreleased
## 2.3.0 — 2026-10-29

### Added

- AspNetCore: `FaultResponse.WithoutFaultName()` answers a member without naming it: no `fault`, no `rule` and no member description in the problem, so two causes on one status look alike; the document says that status names no fault, and `IFaultFeature.NameWithheld` tells the host ([Faults a client must not tell apart](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#faults-a-client-must-not-tell-apart)).
- `Result.CatchAsync` takes a call that returns a `ValueTask`, `stream.ReadAsync(buffer)` without `.AsTask()`; an `async` lambda binds as before ([An exception as an outcome](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/result.md#an-exception-as-an-outcome)).
- `TraverseAsync` on `Option<T>` and `Patch<T>`: an asynchronous check of an optional input or a patch field, with a token form; nothing is called without a value ([Collect many results](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/result.md#collect-many-results)).
- `Choose()`, `Choose(selector)`, `SequenceAsync` and `TraverseAsync` read an `IAsyncEnumerable<T>`, in order and without a list first; the token reaches the stream, nothing is read after the first error, and the enumerator is disposed however they stop ([Chain steps](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit/README.md#chain-steps)).
- `Fault<TFault>.Cause`: `Fault.Of(reason, detail, exception)` keeps the exception underneath for your log; equality and `ToString()` leave it out, and `ToHttpResult()` hands it to the host as `IFaultFeature.Cause` and `FaultProblem<TFault>.Cause`, never into the problem ([What failed underneath](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.AspNetCore/README.md#what-failed-underneath)).
- Testing: every assertion works on a `Task` or `ValueTask` of what it checks, `await service.FindAsync(id).ShouldBeSome()`; a task that throws fails the test with its own exception, and an assertion left without `await` is reported (CMTK0003): through `ConfigureAwait` or `WaitAsync`, in a branch of a `?:`, `??` or `switch`, and handed to `Task.WhenAll`, `Task.Run` or `Task.WhenEach` and dropped, or to `Task.WhenAny` or `Task.WaitAny`, nested at any depth, or awaited in an `async` lambda whose `catch` throws again or can't catch it ([CodoMetis.TypeKit.Testing](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Testing/README.md)).
- OpenAPI: `AddTypeKit(typeKit => typeKit.InlineValueObjects = true)` describes every value object in place by its underlying type's schema, in every position and in OpenAPI 3.1, 3.0 and 2.0, with no component of its own; closed sets keep theirs, and without the setting the document is unchanged ([Describe value objects as plain types](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/aspnet-core.md#describe-value-objects-as-plain-types)).
- Analyzers: CMTK0009 warns at `FirstOrNone`, `SingleOrNone` or `FirstOrError` on a query given a `Func` variable or a method group, which binds to the `IEnumerable<T>` overload and reads every row; a lambda at the call or an `Expression` stays silent ([A condition that reads every row](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#a-condition-that-reads-every-row)).
- AspNetCore: `FaultResponse.WithoutFaultName()` answers a member without naming it: no `fault`, no `rule` and no member description in the problem, so two causes on one status look alike; the document says that status names no fault, and `IFaultFeature.NameWithheld` tells the host ([Faults a client must not tell apart](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#faults-a-client-must-not-tell-apart)).
- `Result.CatchAsync` takes a call that returns a `ValueTask`, `stream.ReadAsync(buffer)` without `.AsTask()`; an `async` lambda binds as before ([An exception as an outcome](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#an-exception-as-an-outcome)).
- `TraverseAsync` on `Option<T>` and `Patch<T>`: an asynchronous check of an optional input or a patch field, with a token form; nothing is called without a value ([Collect many results](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/result.md#collect-many-results)).
- `Choose()`, `Choose(selector)`, `SequenceAsync` and `TraverseAsync` read an `IAsyncEnumerable<T>`, in order and without a list first; the token reaches the stream, nothing is read after the first error, and the enumerator is disposed however they stop ([Chain steps](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit/README.md#chain-steps)).
- `Fault<TFault>.Cause`: `Fault.Of(reason, detail, exception)` keeps the exception underneath for your log; equality and `ToString()` leave it out, and `ToHttpResult()` hands it to the host as `IFaultFeature.Cause` and `FaultProblem<TFault>.Cause`, never into the problem ([What failed underneath](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.AspNetCore/README.md#what-failed-underneath)).
- Testing: every assertion works on a `Task` or `ValueTask` of what it checks, `await service.FindAsync(id).ShouldBeSome()`; a task that throws fails the test with its own exception, and an assertion left without `await` is reported (CMTK0003): through `ConfigureAwait` or `WaitAsync`, in a branch of a `?:`, `??` or `switch`, and handed to `Task.WhenAll`, `Task.Run` or `Task.WhenEach` and dropped, or to `Task.WhenAny` or `Task.WaitAny`, nested at any depth, or awaited in an `async` lambda whose `catch` throws again or can't catch it ([CodoMetis.TypeKit.Testing](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Testing/README.md)).
- OpenAPI: `AddTypeKit(typeKit => typeKit.InlineValueObjects = true)` describes every value object in place by its underlying type's schema, in every position and in OpenAPI 3.1, 3.0 and 2.0, with no component of its own; closed sets keep theirs, and without the setting the document is unchanged ([Describe value objects as plain types](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/aspnet-core.md#describe-value-objects-as-plain-types)).
- Analyzers: CMTK0009 warns at `FirstOrNone`, `SingleOrNone` or `FirstOrError` on a query given a `Func` variable or a method group, which binds to the `IEnumerable<T>` overload and reads every row; a lambda at the call or an `Expression` stays silent ([A condition that reads every row](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#a-condition-that-reads-every-row)).
- Docs: the guides, the package READMEs, the changelog and the API of every run-time package as one site with a menu and search, built from the latest release ([caffeinatedcoder.github.io/CodoMetis.TypeKit](https://caffeinatedcoder.github.io/CodoMetis.TypeKit/)).
- Analyzers: a validated value object without `Create` gets a code fix, **Add Create**, that writes a `Create` accepting every value, ready for your rules ([Add Create](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#add-create)).
- Docs: a walkthrough follows one orders service from request to response, saying what changes at each boundary; the service is a sample that CI builds from the packages and runs, and the answers the page states are checked against it ([Walkthrough](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/walkthrough.md)).
- Docs: the root README and the Generators README show, folded, the code the generators write for `OrderId`, `Email` and a closed set; a test holds each block to what the build emits ([Declare a value object](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#declare-a-value-object)).
- EF Core: queries, keys and round trips are tested on a real SQL Server, and the README says what differs there from memory: text compared ignoring case, Guids sorted by their last group, `decimal(18,2)` by default, a `TimeSpan` under a day, `Greatest` from compatibility level 160 ([On SQL Server](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#on-sql-server)).
- Analyzers: a validated value object without `Create` gets a code fix, **Add Create**, that writes a `Create` accepting every value, ready for your rules ([Add Create](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#add-create)).
- Docs: a walkthrough follows one orders service from request to response, saying what changes at each boundary; the service is a sample that CI builds from the packages and runs, and the answers the page states are checked against it ([Walkthrough](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/walkthrough.md)).
- Docs: the root README and the Generators README show, folded, the code the generators write for `OrderId`, `Email` and a closed set; a test holds each block to what the build emits ([Declare a value object](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#declare-a-value-object)).
- EF Core: queries, keys and round trips are tested on a real SQL Server, and the README says what differs there from memory: text compared ignoring case, Guids sorted by their last group, `decimal(18,2)` by default, a `TimeSpan` under a day, `Greatest` from compatibility level 160 ([On SQL Server](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#on-sql-server)).

### Changed

- Analyzers: CMTK0003 now reports four forms that code built on 2.2.0 can contain, so your build can fail with it when you upgrade. They are a `Task.WhenAny` of `Result` tasks you don't hold (dropped, awaited, waited for or returned as a plain `Task`), a dropped `Task.WhenEach` of them, `Wait()` on a `?:`, `??` or `switch` of them, and an array of them written in a `Task.WaitAll` or `Task.WaitAny` call. Each one loses results that nobody reads: handle them, or discard them with `_ =`. An awaited `Task.WhenAny` of tasks you hold is no longer reported ([What a new version may change](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Analyzers/README.md#configuration)).
- EF Core: a default value-object key over `ulong` gets a temporary value at `Add`, as a plain `ulong` key does, where it threw `OverflowException` on SQL Server and PostgreSQL; a `char` key configured `ValueGeneratedOnAdd()` gets a temporary `char` on SQL Server, not a character of a GUID string. HiLo and your own generator still come first. A compiled model made with `--nativeaot`, and a key you configure with `ValueObjectConverter` yourself, still throw ([Keys](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)).
- Analyzers: CMTK0003 now reports four forms that code built on 2.2.0 can contain, so your build can fail with it when you upgrade. They are a `Task.WhenAny` of `Result` tasks you don't hold (dropped, awaited, waited for or returned as a plain `Task`), a dropped `Task.WhenEach` of them, `Wait()` on a `?:`, `??` or `switch` of them, and an array of them written in a `Task.WaitAll` or `Task.WaitAny` call. Each one loses results that nobody reads: handle them, or discard them with `_ =`. An awaited `Task.WhenAny` of tasks you hold is no longer reported ([What a new version may change](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Analyzers/README.md#configuration)).
- EF Core: a default value-object key over `ulong` gets a temporary value at `Add`, as a plain `ulong` key does, where it threw `OverflowException` on SQL Server and PostgreSQL; a `char` key configured `ValueGeneratedOnAdd()` gets a temporary `char` on SQL Server, not a character of a GUID string. HiLo and your own generator still come first. A compiled model made with `--nativeaot`, and a key you configure with `ValueObjectConverter` yourself, still throw ([Keys](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.EntityFrameworkCore/README.md#keys)).
- Packages: nuget.org's Release Notes tab and Visual Studio's Updates tab show the version's section of this changelog, as plain text, where they showed a link to the file.
- Changelog: a released version's links name its tag, `blob/v2.2.0/`, so its notes open the pages as they were released, not as they read on `main` today.
- Comparison: SuperStrong.Types 1.0.0 joins the value-object table, Thinktecture.Runtime.Extensions is compared at 10.6.0 and LanguageExt at 5.0.0-beta-78, and the page's facts are re-read before every release ([How it compares](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/comparison.md)).
- README: introduces TypeKit by example, a one-line `OrderId`, a validated `Email`, an `Option` lookup and a `Result` chain, and thanks Vogen, which inspired the value objects; every README says what Metalama is where it first names it ([CodoMetis.TypeKit](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/README.md#what-you-get)).
- Comparison: SuperStrong.Types 1.0.0 joins the value-object table, Thinktecture.Runtime.Extensions is compared at 10.6.0 and LanguageExt at 5.0.0-beta-78, and the page's facts are re-read before every release ([How it compares](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/docs/comparison.md)).
- README: introduces TypeKit by example, a one-line `OrderId`, a validated `Email`, an `Option` lookup and a `Result` chain, and thanks Vogen, which inspired the value objects; every README says what Metalama is where it first names it ([CodoMetis.TypeKit](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/README.md#what-you-get)).

### Fixed

- Generators: the aspects keep no compilation objects in static fields, which in an IDE that keeps Metalama's process across edits held every earlier compilation in memory.
- Generators: a generated `TryParse(string?, IFormatProvider?, out)` ends at its `return`; the unreachable statements after it are gone from the woven code ([Reading the generated code](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/src/CodoMetis.TypeKit.Generators/README.md#reading-the-generated-code)).
- Generators: a generated `TryParse(string?, IFormatProvider?, out)` ends at its `return`; the unreachable statements after it are gone from the woven code ([Reading the generated code](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/v2.3.0/src/CodoMetis.TypeKit.Generators/README.md#reading-the-generated-code)).

## 2.2.0 — 2026-10-18

Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ The packages share one version and are released together, only through
anything but its tag or a full commit id.
3. Move the rules in `src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Unshipped.md` to
`AnalyzerReleases.Shipped.md`, under `## Release <version>`. Their ids and severities are public
contract from then on. `ChangelogTests` fails until steps 2 and 3 agree.
contract from then on. `ChangelogTests` fails until steps 2 and 3 agree. A release that ships no
rule gets the header alone, and loses it when the next release adds its own: the analyzer build
refuses any header after an empty release (RS2007).
4. Commit on the release branch, open its PR into `main` (`chore` and `area/release`, the version's
milestone, `Closes #<epic>`), and merge it with a merge commit. Tag that
merge commit and push the tag: `git tag v1.0.0 && git push origin v1.0.0`. The workflow runs every
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ Rule ID | Category | Severity | Notes
--------|----------|----------|-------------------------------------------------------------
CMTK0014 | Usage | Warning | UndefinedEnumValueAnalyzer, a FaultKind or ConstructorAccessibility that names no member

## Release 2.2.0
## Release 2.3.0
Loading