TypeContractor 0.11.0-pre.2

This is a prerelease version of TypeContractor.
There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global TypeContractor --version 0.11.0-pre.2                
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest # if you are setting up this repo
dotnet tool install --local TypeContractor --version 0.11.0-pre.2                
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=TypeContractor&version=0.11.0-pre.2&prerelease                
nuke :add-package TypeContractor --version 0.11.0-pre.2                

TypeContractor

Looks at one or more assemblies containing contracts and translates to TypeScript interfaces and enums

Goals

  1. Take one or more assemblies and reflect to find relevant types and their dependencies that should get a TypeScript definition published.

  2. Perform replacements on the names to strip away prefixes

    MyCompany.SystemName.Common is unnecessary to have in the output path. We can strip the common prefix and get api/Modules/MyModule/SomeRequest.ts instead of api/MyCompany/SystemName/Common/Modules/MyModule/SomeRequest.ts.

  3. Map custom types to their TypeScript-friendly counterparts if necessary.

    For example, say your system has a custom Money type that maps down to number. If we don't configure that manually, it will create the Money interface, which only contains amount as a number. That's both cumbersome to work with, as well as wrong, since the serialization will (most likely) serialize Money as a number.

Setup and configuration

To accomplish the same configuration and results as described under Goals, create Contractor like this:

Contractor.FromDefaultConfiguration(configuration => configuration
            .AddAssembly("MyCompany.SystemName.Common", "MyCompany.SystemName.Common.dll")
            .AddCustomMap("MyCompany.SystemName.Common.Types.Money", DestinationTypes.Number)
            .StripString("MyCompany.SystemName.Common")
            .SetOutputDirectory(Path.Combine(Directory.GetCurrentDirectory(), "api")));

Run manually

Get an instance of Contractor and call contractor.Build();

Integrate with ASP.NET Core

The easiest way is to TypeContractor, using dotnet tool install --global typecontractor. This adds typecontractor as an executable installed on the system and always available.

Run typecontractor to get a list of available options.

This tool reflects over the main assembly provided and finds all controllers (that inherits from Microsoft.AspNetCore.Mvc.ControllerBase). Each controller is reflected over in turn, and finds all public methods that returns ActionResult<T>. The ActionResult<T> is unwrapped and the inner type T is added to a list of candidates.

Additionally, the public methods returning an ActionResult<T> or a plain ActionResult will have their parameters analyzed as well. Anything that's not a builtin type will be added to the list of candidates.

Meaning if you have a method looking like:

public async Task<ActionResult> Create([FromBody] CreateObjectDto request, CancellationToken cancellationToken)
{
   ...
}

we will add CreateObjectDto to the list of candidates. System.Threading.CancellationToken is a builtin type (currently, this means it is defined inside System.) and will be ignored. Same with other basic types such as int, Guid, IEnumerable<T> and so on.

For each candidate, we apply stripping and replacements and custom mappings and write everything to the output files.

Installing locally

Instead of installing the tool globally, you can also add it locally to the project that is going to use it. This makes it easier to make sure everyone who wants to run the project have it available.

For the initial setup, run:

dotnet new tool-manifest dotnet tool install typecontractor

in your Web-project.

Whenever new users check out the repository, they can run dotnet tool restore and get everything you need installed.

Running automatically

In your Web.csproj add a target that calls the tool after build. Example:

<Target Name="GenerateTypes" AfterTargets="Build">
  <Message Importance="high" Text="Running in CI, not generating new types" Condition="'$(AGENT_ID)' != ''" />
  <Message Importance="high" Text="Generating API types" Condition="'$(AGENT_ID)' == ''" />
  <Exec 
      Condition="'$(AGENT_ID)' == ''"
      ContinueOnError="true"
      Command="typecontractor --assembly $(OutputPath)$(AssemblyName).dll --output $(MSBuildThisFileDirectory)\App\src\api --clean smart --replace My.Web.App.src.modules:App --replace Infrastructure.Common:Common --strip MyCompany" />
  <Message Importance="high" Text="Finished generating API types" Condition="'$(AGENT_ID)' == ''" />
</Target>

This will only run in non-CI environments (tested on Azure DevOps). Adjust the environment variable as needed. You don't want new types to be generated on the build machine, that should use whatever existed when the developer did their thing.

It will first:

  1. Strip out MyCompany. from the beginning of namespaces
  2. Replace My.Web.App.src.modules with App
  3. Replace Infrastructure.Common with Common

by looking at the configured assembly. The resulting files are placed in Web\App\src\api.

When running with --clean smart, which is the default, it first generates the updated or newly created files. After that, it looks in the output directory and removes every file and directory that are no longer needed.

Other options are:

  • none -- which as the name suggests, does no cleanup at all. That part is left as an exercise to the user.
  • remove -- which removes the entire output directory before starting the file generation. This is probably the quickest, but some tools that are watching for changes does not always react so well to having files suddenly disappear and reappear.

Integration with Zod

An experimental option to generate Zod schemas exists behind the --build-zod-schemas flag. This causes each generated TypeScript file to also have a <TypeName>Schema generated that can be integrated with Zod. Currently no support for validations, but that might come in a future update.

Example:

public class PaymentsPerYearResponse
{
    public IEnumerable<int> Years { get; set; } = [2023, 2024];
    public Dictionary<int, int> PaymentsPerYear { get; set; } = new Dictionary<int, int>
    {
        { 2024, 4 },
        { 2023, 12 }
    };
}

generates

import { z } from 'zod';

export interface PaymentsPerYearResponse {
  years: number[];
  paymentsPerYear: { [key: number]: number };
}

export const PaymentsPerYearResponseSchema = z.object({
  years: z.array(z.number()),
  paymentsPerYear: z.record(z.string(), z.number()), // JavaScript is very stringy (https://zod.dev/?id=records)
});

and can be integrated using something similar to

const response = await this.http.fetch('api/paymentsPerYear', { signal: cancellationToken });
const input = await response.json();
return PaymentsPerYearResponseSchema.parse(input);

which will throw a ZodError if input fails to parse against the schema. Otherwise it returns a cleaned up version of input.

Future improvements

  • Kebab-case output files and directories
  • Better documentation
  • Better performance -- if this should run on build, it can't take forever
  • Possible to add types to exclude?
  • Improve method for finding AspNetCore framework DLLs
    • Possible to provide a manual path, so not a priority
  • Work with Hot Reload?
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last updated
0.14.0 90 11/17/2024
0.13.1 100 11/11/2024
0.13.0 85 11/9/2024
0.12.7 111 10/10/2024
0.12.6 105 9/18/2024
0.12.5 89 9/18/2024
0.12.4 93 9/18/2024
0.12.3 97 9/17/2024
0.12.2 96 9/16/2024
0.12.1 93 9/13/2024
0.12.0 103 9/13/2024
0.12.0-pre.4 59 8/30/2024
0.12.0-pre.3 48 8/29/2024
0.12.0-pre.2 47 8/28/2024
0.12.0-pre.1 56 8/28/2024
0.11.0 137 6/21/2024
0.11.0-pre.2 57 4/28/2024
0.11.0-pre.1 64 4/27/2024
0.10.0 142 4/21/2024
0.9.2 213 1/28/2024
0.9.1 206 1/7/2024
0.9.0 212 1/5/2024
0.8.1 197 12/24/2023
0.8.0 242 12/5/2023
0.7.0 242 9/17/2023
0.6.0 261 9/2/2023