using System.Text.Json.Nodes; namespace w4c_workflows.Models.Nodes; // --------------------------------------------------------------------------- // The unit of data that flows between nodes. A node receives one or more // FlowItems and produces zero or more per output port. Items carry their own // provenance so expressions like $("Prepare") and the UI's "trace this item" // can resolve which upstream item produced a given downstream item. // --------------------------------------------------------------------------- /// A binary payload stored out-of-band; items only hold a reference. public sealed record BinaryAttachment( string AssetId, string? FileName = null, string? MimeType = null, long? SizeBytes = null); /// A pointer back to an item emitted by another node run. public sealed record ItemOrigin( string NodeId, int OutputIndex = 0, int RunIndex = 0, int ItemIndex = 0); /// Where a came from (for tracing and expressions). public sealed record FlowItemOrigin( string NodeId, int OutputIndex = 0, int RunIndex = 0, int ItemIndex = 0, List? PairedItems = null); /// /// One data item. is the payload; holds /// named binary references; links it to its source. /// public sealed record FlowItem { public required JsonObject Json { get; init; } public Dictionary? Binary { get; init; } public FlowItemOrigin? Origin { get; init; } /// Convenience factory for a plain JSON item. public static FlowItem FromJson(JsonObject json) => new() { Json = json }; /// Wraps a bare JSON value in a single "value" field. public static FlowItem Scalar(JsonNode? value) => new() { Json = new JsonObject { ["value"] = value?.DeepClone() }, }; } /// Helpers for turning sets of items into and out of JSON. public static class FlowItemJson { /// Serializes a batch of items into a JSON array (or null when empty). public static string? Serialize(IReadOnlyList items) { if (items.Count == 0) return null; var array = new JsonArray(); foreach (var item in items) { var entry = new JsonObject { ["json"] = item.Json.DeepClone() }; if (item.Binary is { Count: > 0 }) { var binary = new JsonObject(); foreach (var (name, attachment) in item.Binary) { binary[name] = new JsonObject { ["assetId"] = attachment.AssetId, ["fileName"] = attachment.FileName, ["mimeType"] = attachment.MimeType, ["sizeBytes"] = attachment.SizeBytes, }; } entry["binary"] = binary; } if (item.Origin != null) { entry["origin"] = new JsonObject { ["nodeId"] = item.Origin.NodeId, ["outputIndex"] = item.Origin.OutputIndex, ["runIndex"] = item.Origin.RunIndex, ["itemIndex"] = item.Origin.ItemIndex, }; } array.Add(entry); } return array.ToJsonString(); } /// /// Parses a JSON payload into items. Accepts a JSON array of /// { "json": … } envelopes, a JSON array of bare objects, or a single /// bare object (which becomes a one-item batch). /// public static List Parse(string? payload) { var items = new List(); if (string.IsNullOrWhiteSpace(payload)) return items; JsonNode? node; try { node = JsonNode.Parse(payload); } catch { return items; } switch (node) { case JsonArray array: foreach (var element in array) items.Add(FromElement(element)); break; case JsonObject obj: items.Add(FromObject(obj)); break; } return items; } private static FlowItem FromElement(JsonNode? element) => element switch { JsonObject obj => FromObject(obj), null => new FlowItem { Json = new JsonObject() }, _ => FlowItem.Scalar(element), }; private static FlowItem FromObject(JsonObject obj) { // Envelope form { json, binary, origin }: unwrap; otherwise treat the // whole object as the payload. if (obj.ContainsKey("json") && obj["json"] is JsonObject payload) { return new FlowItem { Json = (JsonObject)payload.DeepClone(), Origin = ParseOrigin(obj["origin"] as JsonObject), Binary = ParseBinary(obj["binary"] as JsonObject), }; } return new FlowItem { Json = (JsonObject)obj.DeepClone() }; } private static FlowItemOrigin? ParseOrigin(JsonObject? origin) { if (origin == null) return null; return new FlowItemOrigin( origin["nodeId"]?.GetValue() ?? string.Empty, origin["outputIndex"]?.GetValue() ?? 0, origin["runIndex"]?.GetValue() ?? 0, origin["itemIndex"]?.GetValue() ?? 0); } private static Dictionary? ParseBinary(JsonObject? binary) { if (binary == null || binary.Count == 0) return null; var result = new Dictionary(StringComparer.Ordinal); foreach (var (name, value) in binary) { if (value is not JsonObject entry) continue; result[name] = new BinaryAttachment( entry["assetId"]?.GetValue() ?? string.Empty, entry["fileName"]?.GetValue(), entry["mimeType"]?.GetValue(), entry["sizeBytes"]?.GetValue()); } return result; } }