Etymon.Core
0.1.0-preview.17
dotnet add package Etymon.Core --version 0.1.0-preview.17
NuGet\Install-Package Etymon.Core -Version 0.1.0-preview.17
<PackageReference Include="Etymon.Core" Version="0.1.0-preview.17" />
<PackageVersion Include="Etymon.Core" Version="0.1.0-preview.17" />
<PackageReference Include="Etymon.Core" />
paket add Etymon.Core --version 0.1.0-preview.17
#r "nuget: Etymon.Core, 0.1.0-preview.17"
#:package Etymon.Core@0.1.0-preview.17
#addin nuget:?package=Etymon.Core&version=0.1.0-preview.17&prerelease
#tool nuget:?package=Etymon.Core&version=0.1.0-preview.17&prerelease
Etymon.Core
The foundation of the Etymon suite.
Depends on nothing but FSharp.Core. It trims to roughly 85 KB when the
refinement and error model are used, so it can sit in a Blazor WebAssembly
domain layer rather than stopping at a server boundary. That figure is measured,
not estimated: eng/trim-size.sh publishes a trimmed application that uses the
package and prints what the assembly actually weighs.
Take this package alone if you want smart constructors and a proper validation
error model, and nothing else. In particular, the error vocabulary deliberately
does not depend on Etymon.Schema: if you hand-write your JSON codecs — because
the bytes already in your database are a contract you cannot let a rename change
— you can adopt Path and ValidationErrors and keep those codecs exactly as
they are.
What it gives you
Errors that accumulate, and know where they came from
open Etymon
validation {
let! name = NonEmpty.create raw.Name |> Validation.underField "name"
and! age = Age.create raw.Age |> Validation.underField "age"
and! zip = Zip.create raw.Zip |> Validation.underField "address"
return { Name = name; Age = age; Zip = zip }
}
// Error, all at once:
// name: must be at least 1 character
// age: must be between 0 and 130
// address: must be exactly 5 characters
Validation<'T> is an abbreviation for Result<'T, ValidationErrors>, not a new
type, so it pattern-matches as Ok / Error and interoperates with everything
that already understands Result. Use and! to collect every error; a plain
sequence of let! uses bind and stops at the first, because a dependent step
has no value to work with.
Constrained types in a few lines
type Email = private Email of string
module Email =
let refinement =
Refine.ofString "Email"
|> Refine.trimmed
|> Refine.lowercased
|> Refine.length (Some 3) (Some 254)
|> Refine.format Format.Email
|> Refine.wrap Email (fun (Email s) -> s)
let create (s: string) = Refine.create refinement s
let value (Email s) = s
Every rule runs, so a value that breaks three of them reports three errors.
Normalisation (trimmed, lowercased) always happens before any rule, so
" " trims to "" and is then correctly rejected as empty.
Constraints as data
Email.refinement.Constraints
// [ Length(Some 3, Some 254); HasFormat Email ]
This list is the point of the whole suite. Etymon.Schema.OpenApi turns it into
JSON Schema keywords, Etymon.Schema.Sql into SQL constraints,
Etymon.Invariants.FsCheck into generators — all reading the same rules rather
than restating them. A rule Etymon cannot inspect is Opaque: enforced and
documented, but never half-derived into something that looks right and is not.
Secrets that stay secret
let settings = { Host = "db.internal"; Password = Secret.create "hunter2" }
printfn "%A" settings
// { Host = "db.internal"; Password = <redacted> }
Redaction lives on the type, so ToString, %O, %A, string and most
structured loggers are all covered — including when the secret is nested inside
the record somebody actually logs. Getting the value back is Secret.reveal,
which is one greppable token, so auditing where secrets escape is a search
rather than an audit.
Public surface
The complete public surface of this package, every value with its full signature
and its documentation, is in Surface.fsi beside this file. It is written by the
build from the implementation, so it is where to learn what a function hands
back without compiling anything.
Licence
MIT.
| 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 was computed. 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. |
-
net10.0
- FSharp.Core (>= 8.0.100)
-
net8.0
- FSharp.Core (>= 8.0.100)
NuGet packages (16)
Showing the top 5 NuGet packages that depend on Etymon.Core:
| Package | Downloads |
|---|---|
|
Etymon.Schema
A combinator-based schema type for F#: one value that describes how a type is encoded, decoded, validated and documented. JSON via System.Text.Json, decode errors that accumulate and carry field paths, and an untyped description that OpenAPI, SQL and test generators all read. |
|
|
Etymon.Schema.OpenApi
Generates JSON Schema 2020-12 and OpenAPI 3.1 component schemas from an Etymon.Schema, including constraints, formats, descriptions and examples. A pure function of the schema description, so a generated document cannot describe something different from what the codec does. |
|
|
Etymon.Base
The .NET base library, returning option and Result instead of null and exceptions. Null-safe strings, dictionary lookups, environment variables, and file IO with a typed error union naming the cases a caller can act on. Culture-explicit parsing lives in Etymon.Core, so a schema does not drag file and environment access into a browser payload to get it. |
|
|
Etymon
The Etymon suite: one schema definition as the source of truth for JSON codecs, validation, OpenAPI, TypeScript, configuration and database structure. This package has no code of its own; it references the parts of the suite that cost nothing but FSharp.Core. The adapters (Etymon.Api.Giraffe, Etymon.Api.AspNetCore) and the generator package (Etymon.Invariants.FsCheck) are deliberately left out, because each carries a dependency that would then be everyone's. |
|
|
Etymon.Invariants
Rules that must always be true of a type, declared once and exposed as data. For the cross-field rules that cannot live on any single field -- an end date after a start date, a discount within a total, two fields present together or not at all. Every rule runs, so every failure is reported. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-preview.17 | 115 | 10/6/2026 |
| 0.1.0-preview.16 | 115 | 10/5/2026 |
| 0.1.0-preview.15 | 122 | 10/5/2026 |
| 0.1.0-preview.14 | 138 | 9/28/2026 |
| 0.1.0-preview.13 | 237 | 9/22/2026 |
| 0.1.0-preview.12 | 123 | 9/22/2026 |
| 0.1.0-preview.11 | 127 | 9/22/2026 |
| 0.1.0-preview.10 | 127 | 9/22/2026 |
| 0.1.0-preview.9 | 125 | 9/22/2026 |
| 0.1.0-preview.8 | 125 | 9/22/2026 |
| 0.1.0-preview.7 | 121 | 9/21/2026 |
| 0.1.0-preview.6 | 124 | 9/21/2026 |
| 0.1.0-preview.5 | 119 | 9/21/2026 |
| 0.1.0-preview.4 | 125 | 9/21/2026 |
| 0.1.0-preview.3 | 116 | 9/21/2026 |
| 0.1.0-preview.2 | 120 | 9/20/2026 |
| 0.1.0-preview.1 | 127 | 9/20/2026 |