Skip to content

Commit 13cc7d9

Browse files
committed
fix: Use string JSON payloads across oplog and stores
Change Oplog payload representation from JsonElement to string and adapt persistence/network layers accordingly. OplogEntry.Payload is now a string? and callers only deserialize to JsonElement when needed (Document, conflict resolvers). Persisted entities now store normalized JSON strings; BLite/EF stores write payload via GetRawText(). Snapshot, sync handlers, and TCP client now pass JsonData as strings. EntityMappers adds ComputeContentHash(string?) and CreateDocumentMetadata helpers for string content. Tests updated to use string payloads. Also bump BLite packages to 4.3.1.
1 parent 3397b3f commit 13cc7d9

25 files changed

Lines changed: 373 additions & 352 deletions

samples/EntglDb.Demo.Game/EntglDb.Demo.Game.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<PackageReference Include="BLite" Version="4.3.0" />
19-
<PackageReference Include="BLite.SourceGenerators" Version="4.3.0">
18+
<PackageReference Include="BLite" Version="4.3.1" />
19+
<PackageReference Include="BLite.SourceGenerators" Version="4.3.1">
2020
<PrivateAssets>all</PrivateAssets>
2121
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2222
</PackageReference>

samples/EntglDb.Sample.Shared/EntglDb.Sample.Shared.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="BLite.SourceGenerators" Version="4.3.0">
16+
<PackageReference Include="BLite.SourceGenerators" Version="4.3.1">
1717
<PrivateAssets>all</PrivateAssets>
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>

src/EntglDb.Core/Document.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void Merge(OplogEntry oplogEntry, IConflictResolver? resolver = null)
3131
//last wins
3232
if(UpdatedAt <= oplogEntry.Timestamp)
3333
{
34-
Content = oplogEntry.Payload ?? default;
34+
Content = string.IsNullOrEmpty(oplogEntry.Payload) ? default : JsonSerializer.Deserialize<JsonElement>(oplogEntry.Payload);
3535
UpdatedAt = oplogEntry.Timestamp;
3636
IsDeleted = oplogEntry.Operation == OperationType.Delete;
3737
}

src/EntglDb.Core/Sync/LastWriteWinsConflictResolver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
1111
if (local == null)
1212
{
1313
// Construct new document from oplog entry
14-
var content = remote.Payload ?? default;
14+
var content = string.IsNullOrEmpty(remote.Payload) ? default : JsonSerializer.Deserialize<JsonElement>(remote.Payload);
1515
var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
1616
return ConflictResolutionResult.Apply(newDoc);
1717
}
@@ -20,7 +20,7 @@ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
2020
if (remote.Timestamp.CompareTo(local.UpdatedAt) > 0)
2121
{
2222
// Remote is newer, apply it
23-
var content = remote.Payload ?? default;
23+
var content = string.IsNullOrEmpty(remote.Payload) ? default : JsonSerializer.Deserialize<JsonElement>(remote.Payload);
2424
var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
2525
return ConflictResolutionResult.Apply(newDoc);
2626
}

src/EntglDb.Core/Sync/RecursiveNodeMergeConflictResolver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
2626
{
2727
if (local == null)
2828
{
29-
var content = remote.Payload ?? default;
29+
var content = string.IsNullOrEmpty(remote.Payload) ? default : JsonSerializer.Deserialize<JsonElement>(remote.Payload);
3030
var newDoc = new Document(remote.Collection, remote.Key, content, remote.Timestamp, remote.Operation == OperationType.Delete);
3131
return ConflictResolutionResult.Apply(newDoc);
3232
}
@@ -42,7 +42,7 @@ public ConflictResolutionResult Resolve(Document? local, OplogEntry remote)
4242
}
4343

4444
var localJson = local.Content;
45-
var remoteJson = remote.Payload ?? default;
45+
var remoteJson = string.IsNullOrEmpty(remote.Payload) ? default : JsonSerializer.Deserialize<JsonElement>(remote.Payload);
4646
var localTs = local.UpdatedAt;
4747
var remoteTs = remote.Timestamp;
4848

src/EntglDb.Network/CoreTypes/OplogEntry.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Text.Json;
32

43
namespace EntglDb.Core;
54

@@ -111,12 +110,12 @@ public class OplogEntry
111110
public string Collection { get; }
112111
public string Key { get; }
113112
public OperationType Operation { get; }
114-
public JsonElement? Payload { get; }
113+
public string? Payload { get; }
115114
public HlcTimestamp Timestamp { get; }
116115
public string Hash { get; }
117116
public string PreviousHash { get; }
118117

119-
public OplogEntry(string collection, string key, OperationType operation, JsonElement? payload, HlcTimestamp timestamp, string previousHash, string? hash = null)
118+
public OplogEntry(string collection, string key, OperationType operation, string? payload, HlcTimestamp timestamp, string previousHash, string? hash = null)
120119
{
121120
Collection = collection;
122121
Key = key;

src/EntglDb.Persistence.BLite/BLiteDocumentStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public async Task<Document> MergeAsync(Document incoming, CancellationToken canc
329329
incoming.Collection,
330330
incoming.Key,
331331
OperationType.Put,
332-
incoming.Content,
332+
incoming.Content.GetRawText(),
333333
incoming.UpdatedAt,
334334
""));
335335

src/EntglDb.Persistence.BLite/EntglDb.Persistence.BLite.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
</PropertyGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="BLite" Version="4.3.0" />
21-
<PackageReference Include="BLite.SourceGenerators" Version="4.3.0">
20+
<PackageReference Include="BLite" Version="4.3.1" />
21+
<PackageReference Include="BLite.SourceGenerators" Version="4.3.1">
2222
<PrivateAssets>all</PrivateAssets>
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>

src/EntglDb.Persistence.BLite/Entities/EntityMappers.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static OplogEntity ToEntity(this OplogEntry entry)
2727
Key = entry.Key,
2828
Operation = (int)entry.Operation,
2929
// Store normalized (key-sorted) JSON so cross-node payload comparisons are reliable
30-
PayloadJson = entry.Payload.HasValue ? NormalizeJson(entry.Payload.Value) : "",
30+
PayloadJson = string.IsNullOrEmpty(entry.Payload) ? "" : NormalizeJson(JsonSerializer.Deserialize<JsonElement>(entry.Payload)),
3131
TimestampPhysicalTime = entry.Timestamp.PhysicalTime,
3232
TimestampLogicalCounter = entry.Timestamp.LogicalCounter,
3333
TimestampNodeId = entry.Timestamp.NodeId,
@@ -41,18 +41,11 @@ public static OplogEntity ToEntity(this OplogEntry entry)
4141
/// </summary>
4242
public static OplogEntry ToDomain(this OplogEntity entity)
4343
{
44-
JsonElement? payload = null;
45-
// Treat empty string as null payload (Delete operations)
46-
if (!string.IsNullOrEmpty(entity.PayloadJson))
47-
{
48-
payload = JsonSerializer.Deserialize<JsonElement>(entity.PayloadJson);
49-
}
50-
5144
return new OplogEntry(
5245
entity.Collection,
5346
entity.Key,
5447
(OperationType)entity.Operation,
55-
payload,
48+
string.IsNullOrEmpty(entity.PayloadJson) ? null : entity.PayloadJson,
5649
new HlcTimestamp(entity.TimestampPhysicalTime, entity.TimestampLogicalCounter, entity.TimestampNodeId),
5750
entity.PreviousHash,
5851
entity.Hash);
@@ -208,6 +201,36 @@ public static string ComputeContentHashFromNormalized(string normalizedJson)
208201
return Convert.ToHexString(hash).ToLowerInvariant();
209202
}
210203

204+
/// <summary>
205+
/// Computes a SHA-256 hash of the canonical (key-sorted) JSON representation of a document.
206+
/// Returns an empty string for null/empty content (deletes).
207+
/// </summary>
208+
public static string ComputeContentHash(string? contentJson)
209+
{
210+
if (string.IsNullOrEmpty(contentJson)) return "";
211+
return ComputeContentHash(JsonSerializer.Deserialize<JsonElement>(contentJson));
212+
}
213+
214+
/// <summary>
215+
/// Creates a DocumentMetadataEntity from collection, key, timestamp, deleted state, and optional raw JSON content.
216+
/// Used for tracking document sync state when content is available as a string.
217+
/// </summary>
218+
public static DocumentMetadataEntity CreateDocumentMetadata(
219+
string collection, string key, HlcTimestamp timestamp, bool isDeleted, string? contentJson)
220+
{
221+
return new DocumentMetadataEntity
222+
{
223+
Id = $"{collection}/{key}",
224+
Collection = collection,
225+
Key = key,
226+
HlcPhysicalTime = timestamp.PhysicalTime,
227+
HlcLogicalCounter = timestamp.LogicalCounter,
228+
HlcNodeId = timestamp.NodeId,
229+
IsDeleted = isDeleted,
230+
ContentHash = isDeleted ? "" : ComputeContentHash(contentJson)
231+
};
232+
}
233+
211234
/// <summary>
212235
/// Computes a SHA-256 hash of the canonical (key-sorted) JSON representation of a document.
213236
/// Returns an empty string for null content (deletes).

src/EntglDb.Persistence.BLite/PendingChangesFlushService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private async Task ProcessOneChangeAsync(PendingChange change, CancellationToken
9090

9191
// For Put: read current content from the application document store.
9292
var operationType = change.OperationType;
93-
JsonElement? content = null;
93+
string? content = null;
9494

9595
if (operationType == OperationType.Put)
9696
{
@@ -102,7 +102,7 @@ private async Task ProcessOneChangeAsync(PendingChange change, CancellationToken
102102
}
103103
else
104104
{
105-
content = doc.Content;
105+
content = doc.Content.GetRawText();
106106
}
107107
}
108108

0 commit comments

Comments
 (0)