Etymon.Core
0.1.0-preview.7
dotnet add package Etymon.Core --version 0.1.0-preview.7
NuGet\Install-Package Etymon.Core -Version 0.1.0-preview.7
<PackageReference Include="Etymon.Core" Version="0.1.0-preview.7" />
<PackageVersion Include="Etymon.Core" Version="0.1.0-preview.7" />
<PackageReference Include="Etymon.Core" />
paket add Etymon.Core --version 0.1.0-preview.7
#r "nuget: Etymon.Core, 0.1.0-preview.7"
#:package Etymon.Core@0.1.0-preview.7
#addin nuget:?package=Etymon.Core&version=0.1.0-preview.7&prerelease
#tool nuget:?package=Etymon.Core&version=0.1.0-preview.7&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.
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.Migrations
A relational model derived from an Etymon schema, snapshot diffing, and SQL migration scripts for PostgreSQL and SQLite. Generates SQL text and executes nothing, so it carries no database driver. Ambiguous mappings are errors rather than guesses, and destructive changes are flagged rather than emitted silently. |
|
|
Etymon.Api.Client
Calls an Etymon.Api endpoint over HTTP. The declaration is enough to build the URL, encode the body and decode the response, so a console application calling an Etymon-described API needs no web framework and no server. |
|
|
Etymon.Config
Configuration loaded through an Etymon schema, from environment variables, JSON files or a source of your own. Reports every missing or invalid setting at once, each with what was expected and which source supplied it. Sensitive settings are redacted wherever Etymon prints them. |
|
|
Etymon.Schema.Compatibility
Event shapes derived from an Etymon Schema, committed as a snapshot and compared for compatibility in both directions: can new code read events already written, and can already-deployed code read events written by new code. Pure and driver-free. It never emits SQL, never opens a connection, and never proposes rewriting an event, because the events already written are the one irreplaceable thing in the system. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-preview.7 | 0 | 9/21/2026 |
| 0.1.0-preview.6 | 0 | 9/21/2026 |
| 0.1.0-preview.5 | 0 | 9/21/2026 |
| 0.1.0-preview.4 | 31 | 9/21/2026 |
| 0.1.0-preview.3 | 33 | 9/21/2026 |
| 0.1.0-preview.2 | 38 | 9/20/2026 |
| 0.1.0-preview.1 | 48 | 9/20/2026 |