| File: NativeProviderTypeScriptProjection.cs | Web Access |
| Project: src\src\Aspire.Hosting.Native.CodeGeneration\Aspire.Hosting.Native.CodeGeneration.csproj (Aspire.Hosting.Native.CodeGeneration) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text; using System.Text.Json; using Aspire.Hosting.Native.Tooling; using Aspire.TypeSystem; namespace Aspire.Hosting.Native.CodeGeneration; /// <summary>Emits standalone primitive and handle wrappers from parsed provider contracts.</summary> internal static class NativeProviderTypeScriptProjection { public static async Task GenerateAsync(string manifestPath, string output, IReadOnlyList<AtsTypeInfo> coreHandles) { var exports = NativeProviderContractReader.Read(await File.ReadAllTextAsync(manifestPath).ConfigureAwait(false)); var code = Generate(exports, coreHandles); Directory.CreateDirectory(output); await File.WriteAllTextAsync(Path.Combine(output, "provider-client.mts"), code).ConfigureAwait(false); } internal static string Generate(IReadOnlyList<NativeProviderExport> exports, IReadOnlyList<AtsTypeInfo> coreHandles) { var methods = exports.Where(export => export.LifecyclePhase is null).Select(export => export.Capability).ToArray(); var types = methods.SelectMany(method => method.Parameters.Select(parameter => parameter.Type) .Append(method.ReturnType)).OfType<AtsTypeRef>().Where(type => type.Category == AtsTypeCategory.Handle) .Select(type => type.TypeId).Distinct(StringComparer.Ordinal).ToArray(); var coreIds = coreHandles.Select(type => type.AtsTypeId).ToHashSet(StringComparer.Ordinal); var coreTypes = types.Where(coreIds.Contains).ToArray(); var ownTypes = types.Except(coreTypes, StringComparer.Ordinal).ToArray(); var code = new StringBuilder(""" // Generated offline from portable provider metadata. Do not edit. import { type AspireClientRpc, type MarshalledHandle, registerHandleWrapper, wrapIfHandle } from './transport.mjs'; """); if (coreTypes.Length > 0) { code.AppendLine("import type { " + string.Join(", ", coreTypes.Select(Name)) + " } from './aspire.mjs';"); } code.AppendLine("export class IntegrationClient {"); code.AppendLine(" constructor(private readonly client: AspireClientRpc) {}"); foreach (var method in methods.Where(method => method.TargetTypeId is null)) { AppendMethod(method, instance: false); } code.AppendLine("}"); foreach (var type in ownTypes) { var name = Name(type) + "Client"; code.AppendLine("export class " + name + " {"); code.AppendLine(" constructor(private readonly handle: MarshalledHandle, private readonly client: AspireClientRpc) {}"); code.AppendLine(" toJSON(): MarshalledHandle { return this.handle; }"); foreach (var method in methods.Where(method => method.TargetTypeId == type)) { AppendMethod(method, instance: true); } code.AppendLine("}"); code.AppendLine("registerHandleWrapper(" + Quote(type) + ", (handle, client) => new " + name + "(handle, client));"); } return code.ToString(); void AppendMethod(AtsCapabilityInfo method, bool instance) { var target = method.TargetParameterName; var parameters = method.Parameters.Where(parameter => !instance || parameter.Name != target).ToArray(); var signature = string.Join(", ", parameters.Select(parameter => parameter.Name + ": " + TypeName(parameter.Type ?? throw new InvalidDataException("Provider parameters require a type.")))); var returnType = TypeName(method.ReturnType); code.AppendLine(" async " + method.MethodName + "(" + signature + "): Promise<" + returnType + "> {"); code.AppendLine(" const result = await this.client.invokeCapability(" + Quote(method.CapabilityId) + ", {"); if (instance) { code.AppendLine(" " + target + ": this.handle,"); } foreach (var parameter in parameters) { code.AppendLine(" " + parameter.Name + ","); } code.AppendLine(" });"); if (returnType != "void") { code.AppendLine(" return wrapIfHandle(result, this.client) as " + returnType + ";"); } code.AppendLine(" }"); } string TypeName(AtsTypeRef type) { var id = type.TypeId; return type.Category switch { AtsTypeCategory.Handle => Name(id) + (ownTypes.Contains(id) ? "Client" : ""), AtsTypeCategory.Primitive => id switch { "string" => "string", "bool" => "boolean", "int" or "long" => "number", "void" => "void", _ => throw new NotSupportedException($"Unsupported provider primitive '{id}'.") }, _ => throw new NotSupportedException($"Unsupported provider type category '{type.Category}'.") }; } } private static string Name(string id) => id[(id.LastIndexOf('.') + 1)..].Split('/').Last(); private static string Quote(string value) => JsonSerializer.Serialize(value); }