diff --git a/src/BLite.Core/DocumentDbContext.cs b/src/BLite.Core/DocumentDbContext.cs
index c2622d8..04fd579 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 c5f7bbb..cc4e74d 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 e4f48ef..6e28dba 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