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