microsoft / microsoft/aspire

Options interface names depend on what else is loaded, so they aren't a stable part of a package's API

Open
#19,141 2 comments 0 reactions 0 assignees View on GitHub
area-codegen area-integrations area-polyglot triage:bot-seen
Dominant language
C#
Stars
6.3k
Forks
991
Avg merge
2d 15h
Merged PRs (30d)
196

Description

## Problem

TypeScript options interfaces are named after the method that produced them. `RegisterOptionsInterface` in `TypeScriptApiProjector` resolves a collision — two capabilities sharing a method name with optional parameters that can't merge — by giving the first registration the base name and suffixing later ones (`RunAsEmulatorOptions`, `RunAsEmulator1Options`).

Both halves of that are load-dependent. Whether a collision happens depends on which packages the app host references, and which side wins the base name depends on scan order. So the name a given package's method gets is not a function of that package.

This is reachable today. `Aspire.Hosting.Azure.EventHubs` and `Aspire.Hosting.Azure.ServiceBus` both expose:

```csharp
public static IResourceBuilder RunAsEmulator(
this IResourceBuilder builder,
Action>? configureContainer = null)

public static IResourceBuilder RunAsEmulator(
this IResourceBuilder builder,
Action>? configureContainer = null)
```

Same method name, same parameter name, different callback types — incompatible, so they cannot share an interface.

## Consequences

**`sdk generate`:** an app host referencing only ServiceBus gets `RunAsEmulatorOptions`. Add EventHubs and, depending on scan order, the same method may now be typed `RunAsEmulator1Options`. A name in the generated SDK can change because an unrelated package was added.

**`sdk export` (#19032):** each export runs in its own scanner app host referencing one package plus core and codegen, so no export ever sees a sibling collision. Both packages publish `RunAsEmulatorOptions` with incompatible members. aspire.dev concatenates declaration fragments and type-checks the result, so those two fragments conflict.

Note the compatible case is fine — TypeScript merges identical interface declarations, so fragments that agree are harmless. Only the incompatible collision breaks.

## Why the obvious fix doesn't work

Handing the exporter a wider context so it can reproduce generation's naming was tried in #19032 and backed out (d6d6a1b). The scanner app host is built from the requested package alone, so the wider context doesn't contain the sibling. It also made things worse: core *is* in every context, so it newly decided integration-vs-core collisions by scan order while the core export — resolved from a core-only context — wouldn't agree.

More fundamentally, there's no single "generated SDK" for an export to match, because generation's own names vary by app host.

## Suggested direction

Make the name a function of the capability's identity rather than of discovery. That has to be unconditional rather than collision-triggered, since whether a collision occurs is itself load-dependent — for example, always qualifying by the owning assembly.

That renames essentially every options interface in emitted TypeScript, so it's a breaking change to the SDK contract and needs a deliberate decision, including on the aspire.dev side that renders these names.

## Pointers

- `src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs` — `RegisterOptionsInterface`, `GetOptionsInterfaceName`, `AreOptionsCompatible`, `_capabilityOptionsInterfaceMap`
- `src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGenerationService.cs` — `ExportApi`
- `src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs` — builds the one-package scanner app host

Found while reviewing #19032.

Contributor guide

Open the contributing guide

Research direction

Start with RegisterOptionsInterface, GetOptionsInterfaceName, AreOptionsCompatible, and _capabilityOptionsInterfaceMap in src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs. Then trace ExportApi in src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGenerationService.cs and the scanner app host in src/Aspire.Cli/Commands/Sdk/SdkExportCommand.cs. Done means the naming decision is consistent for package generation and export, with the SDK contract and aspire.dev impact explicitly resolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, typescript
Domain
api, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.