Rystem.RepositoryFramework.TypescriptGenerator 10.0.6

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global Rystem.RepositoryFramework.TypescriptGenerator --version 10.0.6
                    
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 Rystem.RepositoryFramework.TypescriptGenerator --version 10.0.6
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Rystem.RepositoryFramework.TypescriptGenerator&version=10.0.6
                    
nuke :add-package Rystem.RepositoryFramework.TypescriptGenerator --version 10.0.6
                    

Rystem TypeScript Generator

CLI tool to generate TypeScript types and services from C# Rystem Repository/CQRS models.

Installation

dotnet tool install -g Rystem.RepositoryFramework.TypescriptGenerator

Local Installation (per-project)

dotnet new tool-manifest # if you don't have one already
dotnet tool install Rystem.RepositoryFramework.TypescriptGenerator

Update

# Global
dotnet tool update -g Rystem.RepositoryFramework.TypescriptGenerator

# Local
dotnet tool update Rystem.RepositoryFramework.TypescriptGenerator

Uninstall

# Global
dotnet tool uninstall -g Rystem.RepositoryFramework.TypescriptGenerator

# Local
dotnet tool uninstall Rystem.RepositoryFramework.TypescriptGenerator

Usage

Basic Syntax

rystem-ts generate --dest <destination> --models <model-definitions> [options]

Options

Option Alias Required Description
--dest -d Destination folder for generated TypeScript files
--models -m Repository definitions (see format below)
--project -p Path to C# project (.csproj) or assembly (.dll)
--overwrite Overwrite existing files (default: true)
--include-deps Include project dependencies in type scanning (default: false)
--deps-prefix Only load dependencies starting with this prefix (e.g., MyCompany.)

Model Definition Format

The --models option accepts repository definitions in the following format:

"{Model,Key,Type,Factory,BackendFactory},{Model2,Key2,Type2,Factory2,}"

Fields:

  • ModelName: Name of the C# entity class. Can be simple (Calendar) or fully qualified (Fantacalcio.Domain.Calendar)
  • KeyName: Name of the key type. Can be simple (LeagueKey) or fully qualified (Fantacalcio.Domain.LeagueKey)
  • RepositoryType: One of Repository, Query, or Command
  • FactoryName: Client-side name used in RepositoryLocator.{factoryName} (optional, defaults to ModelName)
  • BackendFactoryName: Backend factory name used in API path (optional)

API Path Generation:

  • If BackendFactoryName is empty (e.g., {Rank,RankKey,Repository,rank,}) → path = Rank (just ModelName)
  • If BackendFactoryName is set (e.g., {Rank,RankKey,Repository,rank,rank}) → path = Rank/rank (ModelName/BackendFactory)

Note: If multiple types with the same name exist in different namespaces, you must use the fully qualified name (with namespace) to avoid ambiguity.

Examples

Single Repository (no backend factory - path will be just ModelName)
rystem-ts generate \
  --dest ./src/api \
  --models "{User,Guid,Repository,users,}"

This generates x.path = 'User' and x.name = 'users'.

With Backend Factory Name (path will be ModelName/BackendFactory)
rystem-ts generate \
  --dest ./src/api \
  --models "{User,Guid,Repository,users,users}"

This generates x.path = 'User/users' and x.name = 'users'.

Multiple Repositories with Mixed Backend Factories
rystem-ts generate \
  --dest ./src/api \
  --models "{Calendar,LeagueKey,Repository,serieA,serieA},{Team,Guid,Query,teams,},{Match,int,Command,matches,matches}"

This generates:

  • Calendar: x.path = 'Calendar/serieA', x.name = 'serieA'
  • Team: x.path = 'Team', x.name = 'teams'
  • Match: x.path = 'Match/matches', x.name = 'matches'
With Fully Qualified Names (Namespaces)

Use fully qualified names when you have types with the same name in different namespaces:

rystem-ts generate \
  --dest ./src/api \
  --models "{Fantacalcio.Domain.Calendar,Fantacalcio.Domain.LeagueKey,Repository,serieA}"
With Generic Types

The tool supports generic types with both user-friendly and .NET reflection syntax:

User-friendly syntax (recommended):

rystem-ts generate \
  --dest ./src/api \
  --models "{EntityVersions<Timeline>,Guid,Repository,versionedtimelines,}"

.NET Reflection syntax (auto-detected):

rystem-ts generate \
  --dest ./src/api \
  --models "{EntityVersions`1[[GhostWriter.Business.Timeline]],Guid,Repository,versionedtimelines,}"

Both syntaxes will generate:

// TypeScript output
export interface EntityVersions<Timeline> {
  // ...
}

Fully qualified generic types:

rystem-ts generate \
  --dest ./src/api \
  --models "{MyCompany.Infrastructure.EntityVersions<MyCompany.Domain.Timeline>,Guid,Repository,versionedtimelines,}"
With Specific Project
rystem-ts generate \
  --dest ./src/api \
  --models "{Product,int,Repository,products}" \
  --project ./src/MyApp.Core/MyApp.Core.csproj
Disable Overwrite
rystem-ts generate \
  --dest ./src/api \
  --models "{Order,Guid,Repository,orders}" \
  --overwrite false
With Project Dependencies

When your models reference types from other projects or NuGet packages:

rystem-ts generate \
  --dest ./src/api \
  --models "{Order,OrderKey,Repository,orders,orders}" \
  --project ./src/MyApp.Api/MyApp.Api.csproj \
  --include-deps

This will scan all DLLs in the output directory (except system assemblies).

With Dependency Prefix Filter

To only load dependencies from your company/organization:

rystem-ts generate \
  --dest ./src/api \
  --models "{Order,OrderKey,Repository,orders,orders}" \
  --project ./src/MyApp.Api/MyApp.Api.csproj \
  --include-deps \
  --deps-prefix "MyCompany."

This will only load:

  • MyCompany.Core.dll
  • MyCompany.Domain.dll
  • Newtonsoft.Json.dll (skipped)
  • Microsoft.Extensions.dll (skipped)

Generated Output

The tool generates the following structure:

📁 <destination>/
├── 📁 types/
│   ├── calendar.ts           # Raw + Clean interfaces + Mappers
│   ├── leaguekey.ts
│   ├── team.ts
│   └── dayofweek.ts          # Enums
├── 📁 transformers/
│   ├── CalendarTransformer.ts    # ITransformer<Calendar>
│   ├── LeagueKeyTransformer.ts   # ITransformer<LeagueKey>
│   └── index.ts
├── 📁 bootstrap/
│   └── repositorySetup.ts    # RepositoryServices configuration with transformers
└── 📁 services/
    └── repositoryLocator.ts  # RepositoryLocator.{factoryName} -> IRepository

Generated Types

Types (types/*.ts)

For each C# model, the tool generates:

// Raw interface (matches JSON from API)
export interface CalendarRaw {
  league_id: string;           // Uses JsonPropertyName if present
  start_date: string;          // Dates as strings
  teams: TeamRaw[];
}

// Clean interface (TypeScript-friendly)
export interface Calendar {
  leagueId: string;            // camelCase names
  startDate: Date;             // Proper Date type
  teams: Team[];
}

// Mapper function
export function mapCalendar(raw: CalendarRaw): Calendar {
  return {
    leagueId: raw.league_id,
    startDate: new Date(raw.start_date),
    teams: raw.teams.map(mapTeam),
  };
}
Common Types (services/common.ts)
export interface Entity<T, TKey> {
  key: TKey;
  value: T | null;
}

export interface State<T> {
  isOk: boolean;
  message?: string;
  value?: T;
}

export interface Page<T, TKey> {
  items: Entity<T, TKey>[];
  totalCount: number;
  pageSize: number;
  pageIndex: number;
  totalPages: number;
}

export interface BatchOperation<T, TKey> {
  command: 'insert' | 'update' | 'delete';
  key: TKey;
  value?: T;
}
Services (services/*.service.ts)
export class SerieAService {
  private baseUrl: string;
  
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  // Query methods
  async get(key: LeagueKey): Promise<Calendar | null> { ... }
  async query(options?: QueryOptions): Promise<Entity<Calendar, LeagueKey>[]> { ... }
  async page(pageIndex: number, pageSize: number): Promise<Page<Calendar, LeagueKey>> { ... }
  async exist(key: LeagueKey): Promise<State<boolean>> { ... }
  async count(): Promise<number> { ... }

  // Command methods (Repository/Command only)
  async insert(key: LeagueKey, value: Calendar): Promise<State<Calendar>> { ... }
  async update(key: LeagueKey, value: Calendar): Promise<State<Calendar>> { ... }
  async delete(key: LeagueKey): Promise<State<boolean>> { ... }
  async batch(operations: BatchOperation<Calendar, LeagueKey>[]): Promise<BatchResult[]> { ... }
}
Transformers (transformers/*.ts)

Transformers implement ITransformer<T> from rystem.repository.client to convert between Raw and Clean types:

import type { ITransformer } from 'rystem.repository.client';
import type { Calendar, CalendarRaw } from '../types/calendar';
import { mapRawCalendarToCalendar, mapCalendarToRawCalendar } from '../types/calendar';

export const CalendarTransformer: ITransformer<Calendar> = {
  fromPlain: (plain: CalendarRaw): Calendar => mapRawCalendarToCalendar(plain),
  toPlain: (instance: Calendar): CalendarRaw => mapCalendarToRawCalendar(instance),
};
Bootstrap Setup (bootstrap/repositorySetup.ts)

The bootstrap file configures RepositoryServices with transformers for automatic JSON conversion:

import { RepositoryServices } from 'rystem.repository.client';
import type { RepositoryEndpoint } from 'rystem.repository.client';
import { CalendarTransformer } from '../transformers/CalendarTransformer';
import { LeagueKeyTransformer } from '../transformers/LeagueKeyTransformer';

export interface RepositoryConfig {
  baseUrl: string;
  headersEnricher?: (...) => Promise<HeadersInit>;
  errorHandler?: (...) => Promise<boolean>;
}

export const setupRepositoryServices = (config: RepositoryConfig): void => {
  const services = RepositoryServices.Create(config.baseUrl);

  services.addRepository<Calendar, LeagueKey>(x => {
    x.name = 'calendar';
    x.path = 'calendar';
    x.transformer = CalendarTransformer;       // Auto-converts model
    x.keyTransformer = LeagueKeyTransformer;   // Auto-converts key
    x.complexKey = true;
    if (config.headersEnricher) x.addHeadersEnricher(config.headersEnricher);
    if (config.errorHandler) x.addErrorHandler(config.errorHandler);
  });
};
Repository Locator (repositoryLocator.ts)

Provides strongly-typed access to all configured repositories:

import { RepositoryServices } from 'rystem.repository.client';
import type { IRepository, IQuery, ICommand } from 'rystem.repository.client';
import type { Calendar } from './types/calendar';
import type { LeagueKey } from './types/leaguekey';

export const RepositoryLocator = {
  get calendar(): IRepository<Calendar, LeagueKey> {
    return RepositoryServices.Repository<Calendar, LeagueKey>('calendar');
  },
  get teams(): IQuery<Team, string> {
    return RepositoryServices.Query<Team, string>('teams');
  },
  get orders(): ICommand<Order, number> {
    return RepositoryServices.Command<Order, number>('orders');
  },
} as const;

Usage:

import { setupRepositoryServices } from './bootstrap/repositorySetup';
import { RepositoryLocator } from './repositoryLocator';

// 1. Setup once at app startup
setupRepositoryServices({
  baseUrl: 'https://api.example.com/api/',
  headersEnricher: async () => ({
    Authorization: `Bearer ${getToken()}`
  }),
  errorHandler: async (endpoint, uri, method, headers, body, err) => {
    if (err?.status === 401) {
      await refreshToken();
      return true; // Retry
    }
    return false;
  }
});

// 2. Use anywhere in your app with full type safety
const calendar = await RepositoryLocator.calendar.get({ leagueId: 'serie-a', season: 2024 });
const allTeams = await RepositoryLocator.teams.query().toListAsync();
await RepositoryLocator.orders.insert({ orderId: 123 }, { /* order data */ });

Repository Types

Type Query Methods Command Methods
Repository ✅ get, query, page, exist, count ✅ insert, update, delete, batch
Query ✅ get, query, page, exist, count
Command ✅ insert, update, delete, batch

C# Model Requirements

Basic Model

public class User
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedAt { get; set; }
}

With JsonPropertyName (for Raw types)

public class User
{
    [JsonPropertyName("user_id")]
    public Guid Id { get; set; }
    
    [JsonPropertyName("full_name")]
    public string Name { get; set; }
    
    [JsonPropertyName("created_at")]
    public DateTime CreatedAt { get; set; }
}

Custom Key Types

public record LeagueKey(string LeagueId, int Season);

Enums

public enum OrderStatus
{
    Pending = 0,
    Processing = 1,
    Completed = 2,
    Cancelled = 3
}

Integration with Rystem Repository API

This tool is designed to work with Rystem.RepositoryFramework.Api.Server.

Server Setup

// Program.cs
builder.Services.AddRepository<Calendar, LeagueKey>(settings =>
{
    settings.WithEntityFramework<AppDbContext>();
});

// Expose as REST API
app.UseEndpointsForRepositoryPattern();

Client Usage

// Setup (once at app startup)
Services.configure({ 
  baseUrl: 'https://localhost:5001/api' 
});

// Use anywhere in your app
const calendars = await Services.serieA.query();
const calendar = await Services.serieA.get({ leagueId: 'serie-a', season: 2024 });

// Create/Update
await Services.serieA.insert(
  { leagueId: 'serie-a', season: 2025 },
  { startDate: new Date(), teams: [] }
);

Troubleshooting

"Project file not found"

Make sure the --project path is correct, or run the command from a directory containing a .csproj file.

"Multiple .csproj files found"

Specify which project to use with --project:

rystem-ts generate --dest ./src/api --models "{User,Guid,Repository,users}" --project ./src/MyApp.Core.csproj

"Model not found in assembly"

Ensure:

  1. The model class is public
  2. The project has been built (dotnet build)
  3. The model name matches exactly (case-sensitive)

"Multiple types found with name 'X'"

This error occurs when you have multiple classes with the same name in different namespaces:

Multiple types found with name 'Calendar'. Please use the fully qualified name:
  - Fantacalcio.Domain.Calendar
  - OtherProject.Models.Calendar

Solution: Use the fully qualified name (with namespace) in your --models argument:

# Instead of:
--models "{Calendar,LeagueKey,Repository,serieA}"

# Use:
--models "{Fantacalcio.Domain.Calendar,Fantacalcio.Domain.LeagueKey,Repository,serieA}"

Contributing

This tool is part of the Rystem framework.

License

MIT License - see the main repository 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.

This package has no dependencies.

Version Downloads Last Updated
10.0.9 95 5/13/2026
10.0.8 120 3/26/2026
10.0.7 106 3/3/2026
10.0.6 106 2/22/2026
10.0.5 109 2/9/2026
10.0.4 110 2/9/2026
10.0.3 109 2/9/2026
10.0.3-preview.15 61 2/8/2026
10.0.3-preview.14 65 2/7/2026
10.0.3-preview.13 63 2/7/2026
10.0.3-preview.12 65 2/7/2026
10.0.3-preview.10 57 2/7/2026
10.0.3-preview.9 71 2/7/2026
10.0.3-preview.8 82 2/7/2026
10.0.3-preview.7 65 2/1/2026
10.0.3-preview.6 69 2/1/2026
10.0.3-preview.4 65 2/1/2026
10.0.3-preview.3 59 2/1/2026
10.0.3-preview.2 63 2/1/2026
10.0.3-preview.1 64 2/1/2026