distributed_cli 4.0.0

The `distributed` CLI for Distributed applications: contracts check/accept, scaffold projects, describe manifests, compile clients, and render schema artifacts. Also a library so other CLIs (e.g. hops) can mount its commands.
Documentation
use super::super::graphql::{
    CompiledFilterInputTarget, CompiledInputDefinition, CompiledInputType, CompiledOperation,
    CompiledVariableCodec,
};
use super::super::manifest::ClientManifest;
use super::super::ClientCompileError;
use super::artifact::render_operation_artifact_json;
use super::common::json_string;
use super::typescript::{render_data_type, render_variables_type};

pub(super) fn render_operation_module(
    operation: &CompiledOperation,
    manifest: &ClientManifest,
) -> Result<String, ClientCompileError> {
    let variables_name = format!("{}_Variables", operation.export_name);
    let data_name = format!("{}_Data", operation.export_name);
    let variables = render_variables_type(operation, &variables_name)?;
    let data = render_data_type(operation, &data_name)?;
    let artifact_json = render_operation_artifact_json(operation, manifest)?;
    let replica_value_import =
        variable_codec_uses_replica_value(&operation.variable_codec).then_some(", ReplicaValue");
    Ok(format!(
        "/** GENERATED by distributed client. Do not edit. */\n\
         import type {{ ReplicaOperationArtifact{} }} from '@hops-ops/distributed/replica';\n\
         \n\
         {variables}\n\
         \n\
         {data}\n\
         \n\
         /** Exact canonical query bytes sent to the server. */\n\
         export const {}Document = {};\n\
         \n\
         /** Typed normalized-replica operation descriptor. */\n\
         export const {}: ReplicaOperationArtifact<{}, {}> = {};\n",
        replica_value_import.unwrap_or_default(),
        operation.export_name,
        json_string(&operation.query_document)?,
        operation.export_name,
        data_name,
        variables_name,
        artifact_json,
    ))
}

fn variable_codec_uses_replica_value(codec: &CompiledVariableCodec) -> bool {
    fn input_type_uses_replica_value(input: &CompiledInputType) -> bool {
        match input {
            CompiledInputType::Scalar { codec, .. } => codec == "json",
            CompiledInputType::List { item, .. } => input_type_uses_replica_value(item),
            CompiledInputType::Enum { .. } | CompiledInputType::Input { .. } => false,
        }
    }

    codec.variables.values().any(input_type_uses_replica_value)
        || codec.inputs.values().any(|input| match input {
            CompiledInputDefinition::Filter {
                fields,
                relationships,
                ..
            } => {
                fields.iter().any(|field| field.codec == "json")
                    || relationships.iter().any(|relationship| {
                        matches!(&relationship.target, CompiledFilterInputTarget::Opaque)
                    })
            }
            CompiledInputDefinition::Order { .. } => false,
        })
}