CommonNetFuncs.ReinforcedTypings 4.4.0

dotnet add package CommonNetFuncs.ReinforcedTypings --version 4.4.0
                    
NuGet\Install-Package CommonNetFuncs.ReinforcedTypings -Version 4.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CommonNetFuncs.ReinforcedTypings" Version="4.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CommonNetFuncs.ReinforcedTypings" Version="4.4.0" />
                    
Directory.Packages.props
<PackageReference Include="CommonNetFuncs.ReinforcedTypings" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CommonNetFuncs.ReinforcedTypings --version 4.4.0
                    
#r "nuget: CommonNetFuncs.ReinforcedTypings, 4.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CommonNetFuncs.ReinforcedTypings@4.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CommonNetFuncs.ReinforcedTypings&version=4.4.0
                    
Install as a Cake Addin
#tool nuget:?package=CommonNetFuncs.ReinforcedTypings&version=4.4.0
                    
Install as a Cake Tool

CommonNetFuncs.ReinforcedTypings

License NuGet Version nuget

This project extends Reinforced.Typings with a fluent configuration hook that adds three capabilities on top of RT's normal attribute-based interface/class/enum export:

  • TsConst: Generates as const TypeScript object literals from public const/public static readonly fields on a static C# class - of any field type.
  • TsCollection: Generates as const TypeScript array-literal collections from public const/public static readonly array/collection fields on a static C# class, cross-referencing the real generated TypeScript type for any element type that is itself exported via [TsInterface]/[TsClass]/[TsEnum].
  • Valibot schema generation: Generates a companion Valibot validation schema (.schema.ts) next to the interface RT generates for any class decorated with both [TsInterface] and [GenerateValibotSchema], derived from the class's System.ComponentModel.DataAnnotations validation attributes.

All of the above honor the resolved [TsGlobal]/fluent Global(...) settings (WriteWarningComment, TabSymbol, NewLine, CamelCaseForProperties, UnresolvedToUnknown, UseModules, DiscardNamespacesWhenUsingModules, RootNamespace, ExportPureTypings) so the hand-written output matches the same conventions Reinforced.Typings itself would use for the rest of the project.

Contents


Setup

Reinforced.Typings runs an RtConfigurationMethod (configured in your consuming project's Reinforced.Typings.settings.xml) once per build, right before it exports every [TsInterface]/[TsClass]/[TsEnum] type it discovers. Point that setting at ReinforcedTypingsFluentConfig.Configure to enable everything in this package:


<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    
    <RtConfigurationMethod>CommonNetFuncs.ReinforcedTypings.ReinforcedTypingsFluentConfig.Configure</RtConfigurationMethod>
    <RtTargetDirectory>$(ProjectDir)TypeScriptModels</RtTargetDirectory>
    
  </PropertyGroup>
  <Target Name="AddReinforcedTypingsFluentConfigAssembly" BeforeTargets="ReinforcedTypingsGenerate">
    
    <ItemGroup>
      <RtAdditionalAssembly Include="@(ReferencePath)" Condition="'%(ReferencePath.FileName)' == 'CommonNetFuncs.ReinforcedTypings'" />
    </ItemGroup>
  </Target>
</Project>

Configure scans whichever assemblies your own RT settings are configured to export from (falling back to the assembly this package lives in if none are configured), so it behaves correctly whether it's referenced by one project or shared as a library across several. Hand-written TsConst/TsCollection output is written to the same target directory RT itself would use (RtTargetDirectory when dividing types among files, otherwise the directory portion of RtTargetFile), nested into namespace-mirroring subfolders using the same rules RT applies to its own generated files.


TsConst

Marks a static C# class whose public const/public static readonly fields - of any type (strings, numbers, booleans, Guid/date types, enums, arrays/collections, or class/record instances) - should be exported as a TypeScript as const object literal, along with a Key/Value type alias pair (keyof typeof X / union of value types).

By default (TsConstExportMode.All) every eligible field is exported unless it's decorated with [TsIgnoreConst]. Use [TsConst(TsConstExportMode.Selected)] to instead opt fields in individually via [TsExportConst].

TsConst Usage Examples

<details> <summary><h3>Usage Examples</h3></summary>

Export all fields, suppress one
using CommonNetFuncs.ReinforcedTypings.Constants;

[TsConst]
public static class RoleConstants
{
    public const string NmPkgPlAdminName = "NM Pkg PL Admin"; // exported

    [TsIgnoreConst]
    public const string InternalOnly = "internal"; // skipped
}

Generates RoleConstants.ts:

export const RoleConstants = {
	NmPkgPlAdminName: "NM Pkg PL Admin",
} as const;

export type RoleConstantsKey = keyof typeof RoleConstants;
export type RoleConstantsValue = (typeof RoleConstants)[RoleConstantsKey];
Export only selected fields
[TsConst(TsConstExportMode.Selected)]
public static class PolicyNames
{
    public const string InternalPolicy = "InternalPolicy"; // skipped - no [TsExportConst]

    [TsExportConst]
    public const string EditUsersPolicy = "EditUsersPolicy"; // exported
}
Non-string field types

TsConst isn't limited to strings - Guid/date types serialize as ISO strings, enums serialize as numbers (or strings when the enum carries [TsEnum(UseString = true)]), and nested arrays/collections and class/record instances serialize recursively as array/object literals (honoring [TsIgnore] on nested properties):

[TsConst]
public static class AppDefaults
{
    public static readonly Guid TenantId = new("11111111-1111-1111-1111-111111111111");
    public static readonly DateOnly LaunchDate = new(2024, 1, 2);
    public static readonly int[] AllowedPageSizes = [10, 25, 50];
}
export const AppDefaults = {
	TenantId: "11111111-1111-1111-1111-111111111111",
	LaunchDate: "2024-01-02",
	AllowedPageSizes: [10, 25, 50],
} as const;

</details>


TsCollection

Marks a static C# class whose public const/public static readonly array/collection fields (List<T>, T[], ICollection<T>, IEnumerable<T>, etc. - excluding string and dictionaries) should be exported as a TypeScript array-literal collections object, along with a CollectionsKey/CollectionsValue type alias pair.

By default (TsCollectionExportMode.All) every eligible field is exported unless it's decorated with [TsIgnoreCollection]. Use [TsCollection(TsCollectionExportMode.Selected)] to instead opt fields in individually via [TsExportCollection]. If a class has no eligible collection fields, no file is written for it.

Element types that are themselves exported via [TsInterface]/[TsClass]/[TsEnum] are cross-referenced using their real generated TypeScript name (honoring [TsInterface(AutoI = ...)]'s I-prefix convention) - imported from the sibling generated file when UseModules is enabled, or fully namespace-qualified otherwise. Anything else falls back to any (or unknown when UnresolvedToUnknown is set).

TsCollection Usage Examples

<details> <summary><h3>Usage Examples</h3></summary>

Export all fields, suppress one
using CommonNetFuncs.ReinforcedTypings.Collections;

[TsCollection]
public static class RoleConstants
{
    public static readonly string[] AllRoleNames = ["Admin", "Editor", "Viewer"]; // exported

    [TsIgnoreCollection]
    public static readonly string[] Deprecated = ["LegacyRole"]; // skipped
}

Generates RoleConstants.ts:

export const RoleConstantsCollections = {
	AllRoleNames: ["Admin", "Editor", "Viewer"] as string[],
} as const;

export type RoleConstantsCollectionsKey = keyof typeof RoleConstantsCollections;
export type RoleConstantsCollectionsValue =
	(typeof RoleConstantsCollections)[RoleConstantsCollectionsKey];
Export only selected fields
[TsCollection(TsCollectionExportMode.Selected)]
public static class PolicyNames
{
    public static readonly string[] NotExported = ["InternalPolicy"];

    [TsExportCollection]
    public static readonly string[] Exported = ["EditUsersPolicy", "ViewReportsPolicy"];
}
Collections of exported types
[TsEnum]
public enum Role { Admin, Editor, Viewer }

[TsInterface] // AutoI defaults to true -> generated as IUserSummary
public sealed class UserSummary
{
    public string Name { get; set; } = string.Empty;
}

[TsCollection]
public static class Seed
{
    public static readonly List<Role> DefaultRoles = [Role.Admin, Role.Viewer];
    public static readonly List<UserSummary> SampleUsers = [new() { Name = "Ada" }];
}

With UseModules enabled, Seed.ts imports the real generated types instead of falling back to any:

import type { Role } from "./Role";
import type { IUserSummary } from "./UserSummary";

export const SeedCollections = {
	DefaultRoles: [0, 2] as Role[],
	SampleUsers: [{ Name: "Ada" }] as IUserSummary[],
} as const;

export type SeedCollectionsKey = keyof typeof SeedCollections;
export type SeedCollectionsValue = (typeof SeedCollections)[SeedCollectionsKey];

</details>


Valibot Schema Generation

Apply [GenerateValibotSchema] alongside [TsInterface] on classes that represent form-submission models (i.e. classes with System.ComponentModel.DataAnnotations validation attributes that are used as inputs on the frontend). Read-only response/query models that don't need client-side validation should omit this attribute.

ReinforcedTypingsFluentConfig.Configure finds every type carrying both attributes and configures RT to run them through ValibotSchemaGenerator, a custom RT code generator that produces the standard interface and writes a companion <TypeName>.schema.ts file (next to the generated interface) containing:

  • export const <TypeName>Schema = v.object({ ... }) - a Valibot schema derived from the class's properties and their validation attributes.
  • export type <TypeName>Input = v.InferInput<typeof <TypeName>Schema>;
  • export const <TypeName>Labels: Record<string, string> = { ... } - a display-label map, using [DisplayName] where present and falling back to a space-split version of the property name (e.g. PhoneNumber"Phone Number").

Recognized System.ComponentModel.DataAnnotations attributes: [Required], [MaxLength], [MinLength], [StringLength], [Range], [RegularExpression], [EmailAddress], [Url], [Phone], [CreditCard]. Properties marked [TsIgnore] are skipped in both the schema and the labels map. Nested complex properties recurse into an inline v.object({...}) schema (with cycle detection falling back to v.any()), and array/list properties emit v.array(...).

Valibot Usage Examples

<details> <summary><h3>Usage Examples</h3></summary>

Basic form model
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using CommonNetFuncs.ReinforcedTypings.Valibot;
using Reinforced.Typings.Attributes;

[TsInterface]
[GenerateValibotSchema]
public sealed class CreateUserModel
{
    [Required]
    [MaxLength(50)]
    [DisplayName("Full Name")]
    public string Name { get; set; } = string.Empty;

    [EmailAddress]
    public string Email { get; set; } = string.Empty;

    [Range(18, 120)]
    public int? Age { get; set; }
}

Generates CreateUserModel.schema.ts:

import * as v from "valibot";

export const CreateUserModelSchema = v.object({
	Name: v.pipe(v.string(), v.maxLength(50)),
	Email: v.pipe(v.string(), v.email()),
	Age: v.optional(
		v.nullable(v.pipe(v.number(), v.minValue(18), v.maxValue(120))),
	),
});

export type CreateUserModelInput = v.InferInput<typeof CreateUserModelSchema>;

export const CreateUserModelLabels: Record<string, string> = {
	Name: "Full Name",
	Email: "Email",
	Age: "Age",
};
SubsetOf-bound input models

Types decorated with [SubsetOf(typeof(SourceModel))] (e.g. via CommonNetFuncs.SubsetModelBinder) inherit their validation attributes from the source type when generating the schema, so a trimmed-down "bound" model doesn't need to redeclare them:

public sealed class User
{
    [Required]
    [MaxLength(50)]
    public string Name { get; set; } = string.Empty;
}

[TsInterface]
[GenerateValibotSchema]
[SubsetOf(typeof(User))]
public sealed class BoundUser
{
    public string Name { get; set; } = string.Empty; // pulls [Required]/[MaxLength(50)] from User.Name
}

</details>

Installation

Install via NuGet:

dotnet add package CommonNetFuncs.ReinforcedTypings

License

This project is licensed under the MIT License - see the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.4.0 97 9/2/2026
4.3.2 98 8/27/2026
4.3.1 107 8/11/2026
4.3.0 105 8/3/2026
4.2.3 212 8/3/2026
4.2.1 111 7/31/2026
4.2.0 110 7/21/2026