microsoft/aspire · error · InvalidOperationException

Resource types ' ' all map to the generated TypeScript name…

Error message

Resource types '{typeId}' all map to the generated TypeScript name '{group.Key}', but they are not a concrete type and its interfaces.

What it means

Aspire's TypeScript API generator collapses all resource types that map to the same generated TypeScript name into a single declaration. It allows a concrete type plus its interfaces to share a name, but when two or more type IDs with unrelated type relationships collide on the same generated name, the projector cannot pick a single valid representation and throws this InvalidOperationException at generation time.

Solutions

  1. Rename one of the colliding resource types so each TypeId produces a distinct TypeScript name
  2. If one type is genuinely an interface of the other, verify the type hierarchy (implements/inherits) is declared so the projector can treat them as concrete + interfaces
  3. Check the error message for the exact colliding type IDs and adjust the least-used one
  4. Ensure no two custom resource builders register the same generated name via different type IDs

Example fix

// before
var builder = appBuilder.AddResource(new MyRedisResource("cache"));   // typeId: MyRedis
var builder2 = appBuilder.AddResource(new MyRedisResource2("cache2")); // typeId: my-redis -> same TS name
// after
var builder = appBuilder.AddResource(new MyRedisResource("cache"));    // typeId: MyRedis -> MyRedis
var builder2 = appBuilder.AddResource(new MyRedisClusterResource("cache2")); // distinct TS name
Defensive patterns

Strategy: validation

Validate before calling

var tsNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var resource in resourceTypes)
{
    if (!tsNames.Add(GenerateTypeScriptName(resource.TypeId)))
        throw new InvalidOperationException($"Type '{resource.TypeId}' collides on generated TypeScript name with an unrelated type.");
}

Prevention

When it happens

Trigger: Defining two or more custom resource types (different TypeIds) whose generated TypeScript names normalize to the same identifier — e.g. types named 'MyRedis' and 'my-redis' or casing/prefix collisions — and building a project that runs the TypeScript code generation.

Common situations: Renaming a resource type so it now collides with an existing one; adding a second custom resource that differs only by case or separators; copying a resource definition from a sample without changing the generated name.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/880a7b6c11a116c7. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptApiProjector.cs:2846

            .ThenBy(builder => builder.BuilderClassName)
            .GroupBy(builder => builder.BuilderClassName, StringComparer.Ordinal)
            .Select(group =>
            {
                var candidates = group
                    .OrderBy(builder => builder.IsInterface)
                    .ThenBy(builder => builder.TypeId, StringComparer.Ordinal)
                    .ToList();
                var retainedBuilder = candidates[0];
                var unrelatedBuilder = candidates
                    .Skip(1)
                    .FirstOrDefault(candidate => !IsBuilderAlias(retainedBuilder, candidate));

                if (unrelatedBuilder is not null)
                {
                    var collidingTypeIds = candidates
                        .Select(candidate => candidate.TypeId)
                        .Order(StringComparer.Ordinal);
                    throw new InvalidOperationException(
                        $"Resource types {string.Join(", ", collidingTypeIds.Select(typeId => $"'{typeId}'"))} " +
                        $"all map to the generated TypeScript name '{group.Key}', but they are not a concrete type and its interfaces.");
                }

                return retainedBuilder;
            })
            .ToList();
    }

    private static void SortOptionsInterfaceCollisionsByCapabilityIdentity(List<AtsCapabilityInfo> capabilities)
    {
        // Reorder only colliding option-interface slots. Sorting every capability would rewrite
        // long-established source order for methods unrelated to the collision.
        var collisionGroups = capabilities
            .Select((capability, index) => (Capability: capability, Index: index))
            .Where(entry =>
            {
                var (_, optionalParameters) = SeparateParameters(entry.Capability.Parameters);

View on GitHub (pinned to 25830f84bd)