File: ServiceClient\SqliteResourceRepository.Storage.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Data;
using Aspire.Dashboard.Utils;
using Aspire.DashboardService.Proto.V1;
using Dapper;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Microsoft.Data.Sqlite;
 
namespace Aspire.Dashboard.ServiceClient;
 
public sealed partial class SqliteResourceRepository
{
    private const int MaxWriteBatchSize = 100;
 
    private static void InsertResources(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<ResourceToSave> resources)
    {
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            resources,
            MaxWriteBatchSize,
            "dashboard_resources",
            [
                "resource_name", "replica_index", "resource_type", "display_name", "uid", "state",
                "created_at_seconds", "created_at_nanos", "state_style",
                "started_at_seconds", "started_at_nanos", "stopped_at_seconds", "stopped_at_nanos",
                "is_hidden", "supports_detailed_telemetry", "icon_name", "icon_variant", "console_logs_loaded"
            ],
            static (resourceToSave, parameters) =>
            {
                var resource = resourceToSave.Resource;
                parameters[0].Value = resource.Name;
                parameters[1].Value = resourceToSave.ReplicaIndex;
                parameters[2].Value = resource.ResourceType;
                parameters[3].Value = resource.DisplayName;
                parameters[4].Value = resource.Uid;
                parameters[5].Value = resource.HasState ? resource.State : DBNull.Value;
                parameters[6].Value = resource.CreatedAt?.Seconds ?? (object)DBNull.Value;
                parameters[7].Value = resource.CreatedAt?.Nanos ?? (object)DBNull.Value;
                parameters[8].Value = resource.HasStateStyle ? resource.StateStyle : DBNull.Value;
                parameters[9].Value = resource.StartedAt?.Seconds ?? (object)DBNull.Value;
                parameters[10].Value = resource.StartedAt?.Nanos ?? (object)DBNull.Value;
                parameters[11].Value = resource.StoppedAt?.Seconds ?? (object)DBNull.Value;
                parameters[12].Value = resource.StoppedAt?.Nanos ?? (object)DBNull.Value;
                parameters[13].Value = resource.IsHidden;
                parameters[14].Value = resource.SupportsDetailedTelemetry;
                parameters[15].Value = resource.HasIconName ? resource.IconName : DBNull.Value;
                parameters[16].Value = resource.HasIconVariant ? (int)resource.IconVariant : DBNull.Value;
                parameters[17].Value = resourceToSave.ConsoleLogsLoaded;
            });
 
        var resourceModels = resources.Select(resource => resource.Resource).ToArray();
        InsertEnvironment(connection, transaction, resourceModels);
        InsertUrls(connection, transaction, resourceModels);
        InsertVolumes(connection, transaction, resourceModels);
        InsertHealthReports(connection, transaction, resourceModels);
        InsertRelationships(connection, transaction, resourceModels);
        var properties = new List<PropertyToSave>();
        var commands = new List<CommandToSave>();
        foreach (var resource in resourceModels)
        {
            foreach (var (property, ordinal) in resource.Properties.Select((item, ordinal) => (item, ordinal)))
            {
                properties.Add(new(resource.Name, ordinal, property));
            }
 
#pragma warning disable CS0612 // ResourceCommand.Parameter must be persisted for compatibility with older AppHosts.
            foreach (var (command, ordinal) in resource.Commands.Select((item, ordinal) => (item, ordinal)))
            {
                var parameterJsonValue = command.Parameter is not null ? JsonFormatter.Default.Format(command.Parameter) : null;
                commands.Add(new(resource.Name, ordinal, command, parameterJsonValue));
            }
#pragma warning restore CS0612
        }
 
        InsertProperties(connection, transaction, properties);
        InsertCommands(connection, transaction, commands);
    }
 
    private static void InsertConsoleLogs(
        SqliteConnection connection,
        IDbTransaction transaction,
        string resourceName,
        IReadOnlyList<ConsoleLogToInsert> consoleLogs)
    {
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            consoleLogs,
            MaxWriteBatchSize,
            "console_logs",
            ["resource_name", "line_number", "content", "is_stderr"],
            (consoleLog, parameters) =>
            {
                parameters[0].Value = resourceName;
                parameters[1].Value = consoleLog.LineNumber;
                parameters[2].Value = consoleLog.Content;
                parameters[3].Value = consoleLog.IsStdErr;
            });
    }
 
    private static void InsertEnvironment(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<Resource> resources)
    {
        var rows = resources
            .SelectMany(resource => resource.Environment.Select((item, ordinal) => (ResourceName: resource.Name, Ordinal: ordinal, Item: item)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            rows,
            MaxWriteBatchSize,
            "dashboard_resource_environment",
            ["resource_name", "ordinal", "name", "value", "is_from_spec"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Item.Name;
                parameters[3].Value = row.Item.HasValue ? row.Item.Value : DBNull.Value;
                parameters[4].Value = row.Item.IsFromSpec;
            });
    }
 
    private static void InsertUrls(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<Resource> resources)
    {
        var rows = resources
            .SelectMany(resource => resource.Urls.Select((item, ordinal) => (ResourceName: resource.Name, Ordinal: ordinal, Item: item)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            rows,
            MaxWriteBatchSize,
            "dashboard_resource_urls",
            ["resource_name", "ordinal", "endpoint_name", "full_url", "is_internal", "is_inactive", "display_sort_order", "display_name"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Item.HasEndpointName ? row.Item.EndpointName : DBNull.Value;
                parameters[3].Value = row.Item.FullUrl;
                parameters[4].Value = row.Item.IsInternal;
                parameters[5].Value = row.Item.IsInactive;
                parameters[6].Value = row.Item.DisplayProperties.SortOrder;
                parameters[7].Value = row.Item.DisplayProperties.DisplayName;
            });
    }
 
    private static void InsertVolumes(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<Resource> resources)
    {
        var rows = resources
            .SelectMany(resource => resource.Volumes.Select((item, ordinal) => (ResourceName: resource.Name, Ordinal: ordinal, Item: item)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            rows,
            MaxWriteBatchSize,
            "dashboard_resource_volumes",
            ["resource_name", "ordinal", "source", "target", "mount_type", "is_read_only"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Item.Source;
                parameters[3].Value = row.Item.Target;
                parameters[4].Value = row.Item.MountType;
                parameters[5].Value = row.Item.IsReadOnly;
            });
    }
 
    private static void InsertHealthReports(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<Resource> resources)
    {
        var rows = resources
            .SelectMany(resource => resource.HealthReports.Select((item, ordinal) => (ResourceName: resource.Name, Ordinal: ordinal, Item: item)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            rows,
            MaxWriteBatchSize,
            "dashboard_resource_health_reports",
            ["resource_name", "ordinal", "status", "key", "description", "exception", "last_run_at_seconds", "last_run_at_nanos"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Item.HasStatus ? (int)row.Item.Status : DBNull.Value;
                parameters[3].Value = row.Item.Key;
                parameters[4].Value = row.Item.Description;
                parameters[5].Value = row.Item.Exception;
                parameters[6].Value = row.Item.LastRunAt?.Seconds ?? (object)DBNull.Value;
                parameters[7].Value = row.Item.LastRunAt?.Nanos ?? (object)DBNull.Value;
            });
    }
 
    private static void InsertRelationships(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<Resource> resources)
    {
        var rows = resources
            .SelectMany(resource => resource.Relationships.Select((item, ordinal) => (ResourceName: resource.Name, Ordinal: ordinal, Item: item)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            rows,
            MaxWriteBatchSize,
            "dashboard_resource_relationships",
            ["resource_name", "ordinal", "related_resource_name", "relationship_type"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Item.ResourceName;
                parameters[3].Value = row.Item.Type;
            });
    }
 
    private static void InsertProperties(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<PropertyToSave> properties)
    {
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            properties,
            MaxWriteBatchSize,
            "dashboard_resource_properties",
            ["resource_name", "ordinal", "name", "display_name", "value", "is_sensitive", "is_highlighted", "sort_order"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = row.Property.Name;
                parameters[3].Value = row.Property.HasDisplayName ? row.Property.DisplayName : DBNull.Value;
                parameters[4].Value = JsonFormatter.Default.Format(row.Property.Value);
                parameters[5].Value = row.Property.HasIsSensitive ? row.Property.IsSensitive : DBNull.Value;
                parameters[6].Value = row.Property.IsHighlighted;
                parameters[7].Value = row.Property.HasSortOrder ? row.Property.SortOrder : DBNull.Value;
            });
    }
 
    private static void InsertCommands(SqliteConnection connection, IDbTransaction transaction, IReadOnlyList<CommandToSave> commands)
    {
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            commands,
            MaxWriteBatchSize,
            "dashboard_resource_commands",
            [
                "resource_name", "ordinal", "name", "display_name", "confirmation_message", "parameter_value",
                "is_highlighted", "icon_name", "icon_variant", "display_description", "state"
            ],
            static (row, parameters) =>
            {
                var command = row.Command;
                parameters[0].Value = row.ResourceName;
                parameters[1].Value = row.Ordinal;
                parameters[2].Value = command.Name;
                parameters[3].Value = command.DisplayName;
                parameters[4].Value = command.HasConfirmationMessage ? command.ConfirmationMessage : DBNull.Value;
                parameters[5].Value = row.ParameterJsonValue ?? (object)DBNull.Value;
                parameters[6].Value = command.IsHighlighted;
                parameters[7].Value = command.HasIconName ? command.IconName : DBNull.Value;
                parameters[8].Value = command.HasIconVariant ? (int)command.IconVariant : DBNull.Value;
                parameters[9].Value = command.HasDisplayDescription ? command.DisplayDescription : DBNull.Value;
                parameters[10].Value = (int)command.State;
            });
 
        var inputs = commands.SelectMany(command => command.Command.ArgumentInputs.Select((input, ordinal) => (Command: command, Ordinal: ordinal, Input: input))).ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            inputs,
            MaxWriteBatchSize,
            "dashboard_resource_command_inputs",
            [
                "resource_name", "command_ordinal", "ordinal", "label", "placeholder", "input_type", "required", "value",
                "description", "enable_description_markdown", "max_length", "allow_custom_choice", "loading",
                "update_state_on_change", "name", "disabled", "max_file_size", "allow_multiple_files", "file_filter"
            ],
            static (row, parameters) =>
            {
                var input = row.Input;
                parameters[0].Value = row.Command.ResourceName;
                parameters[1].Value = row.Command.Ordinal;
                parameters[2].Value = row.Ordinal;
                parameters[3].Value = input.Label;
                parameters[4].Value = input.Placeholder;
                parameters[5].Value = (int)input.InputType;
                parameters[6].Value = input.Required;
                parameters[7].Value = input.Value;
                parameters[8].Value = input.Description;
                parameters[9].Value = input.EnableDescriptionMarkdown;
                parameters[10].Value = input.MaxLength;
                parameters[11].Value = input.AllowCustomChoice;
                parameters[12].Value = input.Loading;
                parameters[13].Value = input.UpdateStateOnChange;
                parameters[14].Value = input.Name;
                parameters[15].Value = input.Disabled;
                parameters[16].Value = input.MaxFileSize;
                parameters[17].Value = input.AllowMultipleFiles;
                parameters[18].Value = input.FileFilter;
            });
 
        var options = inputs
            .SelectMany(row => row.Input.Options.Select(option => (row.Command, InputOrdinal: row.Ordinal, Option: option)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            options,
            MaxWriteBatchSize,
            "dashboard_resource_command_input_options",
            ["resource_name", "command_ordinal", "input_ordinal", "option_key", "option_value"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.Command.ResourceName;
                parameters[1].Value = row.Command.Ordinal;
                parameters[2].Value = row.InputOrdinal;
                parameters[3].Value = row.Option.Key;
                parameters[4].Value = row.Option.Value;
            });
 
        var validationErrors = inputs
            .SelectMany(row => row.Input.ValidationErrors.Select((validationError, ordinal) => (row.Command, InputOrdinal: row.Ordinal, Ordinal: ordinal, ValidationError: validationError)))
            .ToArray();
        SqliteBatchInsert.BatchInsertRows(
            connection,
            transaction,
            validationErrors,
            MaxWriteBatchSize,
            "dashboard_resource_command_input_validation_errors",
            ["resource_name", "command_ordinal", "input_ordinal", "ordinal", "validation_error"],
            static (row, parameters) =>
            {
                parameters[0].Value = row.Command.ResourceName;
                parameters[1].Value = row.Command.Ordinal;
                parameters[2].Value = row.InputOrdinal;
                parameters[3].Value = row.Ordinal;
                parameters[4].Value = row.ValidationError;
            });
    }
 
    private static IEnumerable<StoredResource> LoadResourceRecords(SqliteConnection connection)
    {
        using var reader = connection.QueryMultiple("""
            SELECT
                resource_name AS ResourceName,
                replica_index AS ReplicaIndex,
                resource_type AS ResourceType,
                display_name AS DisplayName,
                uid AS Uid,
                state AS State,
                created_at_seconds AS CreatedAtSeconds,
                created_at_nanos AS CreatedAtNanos,
                state_style AS StateStyle,
                started_at_seconds AS StartedAtSeconds,
                started_at_nanos AS StartedAtNanos,
                stopped_at_seconds AS StoppedAtSeconds,
                stopped_at_nanos AS StoppedAtNanos,
                is_hidden AS IsHidden,
                supports_detailed_telemetry AS SupportsDetailedTelemetry,
                icon_name AS IconName,
                icon_variant AS IconVariant,
                console_logs_loaded AS ConsoleLogsLoaded
            FROM dashboard_resources
            ORDER BY rowid;
 
            SELECT resource_name AS ResourceName, name AS Name, value AS Value, is_from_spec AS IsFromSpec
            FROM dashboard_resource_environment
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, endpoint_name AS EndpointName, full_url AS FullUrl,
                is_internal AS IsInternal, is_inactive AS IsInactive, display_sort_order AS DisplaySortOrder, display_name AS DisplayName
            FROM dashboard_resource_urls
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, source AS Source, target AS Target, mount_type AS MountType, is_read_only AS IsReadOnly
            FROM dashboard_resource_volumes
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, status AS Status, key AS Key, description AS Description,
                exception AS Exception, last_run_at_seconds AS LastRunAtSeconds, last_run_at_nanos AS LastRunAtNanos
            FROM dashboard_resource_health_reports
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, related_resource_name AS RelatedResourceName, relationship_type AS RelationshipType
            FROM dashboard_resource_relationships
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, name AS Name, display_name AS DisplayName, value AS JsonValue,
                is_sensitive AS IsSensitive, is_highlighted AS IsHighlighted, sort_order AS SortOrder
            FROM dashboard_resource_properties
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, ordinal AS Ordinal, name AS Name, display_name AS DisplayName,
                confirmation_message AS ConfirmationMessage, parameter_value AS ParameterJsonValue,
                is_highlighted AS IsHighlighted, icon_name AS IconName, icon_variant AS IconVariant,
                display_description AS DisplayDescription, state AS State
            FROM dashboard_resource_commands
            ORDER BY resource_name, ordinal;
 
            SELECT resource_name AS ResourceName, command_ordinal AS CommandOrdinal, ordinal AS Ordinal,
                label AS Label, placeholder AS Placeholder, input_type AS InputType, required AS Required,
                value AS Value, description AS Description, enable_description_markdown AS EnableDescriptionMarkdown,
                max_length AS MaxLength, allow_custom_choice AS AllowCustomChoice, loading AS Loading,
                update_state_on_change AS UpdateStateOnChange, name AS Name, disabled AS Disabled,
                max_file_size AS MaxFileSize, allow_multiple_files AS AllowMultipleFiles, file_filter AS FileFilter
            FROM dashboard_resource_command_inputs
            ORDER BY resource_name, command_ordinal, ordinal;
 
            SELECT resource_name AS ResourceName, command_ordinal AS CommandOrdinal, input_ordinal AS InputOrdinal,
                option_key AS OptionKey, option_value AS OptionValue
            FROM dashboard_resource_command_input_options
            ORDER BY resource_name, command_ordinal, input_ordinal, option_key;
 
            SELECT resource_name AS ResourceName, command_ordinal AS CommandOrdinal, input_ordinal AS InputOrdinal,
                validation_error AS ValidationError
            FROM dashboard_resource_command_input_validation_errors
            ORDER BY resource_name, command_ordinal, input_ordinal, ordinal;
 
            """);
 
        var resourceRecords = reader.Read<ResourceRecord>().AsList();
        var environments = reader.Read<EnvironmentRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var urls = reader.Read<UrlRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var volumes = reader.Read<VolumeRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var healthReports = reader.Read<HealthReportRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var relationships = reader.Read<RelationshipRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var properties = reader.Read<PropertyRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var commands = reader.Read<CommandRecord>().ToLookup(record => record.ResourceName, StringComparers.ResourceName);
        var inputs = reader.Read<InputRecord>().ToLookup(record => (record.ResourceName, record.CommandOrdinal));
        var options = reader.Read<OptionRecord>().ToLookup(record => (record.ResourceName, record.CommandOrdinal, record.InputOrdinal));
        var validationErrors = reader.Read<ValidationErrorRecord>().ToLookup(record => (record.ResourceName, record.CommandOrdinal, record.InputOrdinal));
 
        foreach (var record in resourceRecords)
        {
            var resource = new Resource
            {
                Name = record.ResourceName,
                ResourceType = record.ResourceType,
                DisplayName = record.DisplayName,
                Uid = record.Uid,
                IsHidden = record.IsHidden,
                SupportsDetailedTelemetry = record.SupportsDetailedTelemetry
            };
 
            SetOptionalResourceFields(resource, record);
            foreach (var environment in environments[record.ResourceName])
            {
                var item = new EnvironmentVariable { Name = environment.Name, IsFromSpec = environment.IsFromSpec };
                if (environment.Value is not null)
                {
                    item.Value = environment.Value;
                }
                resource.Environment.Add(item);
            }
            foreach (var url in urls[record.ResourceName])
            {
                var item = new Url
                {
                    FullUrl = url.FullUrl,
                    IsInternal = url.IsInternal,
                    IsInactive = url.IsInactive,
                    DisplayProperties = new UrlDisplayProperties { SortOrder = url.DisplaySortOrder, DisplayName = url.DisplayName }
                };
                if (url.EndpointName is not null)
                {
                    item.EndpointName = url.EndpointName;
                }
                resource.Urls.Add(item);
            }
            resource.Volumes.Add(volumes[record.ResourceName].Select(volume => new Volume
            {
                Source = volume.Source,
                Target = volume.Target,
                MountType = volume.MountType,
                IsReadOnly = volume.IsReadOnly
            }));
            foreach (var healthReport in healthReports[record.ResourceName])
            {
                var item = new HealthReport { Key = healthReport.Key, Description = healthReport.Description, Exception = healthReport.Exception };
                if (healthReport.Status is not null)
                {
                    item.Status = (HealthStatus)healthReport.Status.Value;
                }
                if (healthReport.LastRunAtSeconds is not null)
                {
                    item.LastRunAt = CreateTimestamp(healthReport.LastRunAtSeconds.Value, healthReport.LastRunAtNanos);
                }
                resource.HealthReports.Add(item);
            }
            resource.Relationships.Add(relationships[record.ResourceName].Select(relationship => new ResourceRelationship
            {
                ResourceName = relationship.RelatedResourceName,
                Type = relationship.RelationshipType
            }));
            foreach (var property in properties[record.ResourceName])
            {
                var item = new ResourceProperty
                {
                    Name = property.Name,
                    Value = MaterializeValue(property.JsonValue),
                    IsHighlighted = property.IsHighlighted
                };
                if (property.DisplayName is not null)
                {
                    item.DisplayName = property.DisplayName;
                }
                if (property.IsSensitive is not null)
                {
                    item.IsSensitive = property.IsSensitive.Value;
                }
                if (property.SortOrder is not null)
                {
                    item.SortOrder = property.SortOrder.Value;
                }
                resource.Properties.Add(item);
            }
 
#pragma warning disable CS0612 // ResourceCommand.Parameter must be restored for compatibility with older AppHosts.
            foreach (var commandRecord in commands[record.ResourceName])
            {
                var command = new ResourceCommand
                {
                    Name = commandRecord.Name,
                    DisplayName = commandRecord.DisplayName,
                    IsHighlighted = commandRecord.IsHighlighted,
                    State = (ResourceCommandState)commandRecord.State
                };
                if (commandRecord.ConfirmationMessage is not null)
                {
                    command.ConfirmationMessage = commandRecord.ConfirmationMessage;
                }
                if (commandRecord.ParameterJsonValue is not null)
                {
                    command.Parameter = MaterializeValue(commandRecord.ParameterJsonValue);
                }
                if (commandRecord.IconName is not null)
                {
                    command.IconName = commandRecord.IconName;
                }
                if (commandRecord.IconVariant is not null)
                {
                    command.IconVariant = (IconVariant)commandRecord.IconVariant.Value;
                }
                if (commandRecord.DisplayDescription is not null)
                {
                    command.DisplayDescription = commandRecord.DisplayDescription;
                }
 
                foreach (var inputRecord in inputs[(record.ResourceName, commandRecord.Ordinal)])
                {
                    var input = new InteractionInput
                    {
                        Label = inputRecord.Label,
                        Placeholder = inputRecord.Placeholder,
                        InputType = (InputType)inputRecord.InputType,
                        Required = inputRecord.Required,
                        Value = inputRecord.Value,
                        Description = inputRecord.Description,
                        EnableDescriptionMarkdown = inputRecord.EnableDescriptionMarkdown,
                        MaxLength = inputRecord.MaxLength,
                        AllowCustomChoice = inputRecord.AllowCustomChoice,
                        Loading = inputRecord.Loading,
                        UpdateStateOnChange = inputRecord.UpdateStateOnChange,
                        Name = inputRecord.Name,
                        Disabled = inputRecord.Disabled,
                        MaxFileSize = inputRecord.MaxFileSize,
                        AllowMultipleFiles = inputRecord.AllowMultipleFiles,
                        FileFilter = inputRecord.FileFilter
                    };
                    foreach (var option in options[(record.ResourceName, commandRecord.Ordinal, inputRecord.Ordinal)])
                    {
                        input.Options.Add(option.OptionKey, option.OptionValue);
                    }
                    input.ValidationErrors.Add(validationErrors[(record.ResourceName, commandRecord.Ordinal, inputRecord.Ordinal)].Select(error => error.ValidationError));
                    command.ArgumentInputs.Add(input);
                }
                resource.Commands.Add(command);
            }
#pragma warning restore CS0612
 
            yield return new StoredResource(resource, record.ReplicaIndex, record.ConsoleLogsLoaded);
        }
    }
 
    private static Value MaterializeValue(string jsonValue) => JsonParser.Default.Parse<Value>(jsonValue);
 
    private static void SetOptionalResourceFields(Resource resource, ResourceRecord record)
    {
        if (record.State is not null)
        {
            resource.State = record.State;
        }
        if (record.CreatedAtSeconds is not null)
        {
            resource.CreatedAt = CreateTimestamp(record.CreatedAtSeconds.Value, record.CreatedAtNanos);
        }
        if (record.StateStyle is not null)
        {
            resource.StateStyle = record.StateStyle;
        }
        if (record.StartedAtSeconds is not null)
        {
            resource.StartedAt = CreateTimestamp(record.StartedAtSeconds.Value, record.StartedAtNanos);
        }
        if (record.StoppedAtSeconds is not null)
        {
            resource.StoppedAt = CreateTimestamp(record.StoppedAtSeconds.Value, record.StoppedAtNanos);
        }
        if (record.IconName is not null)
        {
            resource.IconName = record.IconName;
        }
        if (record.IconVariant is not null)
        {
            resource.IconVariant = (IconVariant)record.IconVariant.Value;
        }
    }
 
    private static Timestamp CreateTimestamp(long seconds, int? nanos)
    {
        return new Timestamp { Seconds = seconds, Nanos = nanos ?? 0 };
    }
 
    private sealed record ResourceToSave(Resource Resource, int ReplicaIndex, bool ConsoleLogsLoaded);
 
    private sealed record ConsoleLogToInsert(int LineNumber, string Content, bool IsStdErr);
 
    private sealed record PropertyToSave(string ResourceName, int Ordinal, ResourceProperty Property);
 
    private sealed record CommandToSave(string ResourceName, int Ordinal, ResourceCommand Command, string? ParameterJsonValue);
 
    private sealed record StoredResource(Resource Resource, int ReplicaIndex, bool ConsoleLogsLoaded);
 
    private sealed class ResourceRecord
    {
        public required string ResourceName { get; init; }
        public required int ReplicaIndex { get; init; }
        public required string ResourceType { get; init; }
        public required string DisplayName { get; init; }
        public required string Uid { get; init; }
        public string? State { get; init; }
        public long? CreatedAtSeconds { get; init; }
        public int? CreatedAtNanos { get; init; }
        public string? StateStyle { get; init; }
        public long? StartedAtSeconds { get; init; }
        public int? StartedAtNanos { get; init; }
        public long? StoppedAtSeconds { get; init; }
        public int? StoppedAtNanos { get; init; }
        public required bool IsHidden { get; init; }
        public required bool SupportsDetailedTelemetry { get; init; }
        public string? IconName { get; init; }
        public int? IconVariant { get; init; }
        public required bool ConsoleLogsLoaded { get; init; }
    }
 
    private sealed class EnvironmentRecord
    {
        public required string ResourceName { get; init; }
        public required string Name { get; init; }
        public string? Value { get; init; }
        public required bool IsFromSpec { get; init; }
    }
 
    private sealed class UrlRecord
    {
        public required string ResourceName { get; init; }
        public string? EndpointName { get; init; }
        public required string FullUrl { get; init; }
        public required bool IsInternal { get; init; }
        public required bool IsInactive { get; init; }
        public required int DisplaySortOrder { get; init; }
        public required string DisplayName { get; init; }
    }
 
    private sealed class VolumeRecord
    {
        public required string ResourceName { get; init; }
        public required string Source { get; init; }
        public required string Target { get; init; }
        public required string MountType { get; init; }
        public required bool IsReadOnly { get; init; }
    }
 
    private sealed class HealthReportRecord
    {
        public required string ResourceName { get; init; }
        public int? Status { get; init; }
        public required string Key { get; init; }
        public required string Description { get; init; }
        public required string Exception { get; init; }
        public long? LastRunAtSeconds { get; init; }
        public int? LastRunAtNanos { get; init; }
    }
 
    private sealed class PropertyRecord
    {
        public required string ResourceName { get; init; }
        public required string Name { get; init; }
        public string? DisplayName { get; init; }
        public required string JsonValue { get; init; }
        public bool? IsSensitive { get; init; }
        public required bool IsHighlighted { get; init; }
        public int? SortOrder { get; init; }
    }
 
    private sealed class CommandRecord
    {
        public required string ResourceName { get; init; }
        public required int Ordinal { get; init; }
        public required string Name { get; init; }
        public required string DisplayName { get; init; }
        public string? ConfirmationMessage { get; init; }
        public string? ParameterJsonValue { get; init; }
        public required bool IsHighlighted { get; init; }
        public string? IconName { get; init; }
        public int? IconVariant { get; init; }
        public string? DisplayDescription { get; init; }
        public required int State { get; init; }
    }
 
    private sealed class InputRecord
    {
        public required string ResourceName { get; init; }
        public required int CommandOrdinal { get; init; }
        public required int Ordinal { get; init; }
        public required string Label { get; init; }
        public required string Placeholder { get; init; }
        public required int InputType { get; init; }
        public required bool Required { get; init; }
        public required string Value { get; init; }
        public required string Description { get; init; }
        public required bool EnableDescriptionMarkdown { get; init; }
        public required int MaxLength { get; init; }
        public required bool AllowCustomChoice { get; init; }
        public required bool Loading { get; init; }
        public required bool UpdateStateOnChange { get; init; }
        public required string Name { get; init; }
        public required bool Disabled { get; init; }
        public required long MaxFileSize { get; init; }
        public required bool AllowMultipleFiles { get; init; }
        public required string FileFilter { get; init; }
    }
 
    private sealed class OptionRecord
    {
        public required string ResourceName { get; init; }
        public required int CommandOrdinal { get; init; }
        public required int InputOrdinal { get; init; }
        public required string OptionKey { get; init; }
        public required string OptionValue { get; init; }
    }
 
    private sealed class RelationshipRecord
    {
        public required string ResourceName { get; init; }
        public required string RelatedResourceName { get; init; }
        public required string RelationshipType { get; init; }
    }
 
    private sealed class ValidationErrorRecord
    {
        public required string ResourceName { get; init; }
        public required int CommandOrdinal { get; init; }
        public required int InputOrdinal { get; init; }
        public required string ValidationError { get; init; }
    }
 
}