Wiaoj.Primitives 0.3.0-alpha.1

This is a prerelease version of Wiaoj.Primitives.
dotnet add package Wiaoj.Primitives --version 0.3.0-alpha.1
                    
NuGet\Install-Package Wiaoj.Primitives -Version 0.3.0-alpha.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Wiaoj.Primitives" Version="0.3.0-alpha.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Wiaoj.Primitives" Version="0.3.0-alpha.1" />
                    
Directory.Packages.props
<PackageReference Include="Wiaoj.Primitives" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Wiaoj.Primitives --version 0.3.0-alpha.1
                    
#r "nuget: Wiaoj.Primitives, 0.3.0-alpha.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Wiaoj.Primitives@0.3.0-alpha.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Wiaoj.Primitives&version=0.3.0-alpha.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Wiaoj.Primitives&version=0.3.0-alpha.1&prerelease
                    
Install as a Cake Tool

Wiaoj.Primitives

Wiaoj.Primitives is a high-performance, security-focused .NET foundation library engineered to eliminate Primitive Obsession in domain-driven applications.

In standard .NET development, developers frequently rely on generic primitives (string, byte[], double, long) to represent complex domain concepts like cryptographic keys, identifiers, semantic versions, and encoded payloads. This leads to defensive coding, memory leaks in the managed heap, and severe performance bottlenecks.

This library replaces these generic types with self-validating, strongly-typed value objects. Designed for modern .NET, it heavily utilizes ref struct, Span<T>, SIMD vectorization (SearchValues<T>), fixed-size buffers, and unmanaged memory manipulation to deliver zero-allocation performance and cryptographic-grade security.

Installation & Requirements

dotnet add package Wiaoj.Primitives

Factory Naming

Every type follows the same verbs, so the name of a factory tells you what it does:

Verb Meaning Examples
Parse / TryParse Read the type's canonical text form (ISpanParsable<T>, IUtf8SpanParsable<T>) SemVer.Parse("1.2.3"), UnixTimestamp.TryParse(text, out _)
From / FromX / TryFrom Wrap one existing value: bytes, a number, a unit Sha256Hash.FromBytes(bytes), UnixTimestamp.FromSeconds(exp), Ed25519PublicKey.From(x)
Create / TryCreate Build from several parts GeoCoordinate.Create(lat, lon), RsaPublicKey.Create(modulus, exponent), Urn.Create(nid, nss)
Generate Produce new random key material AesGcmKey.Generate256(), RsaKeyPair.Generate()
New / NewId Produce a new identifier SnowflakeId.NewId(), NanoId.NewId()
  • Try variants: the Try form of each verb returns false instead of throwing, for untrusted input such as a JWT exp claim.
  • Exception: GuidV7.Create() keeps the name of the BCL's Guid.CreateVersion7(), which it wraps.

1. Secure Memory Management (Secret<T>)

Standard .NET types like string and byte[] are managed by the Garbage Collector (GC). They are immutable, meaning any modification creates a copy, and they linger in memory until a GC cycle collects them. This makes them highly vulnerable to memory dumps and timing attacks.

Secret<T> solves this by allocating memory outside the managed heap using NativeMemory.AllocZeroed. The memory is pinned, inaccessible to the GC, and guaranteed to be cryptographically wiped (CryptographicOperations.ZeroMemory) when disposed.

using Wiaoj.Primitives;

// 1. Generate a cryptographically strong 256-bit AES key directly into secure memory
using Secret<byte> masterKey = Secret.Factory.Aes256Key();

// 2. Parse a secret from an external source (intermediate strings are wiped)
using Secret<byte> apiKey = Secret.From("super-secret-key-123");

// 3. Controlled Access (The Expose Pattern)
// Data cannot escape this scope. It prevents accidental logging or leaking.
apiKey.Expose(span => 
{
    // 'span' is a ReadOnlySpan<byte> pointing directly to the unmanaged memory.
    // It is safe to pass this to cryptographic algorithms.
    Console.WriteLine($"Key length: {span.Length}");
});

// 4. Secure Key Derivation (HKDF-SHA256)
using Secret<byte> derivedKey = masterKey.DeriveKey(salt: apiKey, outputByteCount: 64);

2. Distributed Identity

Relying on standard auto-incrementing int or random Guid (v4) for primary keys leads to database B-Tree index fragmentation and leaks business intelligence (e.g., how many orders you process daily).

SnowflakeId & GuidV7 (Time-Ordered IDs)

SnowflakeId is a lock-free, thread-safe implementation of the Twitter Snowflake algorithm. It generates 64-bit, k-sorted identifiers that eliminate database fragmentation without requiring a central coordination server. GuidV7 provides the 128-bit RFC 9562 equivalent.

using Wiaoj.Primitives.Snowflake;

// Configure the node identity at application startup
SnowflakeId.Configure(nodeId: 1);

// Generate a 64-bit ID (Zero allocation)
SnowflakeId internalId = SnowflakeId.NewId();
UnixTimestamp creationTime = internalId.ToUnixTimestamp();

Public identifiers

OpaqueId and the Wiaoj.Primitives.Obfuscation namespace have been removed. Their obfuscators looked like encryption but were not: the keys came straight from the seed's first bytes, the ciphers were custom, and nothing detected a forged value. Strongly typed, prefixed public identifiers, with a plain or AES-encrypted form, are moving to Wiaoj.Identifiers.

NanoId

For completely random, highly collision-resistant public identifiers.

// Generates a 21-character URL-safe string. 
// Uses a default profanity-safe alphabet (vowels removed).
NanoId videoId = NanoId.NewId();

3. Allocation-Free Cryptographic Hashing

Traditional hashing APIs return a byte[], forcing a heap allocation on every single hash computation.

Wiaoj.Primitives provides Sha256Hash, Sha512Hash, Md5Hash, and Hmac variants as fixed-size struct wrappers containing inline arrays (e.g., fixed byte[32]). They live entirely on the stack. Furthermore, their Equals operators use CryptographicOperations.FixedTimeEquals to prevent timing attacks.

using Wiaoj.Primitives.Cryptography.Hashing;

// Compute hash on the stack (Zero heap allocations)
Sha256Hash documentHash = Sha256Hash.Compute("document content");

// Constant-time equality comparison
if (documentHash == expectedHash) {
    // Valid
}

// Memory-efficient async stream hashing (e.g., large file uploads)
using var stream = File.OpenRead("large_data.bin");
Sha256Hash streamHash = await Sha256HashExtensions.ComputeAsync(stream);

4. Strongly-Typed Encodings

A method requiring a Base64 payload should not accept a standard string. By using specialized types, the format is validated instantly upon instantiation using .NET 8 SearchValues<T> for SIMD-accelerated character scanning.

Available types: Base64String, Base64UrlString, Base32String, Base62String, HexString.

// Validates format instantly. Throws FormatException if invalid.
Base64String payload = Base64String.Parse("SGVsbG8gV29ybGQ=");
HexString signature = HexString.Parse("48656C6C6F");

// Decode directly to stack memory without allocating intermediate byte arrays
Span<byte> buffer = stackalloc byte[64];
if (signature.TryDecode(buffer, out int bytesWritten)) {
    // Process raw bytes securely
}

5. Domain Primitives & Value Objects

Range<T>

A structural pattern that guarantees Min <= Max at creation. It provides deep domain-specific extensions based on the generic type.

// Numeric Ranges
Range<int> validAges = new Range<int>(18, 65);
int userAge = validAges.Clamp(70); // Returns 65
int span = validAges.Length();     // Returns 47

// Temporal Ranges
Range<DateTime> promotionPeriod = Range<DateTime>.Between(startDate, endDate);
bool isActive = promotionPeriod.IsNowWithin(); // Automatically checks against UTC Now

// Semantic Version Ranges
Range<SemVer> requiredVersion = new Range<SemVer>(SemVer.Parse("1.0.0"), SemVer.Parse("2.0.0"));
SemVer? latest = requiredVersion.GetLatestCompatible(availableVersions);

UnixTimestamp

Eliminates the ambiguity of passing long variables around by explicitly representing UTC milliseconds since Epoch. Provides low-overhead date manipulation without instantiating DateTime objects.

UnixTimestamp now = UnixTimestamp.Now;

// High-performance truncation using integer math
UnixTimestamp startOfDay = now.TruncateToDay(); 

// Temporal logic
bool isExpired = cacheExpiry.IsOlderThan(TimeSpan.FromMinutes(10));
TimeSpan remaining = cacheExpiry.TimeUntil();

Values are always within UnixTimestamp.MinValue…MaxValue (the range of DateTimeOffset):

  • Creating: FromSeconds, FromMilliseconds, the long cast and arithmetic throw ArgumentOutOfRangeException outside it.
  • Parsing: TryParse returns false, and JSON throws JsonException.
  • Untrusted numbers: such as a JWT exp claim; use the non-throwing factories:
if(!UnixTimestamp.TryFromSeconds(expSeconds, out UnixTimestamp expiration)) {
    return JwtParseStatus.InvalidPayloadJson;
}

MonotonicTimestamp arithmetic throws OverflowException instead of wrapping around, so now + TimeSpan.MaxValue can't turn into an instant in the past.

SemVer

A strict, allocation-free implementation of Semantic Versioning 2.0.0. It supports pre-release and build metadata parsing without the heavy overhead of System.Version.

SemVer current = SemVer.Parse("1.2.3-beta.1");
SemVer next = current.BumpMinor(); // 1.3.0

if (next.IsBackwardCompatibleWith(current)) {
    // Validates Major/Minor precedence rules
}

Percentage

Wraps a double representing a value between 0.0 and 1.0. Prevents logic errors involving out-of-bounds percentages.

Percentage discount = Percentage.FromInt(15); // Represents 0.15
Percentage remaining = discount.Remaining;    // Represents 0.85

double finalPrice = remaining.ApplyTo(100.0); // 85.0

OperationTimeout

Unifies API parameters that historically required passing both a TimeSpan (for absolute timeouts) and a CancellationToken (for external cancellation).

// Method signature expects a single unified object
public async ValueTask FetchDataAsync(OperationTimeout timeout) 
{
    // Throws instantly if already expired or cancelled
    timeout.ThrowIfExpired(); 

    // Create a combined CancellationTokenSource automatically
    using var cts = timeout.CreateCancellationTokenSource();
    await _httpClient.GetAsync("/api/data", cts.Token);
}

// Caller can pass either type, and it implicitly converts:
await FetchDataAsync(TimeSpan.FromSeconds(5)); 
await FetchDataAsync(HttpContext.RequestAborted); 

System.Text.Json Integration

All primitives (SnowflakeId, Base64String, SemVer, UnixTimestamp, etc.) are decorated with high-performance JsonConverter implementations. They serialize directly to primitive JSON formats (strings or numbers) using UTF-8 span writers, bypassing intermediate string allocations entirely.

License

Licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (17)

Showing the top 5 NuGet packages that depend on Wiaoj.Primitives:

Package Downloads
Tyto

Package Description

Wiaoj.Extensions

High-performance, general-purpose extension methods for the Wiaoj ecosystem. Built on top of Wiaoj.Primitives and Preconditions.

Wiaoj.Serialization.Security

Encrypts what a Wiaoj serializer writes with a Wiaoj.Security key ring: authenticated AES-GCM that follows key rotation.

Tyto.Caching.Abstractions

Package Description

Tyto.Transports.InMemory

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0-alpha.1 248 10/5/2026
0.2.0-alpha.3 191 9/24/2026
0.2.0-alpha.2 180 9/24/2026
0.2.0-alpha.1 182 9/24/2026
0.1.0-alpha.9 295 9/21/2026
0.1.0-alpha.8 184 9/21/2026
0.1.0-alpha.7 2,174 9/18/2026
0.1.0-alpha.6 179 9/16/2026
0.1.0-alpha.5 173 9/16/2026
0.1.0-alpha.4 175 9/16/2026
0.1.0-alpha.3 233 9/15/2026
0.1.0-alpha.2 503 9/15/2026
0.1.0-alpha.1 294 9/14/2026
0.0.1-alpha.112-preview 168 9/13/2026
0.0.1-alpha.111-preview 160 9/13/2026
0.0.1-alpha.110-preview 156 9/12/2026
0.0.1-alpha.109-preview 180 9/11/2026
0.0.1-alpha.108-preview 181 9/8/2026
0.0.1-alpha.107-preview 205 9/8/2026
0.0.1-alpha.106-preview 179 9/8/2026
Loading failed