Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0c0727d
Introduced defer to new operation compiler
michaelstaib Feb 6, 2026
491ad28
Started work on integrating defer into the execution engine
michaelstaib Feb 6, 2026
0f6c935
Reworked the coordinator
michaelstaib Feb 6, 2026
28222e9
Added more defer logic ...
michaelstaib Feb 9, 2026
f41474e
completed task handling.
michaelstaib Feb 9, 2026
66d907c
Merge branch 'main' into mst/defer-v2
michaelstaib Feb 9, 2026
073fb57
reworked scheduler and general result handling.
michaelstaib Feb 9, 2026
d09df58
Removed mutation transaction scope
michaelstaib Feb 9, 2026
8c81364
Reworked the execution middleware
michaelstaib Feb 9, 2026
26f8350
Fixed more issues on defer
michaelstaib Feb 10, 2026
395de39
Fixed more things around defer
michaelstaib Feb 10, 2026
f8d546b
Merge branch 'main' into mst/defer-v2
michaelstaib Feb 10, 2026
9ae122f
refined more
michaelstaib Feb 10, 2026
e611668
Reworked Defer Tests
michaelstaib Feb 10, 2026
b9495ad
More bug fixes
michaelstaib Feb 11, 2026
e923ed9
Fixed null propagation
michaelstaib Feb 11, 2026
57c6b93
Fixed more issues
michaelstaib Feb 11, 2026
74a2f49
Fixed more issues
michaelstaib Feb 12, 2026
635251f
Fixed more issues
michaelstaib Feb 12, 2026
f2b14ae
Fixed tests
michaelstaib Feb 12, 2026
5c02c4a
Fixed more tests
michaelstaib Feb 12, 2026
5995da0
Fixed more tests
michaelstaib Feb 12, 2026
2eef579
fixed tests
michaelstaib Feb 12, 2026
a1de950
Fixed more tests
michaelstaib Feb 12, 2026
1f11f6c
Fixed more tests
michaelstaib Feb 13, 2026
156939a
Fixed more tests
michaelstaib Feb 13, 2026
3bbe133
fixed more tests
michaelstaib Feb 13, 2026
0596f91
Fixed more tests
michaelstaib Feb 13, 2026
0aa5f34
Fixed more tests
michaelstaib Feb 13, 2026
d6313f2
Merge branch 'main' into mst/defer-v2
michaelstaib Feb 13, 2026
54ebfe2
Fixed more tests
michaelstaib Feb 13, 2026
2b42b44
fixed snapshot
michaelstaib Feb 13, 2026
024a02b
Fixed Tests
michaelstaib Feb 13, 2026
3a1ed0e
fixed test
michaelstaib Feb 13, 2026
207923f
Fixed test
michaelstaib Feb 13, 2026
4ff001a
fixed more tests
michaelstaib Feb 13, 2026
2fca9b3
Fixed file upload
michaelstaib Feb 13, 2026
ac24bf8
Fixed more stuff
michaelstaib Feb 13, 2026
b3c0439
Fixed more issues
michaelstaib Feb 13, 2026
6afbbfe
Fixed helper
michaelstaib Feb 13, 2026
0a2b4da
Fixed issue
michaelstaib Feb 13, 2026
77901f6
Fixed more issues.
michaelstaib Feb 13, 2026
55e9e79
Refactor JSON serialization to support null value ignoring
michaelstaib Feb 14, 2026
ea1a7a1
Fixed more tests
michaelstaib Feb 14, 2026
cc5bc9e
Fixed in-memory client
michaelstaib Feb 14, 2026
82b165d
skipped test
michaelstaib Feb 14, 2026
851328f
Fixed variable coercion.
michaelstaib Feb 14, 2026
cc6e8f8
Merge branch 'main' into mst/defer-v2
michaelstaib Feb 14, 2026
582bfa7
cleanup
michaelstaib Feb 14, 2026
b5ffc94
Fixed more tests
michaelstaib Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using HotChocolate.Language;
using SnapshotValueFormatters = CookieCrumble.HotChocolate.Language.Formatters.SnapshotValueFormatters;

namespace CookieCrumble.HotChocolate;

public static class SnapshotExtensions
{
public static void MatchSnapshot(
this ISyntaxNode? value,
string? postFix = null)
=> Snapshot.Match(
value,
postFix,
extension: ".graphql",
formatter: SnapshotValueFormatters.GraphQLSyntaxNode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal sealed class GraphQLSyntaxNodeSnapshotValueFormatter : SnapshotValueFor
{
protected override void Format(IBufferWriter<byte> snapshot, ISyntaxNode value)
{
var serialized = value.Print().AsSpan();
var serialized = value.Print(indented: true).AsSpan();
var buffer = ArrayPool<char>.Shared.Rent(serialized.Length);
var span = buffer.AsSpan()[..serialized.Length];
var written = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ public sealed class CookieCrumbleHotChocolate : SnapshotModule
protected override IEnumerable<ISnapshotValueFormatter> CreateFormatters()
{
yield return SnapshotValueFormatters.ExecutionResult;
yield return SnapshotValueFormatters.GraphQL;
yield return SnapshotValueFormatters.GraphQLHttp;
yield return SnapshotValueFormatters.OperationResult;
yield return SnapshotValueFormatters.Schema;
yield return SnapshotValueFormatters.SchemaError;
yield return SnapshotValueFormatters.Error;
yield return SnapshotValueFormatters.ErrorList;
yield return SnapshotValueFormatters.ResultElement;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
using CookieCrumble.HotChocolate.Formatters;
using HotChocolate;
using HotChocolate.Execution;
using HotChocolate.Language;
using CoreFormatters = CookieCrumble.Formatters.SnapshotValueFormatters;

namespace CookieCrumble.HotChocolate;

public static class SnapshotExtensions
{
public static void MatchSnapshot(
this ISyntaxNode? value,
string? postFix = null)
=> Snapshot.Match(
value,
postFix,
extension: ".graphql",
formatter: SnapshotValueFormatters.GraphQL);

public static void MatchSnapshot(
this ISchemaDefinition? value,
string? postFix = null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Buffers;
using System.Text.Encodings.Web;
using System.Text.Json;
using CookieCrumble.Formatters;
using HotChocolate;
using HotChocolate.Execution;
using HotChocolate.Text.Json;

namespace CookieCrumble.HotChocolate.Formatters;

internal sealed class ErrorListSnapshotValueFormatter
: SnapshotValueFormatter<IReadOnlyList<IError>>
{
protected override void Format(IBufferWriter<byte> snapshot, IReadOnlyList<IError> value)
{
var writerOptions = new JsonWriterOptions
{
Indented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

var serializationOptions = new JsonSerializerOptions
{
WriteIndented = true
};

var writer = new JsonWriter(snapshot, writerOptions);
JsonValueFormatter.WriteErrors(writer, value, serializationOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ internal sealed class ErrorSnapshotValueFormatter()
protected override void Format(IBufferWriter<byte> snapshot, IError value)
{
var jsonWriter = new JsonWriter(snapshot, new JsonWriterOptions { Indented = true });
JsonValueFormatter.WriteError(jsonWriter, value, new JsonSerializerOptions { WriteIndented = true }, default);
JsonValueFormatter.WriteError(jsonWriter, value, new JsonSerializerOptions { WriteIndented = true });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,163 +101,138 @@ private static async Task FormatStreamAsync(

internal sealed class JsonResultPatcher
{
private const string Data = "data";
private const string Items = "items";
private const string Incremental = "incremental";
private const string Path = "path";
private const string DataProp = "data";
private const string ItemsProp = "items";
private const string IncrementalProp = "incremental";
private const string PendingProp = "pending";
private const string PathProp = "path";
private const string SubPathProp = "subPath";
private const string IdProp = "id";
private JsonObject? _json;
private readonly Dictionary<string, JsonElement> _pendingPaths = new();

public void SetResponse(JsonDocument response)
{
ArgumentNullException.ThrowIfNull(response);

_json = JsonObject.Create(response.RootElement);
ProcessPayload(response.RootElement);
}

public void WriteResponse(IBufferWriter<byte> snapshot)
public void ApplyPatch(JsonDocument patch)
{
if (_json is null)
{
throw new InvalidOperationException(
"You must first set the initial response before you can apply patches.");
}

using var writer = new Utf8JsonWriter(snapshot, new JsonWriterOptions { Indented = true });

_json.Remove("hasNext");

_json.WriteTo(writer);
writer.Flush();
ProcessPayload(patch.RootElement);
}

public void ApplyPatch(JsonDocument patch)
public void WriteResponse(IBufferWriter<byte> snapshot)
{
if (_json is null)
{
throw new InvalidOperationException(
"You must first set the initial response before you can apply patches.");
}

if (!patch.RootElement.TryGetProperty(Incremental, out var incremental))
{
throw new ArgumentException("A patch result must contain a property `incremental`.");
}
using var writer = new Utf8JsonWriter(snapshot, new JsonWriterOptions { Indented = true });

foreach (var element in incremental.EnumerateArray())
{
if (element.TryGetProperty(Data, out var data))
{
PatchIncrementalData(element, JsonObject.Create(data)!);
}
else if (element.TryGetProperty(Items, out var items))
{
PatchIncrementalItems(element, JsonArray.Create(items)!);
}
}
}
_json.Remove("hasNext");
_json.Remove("pending");
_json.Remove("incremental");
_json.Remove("completed");

private void PatchIncrementalData(JsonElement incremental, JsonObject data)
{
if (incremental.TryGetProperty(Path, out var pathProp))
{
var (current, last) = SelectNodeToPatch(_json![Data]!, pathProp);
ApplyPatch(current, last, data);
}
_json.WriteTo(writer);
writer.Flush();
}

private void PatchIncrementalItems(JsonElement incremental, JsonArray items)
private void ProcessPayload(JsonElement root)
{
if (incremental.TryGetProperty(Path, out var pathProp))
if (root.TryGetProperty(PendingProp, out var pending))
{
var (current, last) = SelectNodeToPatch(_json![Data]!, pathProp);
var i = last.GetInt32();
var target = current.AsArray();

while (items.Count > 0)
foreach (var entry in pending.EnumerateArray())
{
var item = items[0];
items.RemoveAt(0);
target.Insert(i++, item);
if (entry.TryGetProperty(IdProp, out var id)
&& entry.TryGetProperty(PathProp, out var path))
{
_pendingPaths[id.GetString()!] = path.Clone();
}
}
}
}

private static void ApplyPatch(JsonNode current, JsonElement last, JsonObject patchData)
{
if (last.ValueKind is JsonValueKind.Undefined)
if (root.TryGetProperty(IncrementalProp, out var incremental))
{
foreach (var prop in patchData.ToArray())
foreach (var element in incremental.EnumerateArray())
{
patchData.Remove(prop.Key);
current[prop.Key] = prop.Value;
}
}
else if (last.ValueKind is JsonValueKind.String)
{
current = current[last.GetString()!]!;
if (!element.TryGetProperty(IdProp, out var idElement))
{
continue;
}

foreach (var prop in patchData.ToArray())
{
patchData.Remove(prop.Key);
current[prop.Key] = prop.Value;
}
}
else if (last.ValueKind is JsonValueKind.Number)
{
var index = last.GetInt32();
var element = current[index];
var id = idElement.GetString()!;

if (element is null)
{
current[index] = patchData;
}
else
{
foreach (var prop in patchData.ToArray())
if (!_pendingPaths.TryGetValue(id, out var basePath))
{
continue;
}

if (element.TryGetProperty(DataProp, out var data))
{
PatchData(basePath, element, JsonObject.Create(data)!);
}
else if (element.TryGetProperty(ItemsProp, out var items))
{
patchData.Remove(prop.Key);
element[prop.Key] = prop.Value;
PatchItems(basePath, JsonArray.Create(items)!);
}
}
}
else
}

private void PatchData(JsonElement basePath, JsonElement incremental, JsonObject data)
{
var current = NavigatePath(_json![DataProp]!, basePath);

if (incremental.TryGetProperty(SubPathProp, out var subPath))
{
throw new NotSupportedException("Path segment must be int or string.");
current = NavigatePath(current, subPath);
}

foreach (var prop in data.ToArray())
{
data.Remove(prop.Key);
current[prop.Key] = prop.Value;
}
}

private static (JsonNode Node, JsonElement PathSegment) SelectNodeToPatch(
JsonNode root,
JsonElement path)
private void PatchItems(JsonElement basePath, JsonArray items)
{
if (path.GetArrayLength() == 0)
var target = NavigatePath(_json![DataProp]!, basePath).AsArray();

while (items.Count > 0)
{
return (root, default);
var item = items[0];
items.RemoveAt(0);
target.Add(item);
}
}

private static JsonNode NavigatePath(JsonNode root, JsonElement path)
{
var current = root;
JsonElement? last = null;

foreach (var element in path.EnumerateArray())
foreach (var segment in path.EnumerateArray())
{
if (last is not null)
current = segment.ValueKind switch
{
current = last.Value.ValueKind switch
{
JsonValueKind.String => current[last.Value.GetString()!]!,
JsonValueKind.Number => current[last.Value.GetInt32()]!,
_ => throw new NotSupportedException("Path segment must be int or string.")
};
}

last = element;
}

if (current is null || last is null)
{
throw new InvalidOperationException("Patch had invalid structure.");
JsonValueKind.String => current[segment.GetString()!]!,
JsonValueKind.Number => current[segment.GetInt32()]!,
_ => throw new NotSupportedException("Path segment must be int or string.")
};
}

return (current, last.Value);
return current;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ internal sealed class ResultElementSnapshotValueFormatter
: SnapshotValueFormatter<ResultElement>
{
protected override void Format(IBufferWriter<byte> snapshot, ResultElement element)
=> element.WriteTo(snapshot, indented: true);
=> element.WriteTo(snapshot, indented: true);
}
Loading
Loading