Skip to content

Latest commit

History

446 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

⚡ BLite

High-Performance BSON Database Engine for .NET

NuGetNuGet DownloadsBuy Me a CoffeeBuild StatusLicensePlatformStatus

BLite is an embedded, ACID-compliant, document-oriented database built from scratch for maximum performance and zero allocation. It leverages modern .NET features like Span<T>, Memory<T>, and Source Generators to eliminate runtime overhead.

Compatibility: Targets net10.0 and netstandard2.1 — works with .NET 5+, Unity, Xamarin, MAUI, and any netstandard2.1-compatible runtime.

Note

v5.0.0 is now available! BLite 5 introduces transparent AES-256-GCM encryption at rest, a formal audit trail (IBLiteAuditSink, BLiteMetrics, OpenTelemetry), full GDPR compliance primitives ([PersonalData], subject export, CDC field masking, GdprMode.Strict), multi-process WAL (.wal-shm), and generalized retention policies. See the sections below for details, or install now:

dotnet add package BLite --version 5.0.0

Important

v4.0.0 — Breaking Change: Async-Only CRUD API Synchronous data methods (Insert, Update, Delete, FindById, FindAll, Find, InsertBulk, UpdateBulk, DeleteBulk, Count) have been removed from DocumentCollection<TId, T> and DynamicCollection. Only *Async variants are available. Update all call sites to use await InsertAsync(...), await FindByIdAsync(...), etc., and SaveChangesAsync() as the commit path.

🚀 Why BLite?

Most embedded databases for .NET are either wrappers around C libraries (SQLite, RocksDB) or legacy C# codebases burdened by heavy GC pressure.

BLite is different:

  • Zero Allocation: I/O and interaction paths use Span<byte> and stackalloc. No heap allocations for reads/writes.
  • Type-Safe: No reflection. All serialization code is generated at compile-time.
  • Developer Experience: Full LINQ provider (IQueryable) that feels like Entity Framework but runs on bare metal.
  • Reliable: Full ACID transactions with Write-Ahead Logging (WAL) and Snapshot Isolation.

✨ Key Features

🚄 Zero-Allocation Architecture

  • Span-based I/O: The entire pipeline, from disk to user objects, utilizes Span<T> to avoid copying memory.
  • Memory-Mapped Files: OS-level paging and caching for blazing fast access.

🧠 Powerful Query Engine (LINQ)

Write queries naturally using LINQ. The engine automatically translates them to optimized B-Tree lookups.

// Automatic Index Usagevarusers=collection.AsQueryable().Where(x =>x.Age>25&&x.Name.StartsWith("A")).OrderBy(x =>x.Age).Take(10).AsEnumerable();// Executed efficiently on the engine
  • Optimized: Uses B-Tree indexes for =, >, <, Between, and StartsWith.
  • Hybrid Execution: Combines storage-level optimization with in-memory LINQ to Objects.
  • Advanced Features: Full support for GroupBy, Join, Select (including anonymous types), and Aggregations (Count, Sum, Min, Max, Average).

🔍 Advanced Indexing

  • B-Tree Indexes: Logarithmic time complexity for lookups.
  • Composite Indexes: Support for multi-column keys.
  • Nested Property Indexes: Index on embedded sub-object fields using lambda expressions (x => x.Address.City) for typed collections, or dot-notation strings ("address.city") for schema-less collections. Intermediate null values are safely skipped.
  • Vector Search (HNSW): Fast similarity search for AI embeddings using Hierarchical Navigable Small World algorithm.

🏷️ Secondary Indexes on Nested Properties (Typed Collections)

Configure secondary indexes on embedded sub-object properties using a standard lambda path in OnModelCreating:

protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){// Index on a top-level propertymodelBuilder.Entity<Customer>().HasIndex(x =>x.Email);// Index on a nested property — dot-notation path is inferred automaticallymodelBuilder.Entity<Customer>().HasIndex(x =>x.Address.City);// Deeper nesting is supported toomodelBuilder.Entity<Order>().HasIndex(x =>x.Shipping.Address.PostalCode);}// The index is then used automatically by the LINQ enginevaritalianCustomers=db.Customers.AsQueryable().Where(c =>c.Address.City=="Milan").ToList();// → B-Tree index hit on "address.city"

Note: If an intermediate property is null (e.g. Address is null) the record is simply skipped by the indexer — no exception is thrown.

🔎 BLQL — BLite Query Language

MQL-inspired query language for schema-less (DynamicCollection) scenarios. Filter, sort, project, and page BsonDocument results using either a fluent C# API or JSON strings — no compile-time types required.

// JSON string entry-point (MQL-style)vardocs=col.Query("""{ "status": "active", "age": { "$gt": 18 } }""").Sort("""{ "name": 1 }""").Skip(0).Take(20).ToList();// Fluent C# APIvardocs=col.Query().Filter(BlqlFilter.And(BlqlFilter.Eq("status","active"),BlqlFilter.Gt("age",18))).OrderBy("name").Project(BlqlProjection.Include("name","email")).ToList();
  • Comparison: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $type, $regex.
  • String: $startsWith, $endsWith, $contains — ordinal comparison, no regex interpretation.
  • Array: $elemMatch (scalar and document arrays), $size, $all.
  • Arithmetic: $mod — modulo check with zero-divisor protection at parse time.
  • Logical: $and, $or, $nor, $not (top-level) and $not (field-level condition negation). Implicit AND for multiple top-level fields.
  • Geospatial: $geoWithin (bounding box) and $geoNear (Haversine radius in km).
  • Vector: $nearVector — index-accelerated ANN search via HNSW.
  • Security-hardened: Unknown $ operators throw FormatException. Every operator validates its JSON type. $mod divisor=0 rejected at parse time. ReDoS protected via NonBacktracking. 252 dedicated security tests.

🤖 AI-Ready Vector Search

BLite natively supports vector embeddings and fast similarity search.

// 1. Configure vector index on float[] propertymodelBuilder.Entity<VectorItem>().HasVectorIndex(x =>x.Embedding,dimensions:1536,metric:VectorMetric.Cosine);// 2. Perform fast similarity searchvarresults=db.Items.AsQueryable().VectorSearch(x =>x.Embedding,queryVector,k:5).ToList();

v3.6.2 HNSW correctness: the HNSW implementation received a full correctness pass — AllocateNode overflow, neighbor link integrity (LinkPageChain), SelectNeighbors heuristic (keep closest, not farthest), random level distribution (mL = 1/ln(M)), and persistence across database close/reopen are all fixed.

🛠️ Vector Source Configuration (RAG Optimization)

For sophisticated RAG (Retrieval-Augmented Generation) scenarios, BLite allows you to define a Vector Source Configuration directly on the collection metadata. This configuration specifies which BSON fields should be used to build the input text for your embedding model.

// Define which fields to include in the normalized text for embeddingvarconfig=newVectorSourceConfig().Add("title",weight:2.0)// Boost important fields.Add("content",weight:1.0).Add("tags",weight:0.5);// Set it on a collectionengine.SetVectorSource("documents",config);// Use TextNormalizer to build the text from any BsonDocumentstringtext=TextNormalizer.BuildEmbeddingText(doc,config);// -> "TITLE [Boost: 2.0] ... CONTENT ... TAGS [Boost: 0.5] ..."

🌍 High-Performance Geospatial Indexing

BLite features a built-in R-Tree implementation for lightning-fast proximity and bounding box searches.

  • Zero-Allocation: Uses coordinate tuples (double, double) and Span-based BSON arrays.
  • LINQ Integrated: Search naturally using .Near() and .Within().
// 1. Configure spatial index (uses R-Tree internally)modelBuilder.Entity<Store>().HasSpatialIndex(x =>x.Location);// 2. Proximity Search (Find stores within 5km)varstores=db.Stores.AsQueryable().Where(s =>s.Location.Near((45.4642,9.1899),5.0)).ToList();// 3. Bounding Box Searchvararea=db.Stores.AsQueryable().Where(s =>s.Location.Within((45.0,9.0),(46.0,10.0))).ToList();

🆔 Custom ID Converters (ValueObjects)

Native support for custom primary key types using ValueConverter<TModel, TProvider>. Configure them easily via the Fluent API.

// 1. Define your ValueObject and ConverterpublicrecordOrderId(stringValue);publicclassOrderIdConverter:ValueConverter<OrderId,string>{ ...}// 2. Configure in OnModelCreatingmodelBuilder.Entity<Order>().Property(x =>x.Id).HasConversion<OrderIdConverter>();// 3. Use it naturallyvarorder=collection.FindById(newOrderId("ORD-123"));

🔒 Encryption at Rest (v5.0.0)

Transparent AES-256-GCM page-level encryption. Pages are encrypted before writing to disk and decrypted after reading — the rest of the engine (LINQ, CDC, WAL, transactions) works unchanged.

Required namespace: using BLite.Core.Encryption;

Simple passphrase mode (recommended)

Pass a passphrase string to CryptoOptions. BLite automatically generates a unique random salt per file, derives the AES-256 key via PBKDF2-SHA256 (600 000 iterations), and stores the salt in the 64-byte encrypted file header — no separate salt file needed.

usingBLite.Core;usingBLite.Core.Encryption;// Create or open an encrypted single-file databasevarcrypto=newCryptoOptions("my-secret-passphrase");usingvarengine=newBLiteEngine("secure.blite",crypto);varcol=engine.GetOrCreateCollection("users");

Using DocumentDbContext with encryption

usingBLite.Core;usingBLite.Core.Encryption;publicpartialclassAppDbContext:DocumentDbContext{publicDocumentCollection<int,User>Users{get;set;}=null!;// Encrypted single-file databasepublicAppDbContext(stringpath,CryptoOptionscrypto):base(path,crypto){}}// Create or open the encrypted databasevarcrypto=newCryptoOptions("my-secret-passphrase");awaitusingvardb=newAppDbContext("secure.blite",crypto);

Advanced: manage salt externally, derive key manually

For scenarios where you need to control the salt lifecycle (e.g. storing it separately from the database file):

usingBLite.Core;usingBLite.Core.Encryption;// One-time setup: generate a salt and persist itbyte[]salt=CryptoOptions.GenerateSalt();// returns byte[32]File.WriteAllBytes("secure.salt",salt);// Subsequent opens: read the salt, derive the key, open the databasebyte[]storedSalt=File.ReadAllBytes("secure.salt");byte[]key=CryptoOptions.DeriveKey("my-passphrase",storedSalt);// PBKDF2-SHA256, 32 bytesusingvarengine=newBLiteEngine("secure.blite",CryptoOptions.FromMasterKey(key));

Master-key mode (KMS / HSM friendly)

Supply a pre-existing 32-byte key (e.g. from Azure Key Vault or AWS KMS). BLite uses HKDF-SHA256 to derive a unique subkey per physical file in multi-file mode.

byte[]masterKey=awaitmyKeyVault.GetKeyAsync("blite-prod");usingvardb=newAppDb("users.db",CryptoOptions.FromMasterKey(masterKey));

Migration and key rotation

// Offline migration (plaintext ↔ encrypted)awaitBLiteEngine.MigrateToEncryptedAsync("old.db","new-encrypted.db",keyProvider);awaitBLiteEngine.MigrateToPlaintextAsync("old-encrypted.db","new-plain.db",keyProvider);// Online key rotationawaitengine.RotateEncryptionKeyAsync(newKeyProvider,newKeyRotationOptions{EmitAuditEvent=true});

In multi-file mode, EncryptionCoordinator derives a unique 256-bit HKDF-SHA256 subkey per file — stealing one file does not expose the others. The default NullCryptoProvider is a transparent no-op with zero overhead for code that does not opt in.

🪵 Audit Trail & Performance Monitoring (v5.0.0)

Formal per-operation callbacks with timing, caller identity, and optional OpenTelemetry integration.

db.ConfigureAudit(newBLiteAuditOptions{Sink=newMyAuditSink(),// IBLiteAuditSink implEnableMetrics=true,// populate db.AuditMetricsSlowOperationThreshold=TimeSpan.FromMilliseconds(50),EnableDiagnosticSource=true,// emit Activity spans (OTel)});// In-memory counters (~10–20 ns overhead, Interlocked)BLiteMetricsm=db.AuditMetrics!;Console.WriteLine($"Cache hit rate: {m.CacheHitRate:P1} | Avg query: {m.AvgQueryMs:F2} ms");// OpenTelemetry — register the source onceservices.AddOpenTelemetry().WithTracing(b =>b.AddSource("BLite.Core"));

Implement IBLiteAuditSink with default no-op methods — override only the events you need (OnInsert, OnQuery, OnCommit, OnSlowOperation). Inject a custom IAuditContextProvider to attach caller identity to every event.

🛡️ GDPR Compliance Primitives (v5.0.0)

Annotate personal data at compile time; the Source Generator emits zero-reflection metadata.

publicclassCustomer{publicObjectIdId{get;set;}[PersonalData]// Art. 4(1)publicstringEmail{get;set;}="";[PersonalData(Sensitivity=DataSensitivity.Special)]// Art. 9publicstring?MedicalNotes{get;set;}[PersonalData(IsTimestamp=true)]publicDateTimeCreatedAt{get;set;}}// Art. 15/20 — subject export (JSON, CSV, or BSON)varreport=awaitdb.ExportSubjectDataAsync(newSubjectQuery{FieldName="email",FieldValue=BsonValue.FromString("alice@example.com"),Format=SubjectExportFormat.Json,});awaitreport.WriteToFileAsync("alice-export.json");// Art. 30 — database inspectionDatabaseInspectionReportinspect=db.InspectDatabase();// → IsEncrypted, IsAuditEnabled, per-collection PersonalDataFields, RetentionPolicy

CDC field masking (WP2): when CapturePayload = true, [PersonalData] fields are automatically masked before dispatch. Opt in to clear data with RevealPersonalData = true. Use IncludeOnlyFields/ExcludeFields for fine-grained control.

GdprMode.Strict (Art. 25): throws at engine-open time if encryption is absent, warns if no audit sink or retention policy is configured.

⏳ Generalized Retention Policy (v5.0.0)

Retention policies — previously only for TimeSeries — now apply to any typed collection:

modelBuilder.Entity<Order>().HasRetentionPolicy(timestampSelector: o =>o.PlacedAt,maxAge:TimeSpan.FromDays(7*365),triggers:RetentionTrigger.OnInsert|RetentionTrigger.Scheduled);modelBuilder.Entity<AuditLogEntry>().HasRetentionPolicy(timestampSelector: e =>e.CreatedAt,maxDocumentCount:10_000);

🗑️ Secure Erase & VACUUM (v5.0.0)

Zero the storage slot on delete for GDPR Art. 17 (Right to Erasure):

modelBuilder.Entity<Customer>().HasSecureErase(true);// Compact and reclaim free spaceawaitdb.VacuumAsync();

🔄 Multi-Process WAL (v5.0.0)

A .wal-shm sidecar enables N-reader / 1-writer access across OS processes (opt-in):

varconfig=newPageFileConfig{AllowMultiProcessAccess=true};usingvardb=newAppDb("shared.db",config);// open from multiple processes

�📡 Change Data Capture (CDC)

Real-time event streaming for database changes with transactional consistency.

  • Zero-Allocation: Events are only captured when watchers exist; no overhead when disabled.
  • Transactional: Events fire only after successful commit, never on rollback.
  • Scalable: Uses Channel-per-subscriber architecture to support thousands of concurrent listeners.
// Watch for changes in a collectionusingvarsubscription=db.People.Watch(capturePayload:true).Subscribe(e =>{Console.WriteLine($"{e.Type}: {e.DocumentId}");if(e.Entity!=null)Console.WriteLine($" Name: {e.Entity.Name}");});// Perform operations - events fire after commitawaitdb.People.InsertAsync(newPerson{Id=1,Name="Alice"});// v3.6.0 — DynamicCollection.Watch() is also supportedusingvardynSub=engine.GetOrCreateCollection("orders").Watch().Subscribe(e =>Console.WriteLine($"{e.Type}: {e.DocumentId}"));

🛡️ Transactions & ACID

  • Atomic: Multi-document transactions.
  • Durable: WAL ensures data safety even in power loss.
  • Isolated: Snapshot isolation allowing concurrent readers and writers.
  • Thread-Safe: Protected with SemaphoreSlim to prevent race conditions in concurrent scenarios.
  • Async-Only: All CRUD operations on DocumentCollection<TId, T> and DynamicCollection are exclusively async/await — no blocking synchronous methods. Eliminates accidental blocking calls on thread-pool threads.
  • Implicit Transactions: Use SaveChangesAsync() for automatic transaction management.

� Native TimeSeries

A dedicated PageType.TimeSeries — an append-only page format optimised for high-throughput time-ordered data. Introduced natively in 1.12; the typed DocumentDbContext fluent API (HasTimeSeries) was added in 3.3.0.

  • No background threads: pruning fires transparently on insert (every 1 000 docs or 5 min).
  • Page-level granularity: entire expired pages are freed in a single pass — O(freed pages), not O(all documents).
  • Transparent reads: FindAll(), BLQL queries, and B-Tree lookups work unchanged.
// Enable on any DynamicCollectionvarsensors=engine.GetOrCreateCollection("sensors");sensors.SetTimeSeries("timestamp",TimeSpan.FromDays(7));engine.Commit();// Insert as normal — routing to TS pages is automaticvardoc=sensors.CreateDocument(["deviceId","temperature","timestamp"],
b =>b.Set("deviceId","sensor-42").Set("temperature",23.5).Set("timestamp",DateTime.UtcNow));awaitsensors.InsertAsync(doc);// Force prune immediately (useful in tests)sensors.ForcePrune();

Typed API (DocumentDbContext — added in 3.3.0)

Configure a typed collection as TimeSeries in OnModelCreating using HasTimeSeries:

protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<SensorReading>().ToCollection("sensor_readings").HasTimeSeries(r =>r.Timestamp,retention:TimeSpan.FromDays(7));}// Insert as normal — routing to TS pages is automaticawaitdb.SensorReadings.InsertAsync(newSensorReading{SensorId="sensor-42",Value=23.5,Timestamp=DateTime.UtcNow});awaitdb.SaveChangesAsync();// Force prune (useful in tests / maintenance)db.SensorReadings.ForcePrune();

�🔄 Hot Backup

BLite supports hot backups of live databases without blocking readers. The engine uses a combination of the commit lock and WAL checkpointing to ensure the backup is a fully consistent, standalone database file.

// 1. Embedded mode (DocumentDbContext)awaitdb.BackupAsync("backups/mydb-2026-02-25.blite",cancellationToken);// 2. Schema-less mode (BLiteEngine)awaitengine.BackupAsync("backups/mydb-backup.blite");

⚡ Async Read Operations

All read paths have a true async counterpart — cancellation is propagated all the way down to OS-level RandomAccess.ReadAsync (IOCP on Windows).

// FindById — async primary-key lookup via B-Treevarorder=awaitdb.Orders.FindByIdAsync(id,ct);// FindAll — async streaming (IAsyncEnumerable)awaitforeach(varorderindb.Orders.FindAllAsync(ct))Process(order);// FindAsync — async predicate scan (IAsyncEnumerable)awaitforeach(varorderindb.Orders.FindAsync(o =>o.Status=="shipped",ct))Process(order);// LINQ — full async materialisationvarshipped=awaitdb.Orders.AsQueryable().Where(o =>o.Status=="shipped").ToListAsync(ct);// Async aggregatesintcount=awaitdb.Orders.AsQueryable().CountAsync(ct);boolany=awaitdb.Orders.AsQueryable().AnyAsync(o =>o.Total>500,ct);boolall=awaitdb.Orders.AsQueryable().AllAsync(o =>o.Currency=="EUR",ct);// First/Single helpersvarfirst=awaitdb.Orders.AsQueryable().FirstOrDefaultAsync(o =>o.Status=="pending",ct);varsingle=awaitdb.Orders.AsQueryable().SingleOrDefaultAsync(o =>o.Id==id,ct);// Materialise to arrayvararr=awaitdb.Orders.AsQueryable().ToArrayAsync(ct);// SaveChanges is also asyncawaitdb.SaveChangesAsync(ct);

Available async methods on DocumentCollection<TId, T> (all CRUD operations are async-only since v4.0.0):

MethodDescription
FindByIdAsync(id, ct)Primary-key lookup via B-Tree; returns ValueTask<T?>
FindAllAsync(ct)Full collection streaming; returns IAsyncEnumerable<T>
FindAsync(predicate, ct)Async predicate scan; returns IAsyncEnumerable<T>
AsQueryable().ToListAsync(ct)LINQ pipeline materialized as Task<List<T>>
AsQueryable().ToArrayAsync(ct)LINQ pipeline materialized as Task<T[]>
AsQueryable().FirstOrDefaultAsync(ct)First match or null
AsQueryable().SingleOrDefaultAsync(ct)Single match or null; throws on duplicates
AsQueryable().CountAsync(ct)Element count
AsQueryable().AnyAsync(predicate, ct)Short-circuits on first match
AsQueryable().AllAsync(predicate, ct)Returns false on first non-match

🔌 Intelligent Source Generation

  • Zero Reflection: Mappers are generated at compile-time for zero overhead.
  • Nested Objects & Collections: Full support for complex graphs, deep nesting, and ref struct handling.
  • Robust Serialization: Correctly handles nested objects, collections, and complex type hierarchies.
  • Lowercase Policy: BSON keys are automatically persisted as lowercase for consistency.
  • Custom Overrides: Use [BsonProperty] or [JsonPropertyName] for manual field naming.

✅ Supported Scenarios

The source generator handles a wide range of modern C# patterns:

FeatureSupportDescription
Property InheritanceProperties from base classes are automatically included in serialization
Private SettersProperties with private set are correctly deserialized using Expression Trees
Init-Only SettersProperties with init are supported via runtime compilation
Private ConstructorsDeserialization works even without parameterless public constructor
Advanced CollectionsIEnumerable<T>, ICollection<T>, IList<T>, HashSet<T>, and more
Nullable Value TypesObjectId?, int?, DateTime? are correctly serialized/deserialized
Nullable CollectionsList<T>?, string? with proper null handling
Unlimited NestingDeeply nested object graphs with circular reference protection
Self-ReferencingEntities can reference themselves (e.g., Manager property in Employee). Schema generation is recursion-safe — cycles are detected and terminated automatically
N-N RelationshipsCollections of ObjectIds for efficient document referencing

❌ Limitations & Design Choices

ScenarioStatusReason
Computed Properties⚠️ ExcludedGetter-only properties without backing fields are intentionally skipped (e.g., FullName => $"{First} {Last}")
Constructor Logic⚠️ BypassedDeserialization uses FormatterServices.GetUninitializedObject() to avoid constructor execution
Constructor Validation⚠️ Not ExecutedValidation logic in constructors won't run during deserialization - use Data Annotations instead

💡 Best Practice: For relationships between entities, prefer referencing (storing ObjectIds) over embedding (full nested objects) to avoid data duplication and maintain consistency. See tests in CircularReferenceTests.cs for implementation patterns.

🏷️ Supported Attributes

BLite supports standard .NET Data Annotations for mapping and validation:

AttributeCategoryDescription
[Table("name")]MappingSets the collection name. Supports Schema="s" for s.name grouping.
[Column("name")]MappingMaps property to a specific BSON field name.
[Column(TypeName="...")]MappingHandles special types (e.g., geopoint for coordinate tuples).
[Key]IdentityExplicitly marks the primary key (maps to _id).
[NotMapped]MappingExcludes property from BSON serialization.
[Required]ValidationEnsures string is not null/empty or nullable type is not null.
[StringLength(max)]ValidationValidates string length (supports MinimumLength).
[MaxLength(n)]ValidationValidates maximum string length.
[MinLength(n)]ValidationValidates minimum string length.
[Range(min, max)]ValidationValidates numeric values stay within the specified range.

Important

Validation attributes ([Required], [Range], etc.) throw a System.ComponentModel.DataAnnotations.ValidationException during serialization if rules are violated.

IDocumentCollection<TId, T> Abstraction (v3.5.0)

Typed collections implement the IDocumentCollection<TId, T> interface — a clean contract covering async CRUD, bulk operations, and LINQ. This makes constructor injection and unit-test mocking straightforward without binding to the concrete DocumentCollection class.

// Inject or mock via the interfacepublicclassOrderService{privatereadonlyIDocumentCollection<ObjectId,Order>_orders;publicOrderService(IDocumentCollection<ObjectId,Order>orders)=>_orders=orders;publicasyncTaskPlaceAsync(Ordero){await_orders.InsertAsync(o);}}

�🗝️ Embedded Key-Value Store

BLite 3.2.0 ships a persistent key-value store co-located in the same database file — no extra process, no extra file. Access it via IBLiteKvStore on any BLiteEngine or DocumentDbContext.

  • Raw bytes: values are byte[] / ReadOnlySpan<byte> — serialize however you like.
  • Optional TTL: per-entry expiry with lazy purge (PurgeExpired()) or auto-purge on open.
  • Prefix scan: enumerate all keys with a given prefix.
  • Atomic batches: set + delete multiple keys under a single lock acquisition.
usingvarengine=newBLiteEngine("data.db");IBLiteKvStorekv=engine.KvStore;// Write (optional TTL)kv.Set("session:abc",Encoding.UTF8.GetBytes("payload"),TimeSpan.FromHours(1));// Readbyte[]?value=kv.Get("session:abc");// Exists / Deleteboolexists=kv.Exists("session:abc");kv.Delete("session:abc");// Refresh expiry without rewriting valuekv.Refresh("session:abc",TimeSpan.FromHours(2));// Prefix scanIEnumerable<string>sessionKeys=kv.ScanKeys("session:");// Atomic batch (one lock)kv.Batch().Set("k1",data1).Set("k2",data2,TimeSpan.FromMinutes(30)).Delete("k3").Execute();// Options (passed to BLiteEngine / DocumentDbContext constructor)varoptions=newBLiteKvOptions{DefaultTtl=TimeSpan.FromDays(1),PurgeExpiredOnOpen=true};usingvardb=newMyDbContext("app.db",options);IBLiteKvStorekv=db.KvStore;

🚀 BLite.Caching — IDistributedCache

BLite.Caching wraps the embedded KV store as a fully compliant IDistributedCache — drop it in anywhere you'd use Redis or SQL Server cache, with zero external dependencies.

dotnet add package BLite.Caching
// ASP.NET Core DI registrationbuilder.Services.AddBLiteDistributedCache("cache.db");// Optionally with KV optionsbuilder.Services.AddBLiteDistributedCache("cache.db",newBLiteKvOptions{DefaultTtl=TimeSpan.FromMinutes(30),PurgeExpiredOnOpen=true});

The package also exposes IBLiteCache — a typed superset of IDistributedCache:

// Typed helpers (uses System.Text.Json internally)awaitcache.SetAsync("user:42",myUser,newDistributedCacheEntryOptions{SlidingExpiration=TimeSpan.FromMinutes(20)});User?user=awaitcache.GetAsync<User>("user:42");// GetOrSet — built-in thundering-herd protection (per-key SemaphoreSlim)Useruser=awaitcache.GetOrSetAsync("user:42",factory:async ct =>awaitdb.LoadUserAsync(42,ct),options:newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow=TimeSpan.FromHours(1)});

📚 Documentation

📖 Official Documentation → blitedb.com/docs/getting-started

For in-depth technical details, see the complete specification documents:

  • RFC.md - Full architectural specification covering storage engine, indexing, transactions, WAL protocol, and query processing
  • C-BSON.md - Detailed wire format specification for BLite's Compressed BSON format, including hex dumps and performance analysis

📦 Quick Start

1. Installation

dotnet add package BLite

2. Basic Usage

// 1. Define your EntitiespublicclassUser{publicObjectIdId{get;set;}publicstringName{get;set;}}// 2. Define your DbContext (Source Generator will produce InitializeCollections)publicpartialclassMyDbContext:DocumentDbContext{publicDocumentCollection<ObjectId,User>Users{get;set;}=null!;publicMyDbContext(stringpath):base(path){InitializeCollections();}}// 3. Use with Async Implicit Transactions (Recommended)usingvardb=newMyDbContext("mydb.db");// Operations are tracked automaticallyawaitdb.Users.InsertAsync(newUser{Name="Alice"});awaitdb.Users.InsertAsync(newUser{Name="Bob"});// Commit all changes at onceawaitdb.SaveChangesAsync();// 4. Query naturally with LINQ (async)varresults=awaitdb.Users.AsQueryable().Where(u =>u.Name.StartsWith("A")).ToListAsync();// 5. Or use explicit transactions for fine-grained controlusing(vartxn=db.BeginTransaction()){awaitdb.Users.InsertAsync(newUser{Name="Charlie"});awaittxn.CommitAsync();// Explicit async commit}

� Schema-less API (BLiteEngine / DynamicCollection)

When compile-time types are not available — server-side query processing, scripting, migrations, or interop scenarios — BLite exposes a fully schema-less BSON API via BLiteEngine and DynamicCollection.

Both paths share the same kernel: StorageEngine, B-Tree, WAL, Vector / Spatial indexes.

Entry Point

usingvarengine=newBLiteEngine("data.db");// Open (or create) a schema-less collectionvarorders=engine.GetOrCreateCollection("orders",BsonIdType.ObjectId);// List all collectionsIReadOnlyList<string>names=engine.ListCollections();// Drop a collectionengine.DropCollection("orders");

Insert

// Build a BsonDocument using the engine's field-name dictionaryvardoc=orders.CreateDocument(["status","total","currency"],
b =>b.Set("status","pending").Set("total",199.99).Set("currency","EUR"));BsonIdid=awaitorders.InsertAsync(doc,ct);// Bulk insert (single transaction)List<BsonId>ids=awaitorders.InsertBulkAsync([doc1,doc2,doc3],ct);

Read

// Primary-key lookupBsonDocument?doc=awaitorders.FindByIdAsync(id,ct);// Full scanawaitforeach(vardinorders.FindAllAsync(ct)){ ...}// Predicate filterawaitforeach(vardinorders.FindAsync(d =>d.GetString("status")=="pending",ct)){ ...}// Zero-copy predicate scan (BsonSpanReader — no heap allocation per document)varpending=orders.Scan(reader =>{// Read "status" field directly from the BSON bytesif(reader.TryReadString("status",outvarstatus))returnstatus=="shipped";returnfalse;});// B-Tree range query on a secondary indexvarrecent=orders.QueryIndex("idx_placed_at",minDate,maxDate);// Vector similarity searchvarsimilar=orders.VectorSearch("idx_embedding",queryVector,k:10);// Geospatial proximity / bounding boxvarnearby=orders.Near("idx_location",(45.46,9.18),radiusKm:5.0);varinArea=orders.Within("idx_location",(45.0,9.0),(46.0,10.0));// Countinttotal=awaitorders.CountAsync(ct);

Update & Delete

boolupdated=awaitorders.UpdateAsync(id,newDoc,ct);booldeleted=awaitorders.DeleteAsync(id,ct);// Bulk (single transaction)intupdatedCount=awaitorders.UpdateBulkAsync([(id1,doc1),(id2,doc2)],ct);intdeletedCount=awaitorders.DeleteBulkAsync([id1,id2,id3],ct);// or via engine shortcuts (async)awaitengine.UpdateAsync("orders",id,newDoc,ct);awaitengine.DeleteAsync("orders",id,ct);intu=awaitengine.UpdateBulkAsync("orders",[(id1,d1),(id2,d2)],ct);intd=awaitengine.DeleteBulkAsync("orders",[id1,id2],ct);

Index Management

// B-Tree secondary indexorders.CreateIndex("status");// default name = "idx_status"orders.CreateIndex("placed_at",unique:false);// Unique indexorders.CreateIndex("order_number",unique:true);// Nested path index (dot-notation) — indexes a field inside an embedded documentorders.CreateIndex("shipping.city");// indexes doc["shipping"]["city"]orders.CreateIndex("customer.address.zip");// arbitrary depth; null intermediates skipped// Vector index (HNSW) — supports nested paths tooorders.CreateVectorIndex("embedding",dimensions:1536,metric:VectorMetric.Cosine);orders.CreateVectorIndex("meta.embedding",dimensions:768,metric:VectorMetric.Cosine);// Spatial index (R-Tree) — supports nested paths tooorders.CreateSpatialIndex("location");orders.CreateSpatialIndex("store.location");// IntrospectIReadOnlyList<string>indexes=orders.ListIndexes();// Droporders.DropIndex("idx_status");

Reading BsonDocument fields

BsonDocument?doc=orders.FindById(id);if(docis not null){stringstatus=doc.GetString("status");doubletotal=doc.GetDouble("total");BsonIddocId=doc.Id;}

When to use which API

DocumentDbContextBLiteEngine
Type safety✅ Compile-time❌ Runtime BsonDocument
Source generators✅ Zero reflection
LINQ✅ Full IQueryable
BLQL✅ JSON string queries
Schema-less / dynamic
Server / scripting mode
Performance✅ Max (generated mappers)✅ Near-identical (same kernel)
Shared storage✅ Same file

🔌 BLiteSession — Per-Connection Isolation (v3.8.0)

When a single BLiteEngine is shared across multiple concurrent clients (e.g. inside a custom server layer), BLiteSession provides per-connection isolated transaction contexts. Each session carries its own transaction state so independent callers cannot interfere with each other.

Open a session with engine.OpenSession(). Disposing the session automatically rolls back any uncommitted transaction.

usingvarengine=newBLiteEngine("data.db");// One session per connected client / per requestusingvarsession=engine.OpenSession();// Begin an explicit transaction scoped to this sessionusingvartxn=session.BeginTransaction();try{awaitsession.InsertAsync("orders",orderDoc,ct);awaitsession.InsertAsync("invoices",invoiceDoc,ct);awaitsession.CommitAsync(ct);}catch{session.Rollback();// or disposed automaticallythrow;}// Convenience CRUD (auto-commit each call)BsonIdid=awaitsession.InsertAsync("users",userDoc,ct);BsonDocument?doc=awaitsession.FindByIdAsync("users",id,ct);// Access collections scoped to this sessionvarcol=session.GetOrCreateCollection("events");col.Insert(eventDoc);

BLiteSession API (selected): BeginTransaction(), CommitAsync(), Rollback(), GetOrCreateCollection(name), GetCollection(name), InsertAsync/InsertBulkAsync, FindByIdAsync/FindAllAsync/FindAsync, UpdateAsync/UpdateBulkAsync, DeleteAsync/DeleteBulkAsync.


📂 Multi-File Storage Layout (v3.8.0)

BLite 3.8.0 introduces an optional multi-file storage layout designed for server deployments where each database should keep its WAL, indexes and collection data in separate files rather than a single monolithic .db file.

usingBLite.Core.Storage;// Build a server-style config — WAL, index and collection data go to separate filesvarconfig=PageFileConfig.Server("data/mydb.db");// → WAL: data/wal/mydb.wal// → Index file: data/mydb.idx// → Collections: data/collections/mydb/<collection>.colusingvarengine=newBLiteEngine("data/mydb.db",config);

PageFileConfig.Server() accepts an optional base config to control page size:

varconfig=PageFileConfig.Server("data/mydb.db",PageFileConfig.Large);// 32 KB pages

BLiteMigration — Single ↔ Multi-File Migration

BLiteMigration migrates an existing database between layouts without data loss:

// Migrate from single-file to server multi-file layoutBLiteMigration.ToMultiFile(sourcePath:"data/mydb.db",targetConfig:PageFileConfig.Server("data/mydb.db"));// Migrate back to a single fileBLiteMigration.ToSingleFile(sourcePath:"data/mydb.db",sourceConfig:PageFileConfig.Server("data/mydb.db"),targetPath:"export/mydb-single.db");

Both methods preserve documents, KV entries (including TTL expiry times), and index definitions.


🔎 BLQL — BLite Query Language

BLQL is a BLite Query Language for DynamicCollection — the schema-less counterpart of LINQ for DocumentDbContext. Inspired by MQL (MongoDB Query Language), it lets you filter, sort, project, and page BsonDocument results using JSON strings or a fluent C# API, with no compile-time type information required.

Entry Points

usingBLite.Core.Query.Blql;// 1. JSON string filter (MQL-style)vardocs=col.Query("""{ "status": "active", "age": { "$gt": 18 } }""").Sort("""{ "name": 1 }""").Skip(0).Take(20).ToList();// 2. Programmatic filtervardocs=col.Query().Filter(BlqlFilter.Eq("status","active").AndAlso(BlqlFilter.Gt("age",18))).OrderByDescending("createdAt").Project(BlqlProjection.Include("name","email","createdAt")).ToList();

Supported Filter Operators

Comparison & field tests

JSON syntaxC# equivalentDescription
{ "f": value }BlqlFilter.Eq("f", v)Equality
{ "f": { "$ne": v } }BlqlFilter.Ne("f", v)Not equal
{ "f": { "$gt": v } }BlqlFilter.Gt("f", v)Greater than
{ "f": { "$gte": v } }BlqlFilter.Gte("f", v)Greater than or equal
{ "f": { "$lt": v } }BlqlFilter.Lt("f", v)Less than
{ "f": { "$lte": v } }BlqlFilter.Lte("f", v)Less than or equal
{ "f": { "$in": [...] } }BlqlFilter.In("f", ...)Value in set
{ "f": { "$nin": [...] } }BlqlFilter.Nin("f", ...)Value not in set
{ "f": { "$exists": true } }BlqlFilter.Exists("f")Field exists
{ "f": { "$type": 16 } }BlqlFilter.Type("f", BsonType.Int32)BSON type check
{ "f": { "$regex": "^Al" } }BlqlFilter.Regex("f", "^Al")Regex (NonBacktracking)

String operators

JSON syntaxC# equivalentDescription
{ "f": { "$startsWith": "Al" } }BlqlFilter.StartsWith("f", "Al")Prefix match (ordinal)
{ "f": { "$endsWith": ".com" } }BlqlFilter.EndsWith("f", ".com")Suffix match (ordinal)
{ "f": { "$contains": "foo" } }BlqlFilter.Contains("f", "foo")Substring match (ordinal)

Array operators

JSON syntaxC# equivalentDescription
{ "f": { "$elemMatch": { "$gt": 80 } } }BlqlFilter.ElemMatch("f", BlqlFilter.Gt("f", 80))Any element satisfies condition
{ "f": { "$size": 3 } }BlqlFilter.Size("f", 3)Array has exact length
{ "f": { "$all": ["a", "b"] } }BlqlFilter.All("f", ...)Array contains all values

Arithmetic

JSON syntaxC# equivalentDescription
{ "f": { "$mod": [3, 0] } }BlqlFilter.Mod("f", 3, 0)field % divisor == remainder

Logical

JSON syntaxC# equivalentDescription
{ "$and": [...] }BlqlFilter.And(...)Logical AND
{ "$or": [...] }BlqlFilter.Or(...)Logical OR
{ "$nor": [...] }BlqlFilter.Nor(...)Logical NOR
{ "$not": {...} }BlqlFilter.Not(...)Top-level NOT
{ "f": { "$not": { "$gt": 0 } } }BlqlFilter.Not(BlqlFilter.Gt("f", 0))Field-level condition negation

Geospatial

JSON syntaxC# equivalentDescription
{ "loc": { "$geoWithin": { "$box": [[minLon,minLat],[maxLon,maxLat]] } } }BlqlFilter.GeoWithin("loc", minLon, minLat, maxLon, maxLat)Point inside bounding box
{ "loc": { "$geoNear": { "$center": [lon,lat], "$maxDistance": km } } }BlqlFilter.GeoNear("loc", lon, lat, km)Point within radius (Haversine)

Vector search

JSON syntaxC# equivalentDescription
{ "emb": { "$nearVector": { "$vector": [...], "$k": 10, "$metric": "cosine" } } }BlqlFilter.NearVector("emb", vector, k: 10)HNSW ANN similarity search

Multiple top-level fields in one JSON object produce an implicit AND:

{ "status": "active", "age": { "$gt": 18 } }

Sorting

// JSON sort (1 = ascending, -1 = descending)varresults=col.Query(filter).Sort("""{ "lastName": 1, "age": -1 }""")// multi-key sort.ToList();// Fluent sortvarresults=col.Query(filter).OrderBy("lastName").ToList();

Projection

// Include only specified fieldsvarresults=col.Query(filter).Project(BlqlProjection.Include("name","email")).ToList();// Exclude specified fieldsvarresults=col.Query(filter).Project(BlqlProjection.Exclude("password","__internal")).ToList();

Paging & Terminal Methods

varpage=col.Query(filter).OrderBy("createdAt").Skip(20).Take(10)// or .Limit(10).ToList();// Single documentBsonDocument?doc=col.Query(filter).FirstOrDefault();// Aggregatesinttotal=col.Query(filter).Count();boolany=col.Query(filter).Any();boolnone=col.Query(filter).None();// Async streamingawaitforeach(vardocincol.Query(filter).AsAsyncEnumerable(ct))Process(doc);

Security

The JSON parser is hardened against BLQL-injection:

  • Unknown $ operators ($where, $expr, $function, …) → FormatException — never passed through.
  • Every operator validates its JSON type (e.g. $startsWith requires string, $mod requires [divisor, remainder]) → FormatException on mismatch.
  • $mod with divisor 0 is rejected at parse time, preventing DivideByZeroException at evaluation.
  • $regex compiled with RegexOptions.NonBacktracking (ReDoS-safe). String operators ($startsWith, $endsWith, $contains) use ordinal comparison — regex metacharacters are literals.
  • Deeply nested JSON (> 64 levels) is rejected by System.Text.Json before evaluation.
  • 252 security tests covering type-confusion, division-by-zero, deep nesting DoS, large $in/$all array DoS, and vector dimension bombing.

�🗺️ Roadmap & Status

We are actively building the core. Here is where we stand:

  • Core Storage: Paged I/O, WAL, Transactions with thread-safe concurrent access.
  • BSON Engine: Zero-copy Reader/Writer with lowercase policy.
  • Indexing: B-Tree implementation.
  • Vector Search: HNSW implementation for Similarity Search.
  • Geospatial Indexing: Optimized R-Tree with zero-allocation tuple API.
  • Query Engine: Hybrid execution (Index/Scan + LINQ to Objects).
  • Advanced LINQ: GroupBy, Joins, Aggregations, Complex Projections.
  • Async I/O: True async reads and writes — FindByIdAsync, FindAllAsync (IAsyncEnumerable<T>), ToListAsync/ToArrayAsync/CountAsync/AnyAsync/AllAsync/FirstOrDefaultAsync/SingleOrDefaultAsync for LINQ pipelines, SaveChangesAsync. CancellationToken propagates to RandomAccess.ReadAsync (IOCP on Windows).
  • Source Generators: Auto-map POCO/DDD classes with robust nested objects, collections, and ref struct support. Self-referencing types (recursive cycles) are handled safely.
  • Nested Property Indexes: Index on embedded sub-object fields via lambda paths (x => x.Address.City) for typed collections and dot-notation strings ("address.city") for schema-less collections. Null intermediates skipped.
  • Projection Push-down: SELECT (and WHERE+SELECT) lambdas compile to a single-pass raw-BSON reader — T is never instantiated. IBLiteQueryable<T> preserves the async chain across all LINQ operators.
  • BLQL: MQL-inspired query language for DynamicCollection — filter, sort, project and page BsonDocument results from JSON strings or via a fluent C# API. Full operator set: comparison, string ($startsWith, $endsWith, $contains), array ($elemMatch, $size, $all), arithmetic ($mod), logical, geospatial ($geoWithin, $geoNear), and vector ($nearVector). Security-hardened against injection, ReDoS, and division-by-zero.
  • Native TimeSeries: Dedicated PageType.TimeSeries (12) with append-only layout, LastTimestamp header field and automatic retention-based pruning. Triggered on insert — no background threads. SetTimeSeries(), ForcePrune(), IsTimeSeries, GetTimeSeriesConfig() on DynamicCollection. Studio UI: TimeSeries tab, TS badge in sidebar.
  • Page Compaction on Delete: Intra-page space is reclaimed on every delete — live documents are packed toward the top of the page, FreeSpaceEnd is updated and the free-space map is refreshed immediately. Deleted bytes are reusable without a VACUUM pass.
  • Typed TimeSeries (DocumentDbContext): HasTimeSeries(x => x.Timestamp, retention) fluent API on EntityTypeBuilder<T>. Configure a typed DocumentDbContext collection as a TimeSeries source from OnModelCreating. ForcePrune() available on DocumentCollection<TId, T>.
  • Auto ID Fallback for string and Guid (v3.4.0): primary keys of type string are auto-generated as CUID-style strings and Guid keys use Guid.NewGuid() — no manual ID assignment required on insert. Fixed index navigation for number-based indexes.
  • IDocumentCollection<TId, T> Abstraction (v3.5.0): typed collections implement IDocumentCollection<TId, T> — a clean interface covering CRUD, LINQ, async, and bulk operations (Update, UpdateBulk, Delete, DeleteBulk). Enables constructor injection and mocking without coupling to the concrete DocumentCollection class.
  • CDC Watch on DynamicCollection (v3.6.0): DynamicCollection.Watch() adds real-time change streams to the schema-less API — previously only available on typed DocumentCollection<TId, T>.
  • HNSW Vector Search Correctness (v3.6.2): full correctness pass — fixes AllocateNode overflow, neighbor link integrity, SelectNeighbors heuristic, random level distribution (mL = 1/ln(M)), and index persistence across close/reopen. 12 dedicated edge-case tests added.
  • OLAP GroupBy Push-down (v3.7.0): aggregate terminal operators (Count, Sum, Min, Max, Average) are pushed down to the storage layer via BTreeQueryProvider.TryBsonAggregate<TResult>, eliminating unnecessary document materialization for large scans.
  • BLiteSession — Per-Connection Isolation (v3.8.0): BLiteEngine.OpenSession() returns a BLiteSession with its own isolated transaction context. Multiple sessions on the same engine run independent concurrent transactions. Disposing a session rolls back any uncommitted transaction automatically.
  • Multi-File Storage Layout (v3.8.0): PageFileConfig.Server(dbPath) configures separate files for WAL, index data, and per-collection data. BLiteMigration.ToMultiFile() / ToSingleFile() migrate existing databases between layouts, preserving all documents, KV entries (including TTL), and index definitions.
  • Non-Blocking Checkpoints (v3.8.0): checkpoint and metadata writes are deferred to avoid blocking the hot path. PageFile uses Lazy<T> for collection file initialization and ReaderWriterLockSlim to fix concurrent read/write races.
  • Async-Only CRUD API (v4.0.0 — Breaking Change): Synchronous data methods (Insert, Update, Delete, FindById, FindAll, Find, and all bulk variants) have been removed from DocumentCollection<TId, T> and DynamicCollection. All data operations are now exclusively async — this eliminates accidental blocking calls on thread-pool threads, simplifies the internal code paths, and enforces correct async usage throughout the entire stack.
  • Encryption at Rest (v5.0.0): Transparent AES-256-GCM page-level encryption. CryptoOptions (passphrase or master-key), EncryptionCoordinator for per-file HKDF-SHA256 subkeys in multi-file mode, offline migration helpers, and online key rotation. Zero overhead when disabled (NullCryptoProvider).
  • Audit Trail (v5.0.0): IBLiteAuditSink callbacks, in-memory BLiteMetrics (lock-free Interlocked counters), and BLiteDiagnostics.ActivitySource for OpenTelemetry. IAuditContextProvider for caller identity injection. Zero overhead when not configured.
  • GDPR Compliance Primitives (v5.0.0): [PersonalData] annotation, DataSensitivity levels, Subject Export (ExportSubjectDataAsync — Art. 15/20), Database Inspection (InspectDatabase — Art. 30), CDC Field Masking (WP2 — RevealPersonalData, IncludeOnlyFields, ExcludeFields), and GdprMode.Strict (Art. 25 privacy-by-default orchestration).
  • Generalized Retention Policy (v5.0.0): HasRetentionPolicy now applies to any typed collection (not only TimeSeries). Supports maxAge, maxDocumentCount, and configurable RetentionTrigger (on-insert or scheduled).
  • Secure Erase & VACUUM (v5.0.0): HasSecureErase(true) zeros the storage slot on delete for GDPR Art. 17. VacuumAsync() compacts the database and reclaims free space.
  • Multi-Process WAL (v5.0.0): .wal-shm sidecar enables N-reader / 1-writer access across OS processes. Opt in via PageFileConfig.AllowMultiProcessAccess = true.

🔮 Future Vision

1. Advanced Querying & Specialized Indices

  • Graph Traversals:
    • Specialized index for "links" (Document IDs) for $O(1)$ navigation without full scans.

2. CDC & Event Integration

  • BSON Change Stream: "Log Miner" that decodes WAL entries and emits structured events.
  • Internal Dispatcher: Keeps specialized indices updated automatically via CDC.

3. Performance & Optimization

  • Portability: ✅ .netstandard2.1 support shipped in v2.0 — compatible with Unity, MAUI, Xamarin, and .NET 5+.

🤝 Contributing

We welcome contributions! This is a great project to learn about database internals, B-Trees, and high-performance .NET.

How to Build

  1. Clone: git clone https://github.com/mrdevrobot/BLite.git
  2. Build: dotnet build
  3. Test: dotnet test (We have comprehensive tests for Storage, Indexing, and LINQ).

Areas to Contribute

  • Missing LINQ Operators: Help us implement additional IQueryable functions.
  • Benchmarks: Help us prove BLite is faster than the competition.
  • Documentation: Examples, Guides, and Wiki.

� Acknowledgements

Special thanks to the community members who helped improve BLite:

  • @LeoYang06 — For identifying and benchmarking real-world performance bottlenecks, directly driving the zero-allocation read path optimisations in BLite 4.x.

�📝 License

Licensed under the MIT License. Use it freely in personal and commercial projects.

About

Embedded Document Database

Resources

Code of conduct

Contributing

Stars

59 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages