Olve.Utilities
Olve.Utilities is a meta-package bundling typed IDs, specialized collections, datetime formatting, pagination, graph utilities, and more for .NET projects.
Installation
dotnet add package Olve.Utilities
Included Sub-packages
Installing Olve.Utilities also brings in:
| Package | Description |
|---|---|
Olve.Results |
Lightweight functional result types for non-throwing error handling |
Olve.Paths |
Cross-platform path manipulation (Unix and Windows) |
Olve.Validation |
Fluent validation helpers |
Overview
| Category | Key Types | Description |
|---|---|---|
| IDs | Id, Id<T>, UnionId<T1, T2> |
GUID-backed typed identifiers with deterministic generation (UUIDv5) |
| Collections | BidirectionalDictionary<T1, T2>, FixedSizeQueue<T>, OneToManyLookup<TLeft, TRight>, ManyToManyLookup<TLeft, TRight> |
Specialized collection types with TryGet pattern lookups |
| DateTime | DateTimeFormatter |
Human-readable relative time formatting |
| Pagination | Pagination, PaginatedResult<T> |
Page/offset calculation and paginated result wrapper |
| Graphs | DirectedGraph, Node, DirectedEdge |
ID-based directed graph with node/edge management |
| Builders | IBuilder<T>, BuilderExtensions |
Builder pattern interface with validation integration |
| Sentinel types | NotFound, Success, AlreadyExists, Waiting, Skipped, Yes, Any |
Zero-size marker types for use with OneOf<T> discriminated unions |
| Startup | IAsyncOnStartup |
Priority-based async startup task interface with DI integration |
| Extensions | DictionaryExtensions, EnumerableExtensions, OneOfTryGetExtensions |
Collection, enumerable, and OneOf helper extensions |
Usage Examples
Typed IDs
Id<T> provides compile-time safety so you can't accidentally pass a user ID where an order ID is expected. Id.FromName() generates deterministic UUIDv5 identifiers from strings.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L18-L26
// Create a random typed ID
var userId = Id.New<User>();
// Deterministic ID from a name (UUIDv5)
var aliceId = Id.FromName<User>("alice");
var aliceId2 = Id.FromName<User>("alice"); // same as aliceId
// Parse from string
Id.TryParse<User>(userId.Value.ToString(), out var parsed); // parsed == userId
BidirectionalDictionary
BidirectionalDictionary<T1, T2> maintains two-way lookups. Both directions use the TryGet pattern.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L35-L42
var dict = new BidirectionalDictionary<string, int>();
dict.Set("alice", 1);
dict.Set("bob", 2);
// Look up in both directions
dict.TryGet("alice", out var id); // 1
dict.TryGet(2, out var name); // "bob"
OneToManyLookup
OneToManyLookup<TLeft, TRight> maps one key to many values. Reverse lookup returns the single owner of a value.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L51-L62
var lookup = new OneToManyLookup<string, int>();
// A parent maps to many children
lookup.Set("alice", 1, true);
lookup.Set("alice", 2, true);
lookup.Set("bob", 3, true);
// Get all values for a key
lookup.TryGet("alice", out var aliceValues); // { 1, 2 }
// Reverse lookup: which key owns this value?
lookup.TryGet(1, out var owner); // "alice"
ManyToManyLookup
ManyToManyLookup<TLeft, TRight> maintains a bidirectional many-to-many relationship. Both directions use the TryGet pattern.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L71-L81
var enrollment = new ManyToManyLookup<string, int>();
enrollment.Set("alice", 101, true);
enrollment.Set("alice", 102, true);
enrollment.Set("bob", 101, true);
// Get all course IDs for a student
enrollment.TryGet("alice", out var aliceCourses); // { 101, 102 }
// Get all students in a course
enrollment.TryGet(101, out var mathStudents); // { "alice", "bob" }
FixedSizeQueue
FixedSizeQueue<T> automatically manages items when the maximum size is exceeded. Configure the behavior with FullQueueBehavior: DropOldest (default), DropNewest, or Throw.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L90-L111
var queue = new FixedSizeQueue<string>(maxSize: 3);
queue.Enqueue("a");
queue.Enqueue("b");
queue.Enqueue("c");
queue.Enqueue("d"); // "a" is dropped, queue is now { "b", "c", "d" }
queue.TryDequeue(out var first); // "b"
// configure back-pressure behavior
var strict = new FixedSizeQueue<string>(maxSize: 2, FullQueueBehavior.Throw);
strict.Enqueue("x");
strict.Enqueue("y");
// strict.Enqueue("z"); // throws InvalidOperationException
var dropping = new FixedSizeQueue<string>(maxSize: 2, FullQueueBehavior.DropNewest);
dropping.Enqueue("x");
dropping.Enqueue("y");
var accepted = dropping.Enqueue("z"); // false — "z" is rejected
// TryEnqueue fails when full, regardless of policy
var tried = strict.TryEnqueue("z"); // false — queue is at capacity
DateTime formatting
DateTimeFormatter.FormatTimeAgo() produces human-readable relative time strings like "2 days ago" or "just now".
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L122-L125
var now = new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero);
var then = new DateTimeOffset(2025, 6, 13, 12, 0, 0, TimeSpan.Zero);
var text = DateTimeFormatter.FormatTimeAgo(now, then); // "2 days ago"
Pagination
Pagination computes offsets from page number and size. PaginatedResult<T> wraps a page of items with total count and navigation metadata.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L132-L141
var items = new[] { "alice", "bob", "charlie" };
var pagination = new Pagination(Page: 0, PageSize: 2);
var result = new PaginatedResult<string>(
items: items[..2],
pagination: pagination,
totalCount: items.Length);
// result.HasNextPage == true
DirectedGraph
DirectedGraph provides an ID-based directed graph with node and edge management.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L151-L163
var graph = new DirectedGraph();
// Create nodes
var nodeA = graph.CreateNode();
var nodeB = graph.CreateNode();
var nodeC = graph.CreateNode();
// Create edges: A -> B, A -> C
graph.CreateEdge(nodeA, nodeB);
graph.CreateEdge(nodeA, nodeC);
// Query outgoing edges
DictionaryExtensions
High-performance GetOrAdd and TryUpdate extensions using CollectionsMarshal for zero-overhead dictionary operations.
// ../../tests/Olve.Utilities.Tests/ReadmeDemo.cs#L172-L182
var cache = new Dictionary<string, List<int>>();
// GetOrAdd: get existing value or create it
var list = cache.GetOrAdd("scores", () => []);
list.Add(100);
var same = cache.GetOrAdd("scores", () => []); // same reference as list
// TryUpdate: update only if the key exists
var updated = cache.TryUpdate("scores", old => [..old, 200]); // true
Documentation
Full API reference: https://olivervea.github.io/Olve.Utilities/api/Olve.Utilities.html
License
MIT License © OliverVea