From 9486ae9d8fa6c7fc6342aa2462d03e9f5c6c574e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 11 May 2026 11:17:42 +0000
Subject: [PATCH 1/2] Initial plan
From 8da38daa0fb773d1c97f22bfd0aa713fd12026b7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 11 May 2026 11:25:20 +0000
Subject: [PATCH 2/2] Add CheckpointAsync and DisposeAsync to DocumentDbContext
Agent-Logs-Url: https://github.com/EntglDb/BLite/sessions/8d7dcaa2-4647-4fe7-85f4-e85efd6ba007
Co-authored-by: mrdevrobot <12503462+mrdevrobot@users.noreply.github.com>
---
src/BLite.Core/DocumentDbContext.cs | 39 +++++++++++++++++++++
src/BLite.Core/IDocumentDbContext.cs | 9 ++++-
tests/BLite.Tests/DbContextTests.cs | 52 +++++++++++++++++++++++++++-
3 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/src/BLite.Core/DocumentDbContext.cs b/src/BLite.Core/DocumentDbContext.cs
index c2622d84..04fd5792 100644
--- a/src/BLite.Core/DocumentDbContext.cs
+++ b/src/BLite.Core/DocumentDbContext.cs
@@ -531,6 +531,18 @@ public void ConfigureAudit(Audit.BLiteAuditOptions options)
///
public Audit.BLiteMetrics? AuditMetrics => _storage?.AuditMetrics;
+ ///
+ /// Forces an immediate checkpoint: merges all committed WAL records into the main data
+ /// file. Call this before disposing the context when you need the database file to be
+ /// fully self-contained (e.g., before copying or shipping the file).
+ ///
+ public Task CheckpointAsync(CancellationToken ct = default)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(GetType().Name);
+ return _storage.CheckpointAsync(ct);
+ }
+
public void Dispose()
{
if (_disposed)
@@ -545,6 +557,33 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Checkpoints the WAL (merging all committed records into the main data file) and then
+ /// releases all resources. Prefer this over when you need the
+ /// database file to be fully self-contained after the context is closed.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ try
+ {
+ if (_storage != null)
+ await _storage.CheckpointAsync(CancellationToken.None);
+ }
+ finally
+ {
+ _storage?.Dispose();
+ _cdc?.Dispose();
+ _ownedCoordinator?.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+ }
+
///
/// Creates a new caller-owned transaction.
/// The caller must pass this transaction to every collection method and
diff --git a/src/BLite.Core/IDocumentDbContext.cs b/src/BLite.Core/IDocumentDbContext.cs
index c5f7bbb0..cc4e74d1 100644
--- a/src/BLite.Core/IDocumentDbContext.cs
+++ b/src/BLite.Core/IDocumentDbContext.cs
@@ -12,7 +12,7 @@ namespace BLite.Core;
///
/// Defines the contract for a document database context.
///
-public interface IDocumentDbContext : IDisposable, ITransactionHolder
+public interface IDocumentDbContext : IDisposable, IAsyncDisposable, ITransactionHolder
{
///
/// Provides access to the embedded Key-Value store that shares the same database file.
@@ -42,6 +42,13 @@ IDocumentCollection CreateSessionCollection(IDocumentMapper
IDocumentCollection Set() where T : class;
+ ///
+ /// Forces an immediate checkpoint: merges all committed WAL records into the main data
+ /// file. Call this before disposing the context when you need the database file to be
+ /// fully self-contained (e.g., before copying or shipping the file).
+ ///
+ Task CheckpointAsync(CancellationToken ct = default);
+
///
/// Begins a new transaction synchronously.
///
diff --git a/tests/BLite.Tests/DbContextTests.cs b/tests/BLite.Tests/DbContextTests.cs
index e4f48ef2..6e28dba9 100644
--- a/tests/BLite.Tests/DbContextTests.cs
+++ b/tests/BLite.Tests/DbContextTests.cs
@@ -1,4 +1,5 @@
-using BLite.Shared;
+using BLite.Core;
+using BLite.Shared;
using System.Security.Cryptography;
namespace BLite.Tests;
@@ -123,6 +124,55 @@ public async Task DbContext_AutoDerivesWalPath()
Assert.True(File.Exists(walPath));
}
+ [Fact]
+ public async Task DbContext_CheckpointAsync_PersistsDataToPageFile()
+ {
+ // Insert data and call CheckpointAsync explicitly via the public API.
+ await using (var db = new TestDbContext(_dbPath))
+ {
+ await db.Users.InsertAsync(new User { Name = "Alice", Age = 30 });
+ // Checkpoint flushes the WAL into the main page file.
+ await db.CheckpointAsync();
+ }
+
+ // Re-open: data must be visible without replaying the WAL from scratch.
+ await using var db2 = new TestDbContext(_dbPath);
+ var all = await db2.Users.FindAllAsync().ToListAsync();
+ Assert.Single(all);
+ Assert.Equal("Alice", all[0].Name);
+ }
+
+ [Fact]
+ public async Task DbContext_DisposeAsync_CheckpointsBeforeClose()
+ {
+ // Insert data and dispose via DisposeAsync (which checkpoints automatically).
+ await using (var db = new TestDbContext(_dbPath))
+ {
+ await db.Users.InsertAsync(new User { Name = "Bob", Age = 25 });
+ }
+
+ // Re-open: data must survive without any explicit checkpoint call.
+ await using var db2 = new TestDbContext(_dbPath);
+ var all = await db2.Users.FindAllAsync().ToListAsync();
+ Assert.Single(all);
+ Assert.Equal("Bob", all[0].Name);
+ }
+
+ [Fact]
+ public async Task DbContext_CheckpointAsync_IsExposedOnInterface()
+ {
+ // Verify the method is accessible through the interface.
+ IDocumentDbContext db = new TestDbContext(_dbPath);
+ await using (db)
+ {
+ await db.Set().InsertAsync(new User { Name = "Carol", Age = 40 });
+ await db.CheckpointAsync();
+ }
+
+ await using var db2 = new TestDbContext(_dbPath);
+ Assert.Single(await db2.Users.FindAllAsync().ToListAsync());
+ }
+
public void Dispose()
{
try