Cabinet 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package Cabinet --version 1.0.3
                    
NuGet\Install-Package Cabinet -Version 1.0.3
                    
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="Cabinet" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cabinet" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Cabinet" />
                    
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 Cabinet --version 1.0.3
                    
#r "nuget: Cabinet, 1.0.3"
                    
#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 Cabinet@1.0.3
                    
#: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=Cabinet&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=Cabinet&version=1.0.3
                    
Install as a Cake Tool

Cabinet

NuGet Version

GitHub Workflow Status

icon

A secure, indexed offline datastore for .NET. It’s the simplest way to persist structured data locally - encrypted, searchable, and AOT-safe - without the complexity of a traditional database.

Why

Most mobile apps don’t need a database, they need an offline data store. Something that can securely persist structured data, search it quickly, and work seamlessly across all target platforms.

Traditional solutions like SQLite or LiteDB are excellent tools, but they bring baggage:

  • Native dependencies and platform-specific quirks
  • AOT compilation issues (LiteDB)
  • Configuration overhead (SQLite encryption - which has commercial implications too)
  • API friction for what often boils down to “store, query, retrieve”

Cabinet was created to solve that problem.

It gives you database-like capabilities without the database: encryption, indexing, fast lookups, and predictable performance — all in pure .NET.

What it is

Cabinet borrows the document-style persistence approach of NoSQL systems, but applies it in a lightweight, encrypted, AOT-friendly way tailored for mobile apps.

Concept Description
A secure offline datastore Encrypted at rest, zero configuration. There is no “unencrypted mode.”
Indexed by design Full-text search and keyword filtering are built in, not bolted on.
AOT-safe No runtime code generation, reflection, or JIT reliance. Works on iOS, Android, macOS, Windows, Catalyst.
Extensible Bring your own storage, indexer, or encryption provider if you need to.
“Pit of success” defaults Security, predictability, and simplicity are defaults — not optional flags.

If you think you need a mobile database, you might actually need this.

What it isn't

It isn’t Because…
A relational database There are no joins or schemas, it stores domain objects, not rows.
An ORM You interact with your models directly.
A cloud sync engine Data lives locally; sync is up to you.
A toy It’s built for production use: encrypted, indexed, and tested under load.

If you’ve ever used IndexedDB in the browser, you can think of this as IndexedDB for standalone .NET projects, but simpler, safer, and designed for .NET idioms.

Features

  • AES-256-GCM encryption (per file)
  • HKDF key derivation with SecureStorage master key
  • Persistent encrypted full-text index
  • Atomic writes (no plaintext ever on disk)
  • JSON serialisation (customisable)
  • Extensible architecture (BYO store, index, encryption)
  • 100% managed .NET, AOT-safe, no native dependencies

Performance Summary

Cabinet offers consistent, predictable performance, even with encryption.

Pattern Records Operation Duration Search (avg) Cold Start
Record-per-file 5,000 Save + Index all records 10,000 ms 0.9 ms 19 ms
Aggregate files 5,000 Save + Index all records 30 ms 0.15 ms 4 ms

Tests measure saving and indexing the entire dataset, not single-record inserts. For incremental operations, performance is effectively instantaneous.

Architecture: Three Layers

Cabinet has a layered architecture that lets you choose your level of control:

Layer 1: Core Storage (Advanced Users)

The foundational layer provides low-level encrypted storage:

  • IOfflineStore - Core encrypted storage operations
  • IEncryptionProvider - Pluggable encryption (default: AES-256-GCM)
  • IIndexProvider - Pluggable full-text search

When to use: You need maximum control over storage behavior, custom encryption, or specialized indexing.

Layer 2: RecordSet API (🎯 Start Here - Most Users)

The high-level domain abstraction that makes Cabinet feel like a document database:

  • RecordSet<T> - Manages collections of typed records with auto-caching and CRUD operations
  • RecordQuery<T> - LINQ-style fluent queries
  • RecordCollection<T> - Scoped collections under a single ID

When to use: This is the recommended API for 95% of scenarios. It handles file discovery, loading, caching, and persistence automatically.

Example:

// Setup (once at app startup)
var store = /* ... see Layer 1 for setup ... */;

// Create a RecordSet for your domain type
var lessons = new RecordSet<LessonRecord>(store, new RecordSetOptions<LessonRecord>
{
    IdSelector = lesson => lesson.LessonId  // For AOT compatibility
});

// Load all lessons into memory cache
await lessons.LoadAsync();

// Add a new lesson (auto-persists to disk)
await lessons.AddAsync(new LessonRecord
{
    LessonId = "lesson-001",
    Subject = "Science",
    Description = "Observed seagulls at the beach"
});

// Query cached data (no disk I/O)
var scienceLessons = lessons.Where(l => l.Subject == "Science");
var recentLessons = lessons.OrderByDescending(l => l.Date).Take(10);

// Search using encrypted index
var results = await lessons.FindAsync("seagulls");

Layer 3: Extension Methods (Convenience)

Syntactic sugar for cleaner code:

  • FindManyAsync() - Search with automatic data extraction
  • WhereMatch() - Fluent filter chaining

When to use: These make your code more readable but are entirely optional.

Quick Start

Install the NuGet package

dotnet add package Cabinet
using Cabinet;
using Cabinet.Core;
using Cabinet.Security;

// 1. Setup storage (once at app startup)
var masterKey = new byte[32];
RandomNumberGenerator.Fill(masterKey);
// In production, store this in SecureStorage

var encryption = new AesGcmEncryptionProvider(masterKey);
var store = new FileOfflineStore(FileSystem.AppDataDirectory, encryption);

// 2. Create RecordSet for your domain type
var options = new RecordSetOptions<LessonRecord>
{
    IdSelector = lesson => lesson.LessonId  // AOT-safe
};
var lessons = new RecordSet<LessonRecord>(store, options);

// 3. Load and use
await lessons.LoadAsync();
await lessons.AddAsync(new LessonRecord { LessonId = "001", Subject = "Science" });
var all = await lessons.GetAllAsync();

Using IOfflineStore Directly (Advanced)

using Cabinet;
using Cabinet.Index;
using Cabinet.Security;

// Generate or retrieve a master encryption key (32 bytes)
var masterKey = new byte[32];
RandomNumberGenerator.Fill(masterKey);

var encryption = new AesGcmEncryptionProvider(masterKey);
var index = new PersistentIndexProvider(FileSystem.AppDataDirectory, encryption);

var store = new FileOfflineStore(
    FileSystem.AppDataDirectory,
    encryption,
    index);

// Save
await store.SaveAsync("lesson-2025-10-27", new LessonRecord {
    Subject = "Science",
    Description = "Observed seagulls at the beach"
});

// Search
var results = await store.FindAsync<LessonRecord>("seagulls");

Extensibility

The core components (storage, indexing, and encryption) are all replaceable. You can implement IOfflineStore, IIndexProvider, or IEncryptionProvider to extend behaviour.

Examples:

  • Replace AES-GCM with a hardware-backed key provider
  • Implement a custom indexer for vector search
  • Store large attachments in a separate encrypted volume

See Architecture for extension points and examples.

Conceptual Architecture

/AppData/
 ├── records/
 │    ├── {id}.dat        # Encrypted JSON
 │    ├── {id}.meta       # Encrypted metadata
 ├── attachments/
 │    ├── {id}-{filename}.bin
 ├── index/
 │    └── search.idx      # Encrypted inverted index
 └── summary/
      └── {year}.sum      # Encrypted summaries

Learn Mode

Topic Description
docs/data-organization.md How to structure your data for speed and maintainability
docs/performance.md Full benchmark data and comparisons
docs/performance-principles.md Why the design scales so well
docs/architecture.md Encryption, atomic writes, and extensibility
docs/api-reference.md Interfaces, extension points, and contracts
docs/use-cases.md Examples of real-world usage patterns
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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 was computed.  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.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.7 144 2/3/2026
1.0.6 285 10/30/2025
1.0.5 288 10/30/2025
1.0.4 290 10/29/2025
1.0.3 277 10/29/2025
1.0.2 267 10/29/2025
1.0.1 280 10/29/2025
1.0.0 270 10/28/2025