Rystem.RepositoryFramework.TypescriptGenerator 10.0.3-preview.4

This is a prerelease version of Rystem.RepositoryFramework.TypescriptGenerator.
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.3-preview.4
                    
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.3-preview.4
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Rystem.RepositoryFramework.TypescriptGenerator&version=10.0.3-preview.4&prerelease
                    
nuke :add-package Rystem.RepositoryFramework.TypescriptGenerator --version 10.0.3-preview.4
                    

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)

Model Definition Format

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

"{Model,Key,Type,Factory},{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: Name for the generated service factory (optional, defaults to ModelName)

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
rystem-ts generate \
  --dest ./src/api \
  --models "{User,Guid,Repository,users}"
Multiple Repositories
rystem-ts generate \
  --dest ./src/api \
  --models "{Calendar,LeagueKey,Repository,serieA},{Team,Guid,Query,teams},{Match,int,Command,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 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

Generated Output

The tool generates the following structure:

📁 <destination>/
├── 📁 types/
│   ├── Calendar.ts           # Raw + Clean interfaces + Mapper
│   ├── LeagueKey.ts
│   ├── Team.ts
│   └── DayOfWeek.ts          # Enums
├── 📁 services/
│   ├── common.ts             # Entity, State, BatchOperation, Page, QueryOptions
│   └── serieA.service.ts     # Service class with API methods
├── 📁 bootstrap/
│   └── repositorySetup.ts    # RepositoryServices configuration with auth handlers
└── index.ts                  # Services registry with lazy singleton pattern

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[]> { ... }
}
Services Registry (index.ts)
export interface ServiceConfig {
  baseUrl: string;
}

export class Services {
  private static config: ServiceConfig | null = null;
  private static _serieA: SerieAService | null = null;

  static configure(config: ServiceConfig): void {
    this.config = config;
  }

  static get serieA(): SerieAService {
    this.ensureConfigured();
    if (!this._serieA) {
      this._serieA = new SerieAService(this.config!.baseUrl);
    }
    return this._serieA;
  }
}

// Usage:
Services.configure({ baseUrl: 'https://api.example.com' });
const calendar = await Services.serieA.get({ leagueId: 'serie-a' });
Bootstrap Setup (bootstrap/repositorySetup.ts)

The bootstrap file integrates with the rystem.repository.client npm package for centralized repository configuration with authentication:

import { RepositoryServices, RepositorySettings, RepositoryEndpoint } from 'rystem.repository.client';
import { CalendarRaw } from '../types/Calendar';
import { LeagueKeyRaw } from '../types/LeagueKey';

export interface RepositoryConfig {
  baseUrl: string;
  headersEnricher?: (
    endpoint: RepositoryEndpoint,
    uri: string,
    method: string,
    headers: HeadersInit,
    body: any
  ) => Promise<HeadersInit>;
  errorHandler?: (
    endpoint: RepositoryEndpoint,
    uri: string,
    method: string,
    headers: HeadersInit,
    body: any,
    err: any
  ) => Promise<boolean>;
}

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

  // Calendar repository
  services.addRepository<CalendarRaw, LeagueKeyRaw>(x => {
    x.name = 'calendar';
    x.path = 'calendar';
    x.complexKey = true;
    if (config.headersEnricher) {
      x.addHeadersEnricher(config.headersEnricher);
    }
    if (config.errorHandler) {
      x.addErrorHandler(config.errorHandler);
    }
  });
};

Usage with Authentication:

import { setupRepositoryServices } from './bootstrap/repositorySetup';
import { tokenService } from './services/tokenService';

// Setup with auth headers and error handling
setupRepositoryServices({
  baseUrl: 'https://api.example.com/api/',

  // Add Bearer token to all requests
  headersEnricher: async (endpoint, uri, method, headers, body) => {
    const token = await tokenService.getAccessToken();
    return token 
      ? { ...headers, Authorization: `Bearer ${token}` }
      : headers;
  },

  // Handle 401 errors with token refresh
  errorHandler: async (endpoint, uri, method, headers, body, err) => {
    if (err?.status === 401) {
      const refreshed = await tokenService.refreshToken();
      return refreshed; // Return true to retry request
    }
    return false; // Don't retry
  }
});

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