docs: split the README and state what the package adds over the alternatives - #19
Merged
Merged
Conversation
…ages
The README had grown to 1227 lines: six "What's new in vX" sections and three
migration guides sat between the pitch and the API docs, so the reference
material a reader returns to started around line 250 and ran to the end. The
problem was organisation, not volume — nothing here is new prose.
Sections were extracted by line range rather than retyped, so 787 of the 789
non-blank lines outside the deleted "What's new" block are byte-identical in
their new home:
docs/ranges.md construction, pattern matching, query and set algebra,
RangeSet, the interface hierarchy
docs/efcore.md mapping, LINQ-to-SQL translation, timerange,
YearMonthRange storage, what runs where
docs/serialization.md literals, parsing, ISpanParsable, System.Text.Json
docs/value-sets.md canonical scalar sets over native arrays
docs/migration.md the v1.x / v2.x / v3.x source changes
The "What's new" sections are dropped rather than moved: CHANGELOG.md already
carries all eight of those versions in full, and duplicating release notes in
the README is what let them accumulate. The README keeps the framing, the type
matrices, install, a new quick start, a documentation index, and the
verified-against-PostgreSQL section.
Four directional references ("documented below", "the rule documented above")
became wrong once their target moved to another page and are now links. The
subsection headings in value-sets.md and efcore.md move from ### to ## because
their ## parent became the page #; GitHub slugs are level-independent, so no
anchor changed.
Verified: a link checker over all 15 markdown files resolves every relative
link and anchor, including the ~20 cross-references rewritten here. The root
README is not packed into any package (all four ship their own), and
PackagingConventionTests asserts only on src/*/README.md. Build clean, 1117
tests pass including the live-PostgreSQL suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The READMEs explained what the library does and never what you would otherwise
use, so the first question a reader has — why not NpgsqlRange<T>, which ships
with the driver — went unanswered.
Adds "Why this exists" to the root README and "Why not NpgsqlRange<T>?" to the
package README, both built from a survey of the current alternatives rather
than a claim of uniqueness, so a reader can check them:
NpgsqlRange<T> declared in NpgsqlTypes, in Npgsql.dll, so a domain
model using it references the driver; the struct has
no algebra, and the EF extension methods carrying it
are documented "only intended for use via SQL
translation as part of an EF Core LINQ query"
NodaTime Interval / real domain types the Npgsql plugin maps, but two
DateInterval date/time shapes only, and DateInterval is always
closed and bounded — never half-open, unbounded, empty
FRange, Open.Range in-memory algebra, no persistence or SQL translation
T[] / List<T> a list, not a set: order and multiplicity are part of
the value
Deliberately not claimed: that this is the only or first such library. The
sections state what each alternative does not cover and make the novelty claim
about the combination, which is checkable and ages honestly. The survey is
dated and invites correction by issue.
One correction worth noting separately: Npgsql already translates the full
range operator set, so SQL translation is not the differentiator and the text
says so. What is missing elsewhere is a domain type to hang it on, and any
equivalent at all on the value set side.
Also fixes a wrong comment on the package README — and so the NuGet page —
which claimed Int32Range.Parse("[1,10)") round-trips to "[1,10)". Discrete
ranges canonicalize to "[1,9]", per
RangeParseFormatTests.Parse_DiscreteRange_HalfOpenInput_CanonicalizesToClosed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mmutability Three corrections to the positioning sections, two of them verified against the libraries' source rather than their package descriptions. FRange keeps unboundedness a runtime fact. Its C# surface exposes LowerBoundValue/UpperBoundValue over an F# DU whose Unbounded case hits `failwith "No bound"` (FRange/CSharp.fs), paired with a HasLowerBound the caller is expected to remember. That is the same question the union design here refuses to let you ask, so the comparison now names it. Neither FRange nor Open.Range has a discrete domain — no step, no canonicalization — so [1,10) and [1,9] stay different values and integer or date adjacency cannot be decided at all. That is a bigger gap than "no persistence" and was missing. Value sets: immutability was the omission. T[] and List<T> are mutable references, so a domain hands out its own invariant on load and cannot defend it afterwards. Both READMEs now say the sets are immutable end to end and canonical on every construction path. Deliberately NOT claimed: that Open.Range accepts invalid ranges. Its constructor calls Range.AssertIsValid, and Boundary<T>.CanRangeWith rejects equal-valued exclusive bounds, so even the degenerate (5,5) throws. The only way past it is default(Range<Boundary<T>>), which bypasses the constructor as any validating struct does — too language-general to hang a comparison on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The README had grown to 1227 lines / 86 KB. Six
What's new in vXsections and three migrationguides sat between the pitch and the API docs, so the reference material a reader actually returns
to started around line 250 and ran to the end. Separately, the READMEs explained what the library
does and never what you would otherwise use — so the first question a reader has, why not
NpgsqlRange<T>, which ships with the driver, went unanswered on both the repo page and the NuGetpage.
Docs only. No source changes, no behavioural changes, no public API surface touched.
What changed
1. Split the README into a landing page and five reference pages (
2256c3f)docs/ranges.mdRangeSet, interface hierarchydocs/efcore.mdtimerange,YearMonthRangestorage, what runs wheredocs/serialization.mdISpanParsable, System.Text.Jsondocs/value-sets.mddocs/migration.mdREADME is now 203 lines. The
What's newsections are dropped rather than moved —CHANGELOG.mdalready carries all eight of those versions in full, and duplicating release notes in the README is
what let them accumulate.
2. Added positioning to both READMEs (
31c7d8e) —## Why this existsin the root README,## Why not NpgsqlRange<T>?on the NuGet page. Built from a survey of the current alternativesrather than a claim of uniqueness, so a reader can check each line.
3. Sharpened the comparison after review (
ac589dd) — discrete domains, FRange's partialaccessors, and value-set immutability.
The comparison, and what it deliberately does not claim
Verified against the libraries' own source and docs, not their package descriptions:
NpgsqlRange<T>is declared inNpgsqlTypes, inNpgsql.dll— a domain model using itreferences the database driver. The struct carries no algebra at all;
Contains,Overlaps,Unionand the rest are EF Core extension methods documented "only intended for use via SQLtranslation as part of an EF Core LINQ query", and throw client-side.
Interval/DateIntervalare real domain types the Npgsql plugin maps andtranslates — but two date/time shapes only, and
DateIntervalis always closed and bounded.LowerBoundValueover anF# DU whose
Unboundedcase isfailwith "No bound"(FRange/CSharp.fs).[1,10)and[1,9]stay different values and integer or date adjacency cannot be decided.T[]/List<T>are mutable references and sequence-equal, so the domain hands out its owninvariant on load.
Two things are stated explicitly against interest, because the sections are meant to be
falsifiable:
differentiator and the README says so; what is missing elsewhere is a domain type to hang it on.
Range.AssertIsValid, andBoundary<T>.CanRangeWithrejects equal-valued exclusive bounds, soeven the degenerate
(5,5)throws.The survey is dated (August 2026) and invites correction by issue.
Drive-by fix
The package README — and therefore the NuGet page — claimed
Int32Range.Parse("[1,10)", null).ToString()returns[1,10). It returns[1,9]; discrete rangescanonicalize, per
RangeParseFormatTests.Parse_DiscreteRange_HalfOpenInput_CanonicalizesToClosed.Verification
Content moved by line-range extraction rather than retyped, then checked mechanically:
What's newblock are byte-identical in theirnew home. The remainder are the two headings promoted to page titles and four lines reworded
because the split broke their
above/belowreferences.including the ~20 cross-references rewritten here.
PackagingConventionTestsasserts only onsrc/*/README.md.Testcontainers suite.
For the reviewer
src/CodoMetis.ValueRanges/README.mdis packed, so this PR changes what renders on the NuGetpage.
consumer-smoke-test.shwill pack it in both modes — that difference frommainisintended, not a regression.
every row should stay defensible.
🤖 Generated with Claude Code