{"id":23983838,"url":"https://github.com/nenonaninu/typedsignalr.client.typescript","last_synced_at":"2025-05-16T06:04:37.692Z","repository":{"id":37006462,"uuid":"497544782","full_name":"nenoNaninu/TypedSignalR.Client.TypeScript","owner":"nenoNaninu","description":"TypeScript source generator to provide strongly typed SignalR clients by analyzing C# type definitions.","archived":false,"fork":false,"pushed_at":"2025-05-08T01:27:36.000Z","size":649,"stargazers_count":107,"open_issues_count":9,"forks_count":11,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-16T06:04:33.563Z","etag":null,"topics":["aspnetcore","csharp","signalr","signalr-client","typescript"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nenoNaninu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-29T09:30:36.000Z","updated_at":"2025-05-13T18:53:16.000Z","dependencies_parsed_at":"2024-02-03T06:28:40.048Z","dependency_job_id":"157d76ff-c80d-4b85-86d0-763b54f1c784","html_url":"https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript","commit_stats":{"total_commits":127,"total_committers":4,"mean_commits":31.75,"dds":"0.35433070866141736","last_synced_commit":"c4c2ba98c57678e34e71124d05b610105b2e4d25"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nenoNaninu%2FTypedSignalR.Client.TypeScript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nenoNaninu%2FTypedSignalR.Client.TypeScript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nenoNaninu%2FTypedSignalR.Client.TypeScript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nenoNaninu%2FTypedSignalR.Client.TypeScript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nenoNaninu","download_url":"https://codeload.github.com/nenoNaninu/TypedSignalR.Client.TypeScript/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254478186,"owners_count":22077675,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspnetcore","csharp","signalr","signalr-client","typescript"],"created_at":"2025-01-07T12:28:58.789Z","updated_at":"2025-05-16T06:04:37.669Z","avatar_url":"https://github.com/nenoNaninu.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TypedSignalR.Client.TypeScript\n\n[![NuGet](https://img.shields.io/nuget/v/TypedSignalR.Client.TypeScript.Attributes.svg)](https://www.nuget.org/packages/TypedSignalR.Client.TypeScript.Attributes)\n[![build-and-test](https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript/actions/workflows/build-and-test.yaml)\n\nTypedSignalR.Client.TypeScript is a library/CLI tool that analyzes SignalR hub and receiver type definitions written in C# and generates TypeScript source code to provide strongly typed SignalR clients.\n\n## Table of Contents\n\n- [Why TypedSignalR.Client.TypeScript?](#why-typedsignalrclienttypescript)\n- [Packages](#packages)\n  - [Install Using .NET Tool](#install-using-net-tool)\n- [Usage](#usage)\n- [Supported Types](#supported-types)\n  - [Built-in Supported Types](#built-in-supported-types)\n  - [User Defined Types](#user-defined-types)\n- [Analyzer](#analyzer)\n- [Streaming Support](#streaming-support)\n- [Client Results Support](#client-results-support)\n- [MessagePack Hub Protocol Support](#messagepack-hub-protocol-support)\n  - [Default Configuration in ASP.NET Core](#default-configuration-in-aspnet-core)\n  - [Recommended Configuration](#recommended-configuration)\n- [Related Work](#related-work)\n\n\n## Why TypedSignalR.Client.TypeScript?\n\nImplementing SignalR Hubs (server-side) in C# can be strongly typed by using interfaces, but the [TypeScript SignalR client](https://github.com/dotnet/aspnetcore/tree/main/src/SignalR/clients/ts/signalr) is not strongly typed. To call Hub methods, we must specify the method defined in Hub using a string. We also have to determine the return type manually. Moreover, registering client methods called from a server also requires specifying the method name as a string, and we must set parameter types manually.\n\n```ts\n// TypeScript SignalR client\n// without TypedSignalR.Client.TypeScript\n\n// Specify a hub method to invoke using string.\nawait connection.invoke(\"HubMethod1\");\n\n// Manually determine a return type.\n// Parameters are cast to any type.\nconst value = await connection.invoke\u003cnumber\u003e(\"HubMethod2\", \"message\", 99);\n\n// Registering a client method requires a string, and parameter types must be set manually.\nconst func = (message: string, count: number) =\u003e {...};\nconnection.on(\"ClientMethod\", func);\nconnection.off(\"ClientMethod\", func);\n```\n\nThese are very painful and cause bugs.\n\nTypedSignalR.Client.TypeScript aims to **generates TypeScript** source code to provide strongly typed SignalR clients by **analyzing C#** interfaces in which the server and client methods are defined.\nYou only need to execute one command to analyze your C# code and generate TypeScript code.\nPlease see the [Install Using .NET Tool](#install-using-net-tool) and [Usage](#usage) sections for more information.\n\n```bash\n$ dotnet tsrts --project path/to/Project.csproj --output generated\n```\n\n```ts\n// TypeScript SignalR client\n// with TypedSignalR.Client.TypeScript\n// Generated source code\n\nexport type HubProxyFactory\u003cT\u003e = {\n    createHubProxy(connection: HubConnection): T;\n}\n\nexport type ReceiverRegister\u003cT\u003e = {\n    register(connection: HubConnection, receiver: T): Disposable;\n}\n\n// Overload function type\n// Because string literal types are used, there is no need to worry about typos.\nexport type HubProxyFactoryProvider = {\n    // In this example, IHub1 and IHub2 are transpiled from C# to TypeScript.\n    (hubType: \"IHub1\"): HubProxyFactory\u003cIHub1\u003e;\n    (hubType: \"IHub2\"): HubProxyFactory\u003cIHub2\u003e;\n}\n\n// Overload function type\nexport type ReceiverRegisterProvider = {\n    // In this example, IReceiver1 and IReceiver2 are transpiled from C# to TypeScript.\n    (receiverType: \"IReceiver1\"): ReceiverRegister\u003cIReceiver1\u003e;\n    (receiverType: \"IReceiver2\"): ReceiverRegister\u003cIReceiver2\u003e;\n}\n\nexport const getHubProxyFactory : HubProxyFactoryProvider = ...; \nexport const getReceiverRegister : ReceiverRegisterProvider = ...;\n```\n\n```ts\n// Usage of generated code.\n\nconst hubProxy = getHubProxyFactory(\"IHub1\") // HubProxyFactory\u003cIHub1\u003e\n    .createHubProxy(connection); // IHub1\n\nconst receiver : IReceiver1 = {...};\n\nconst subscription = getReceiverRegister(\"IReceiver1\") // ReceiverRegister\u003cIReceiver1\u003e\n    .register(connection, receiver); // Disposable\n\n// We no longer need to specify the method using a string.\nawait hubProxy.hubMethod1();\n\n// Both parameters and return types are strongly typed.\nconst value = await hubProxy.hubMethod2(\"message\", 99); // Type inference works.\n\nsubscription.dispose();\n```\n\nThe example of the actual generated code exists in [`/samples/console.typescript/generated`](/samples/console.typescript/generated/) so please have a look if you are interested.\n\n## Packages\n\n- [TypedSignalR.Client.TypeScript.Attributes](https://www.nuget.org/packages/TypedSignalR.Client.TypeScript.Attributes/)\n- [TypedSignalR.Client.TypeScript.Analyzer](https://www.nuget.org/packages/TypedSignalR.Client.TypeScript.Analyzer/)\n- [TypedSignalR.Client.TypeScript.Generator](https://www.nuget.org/packages/TypedSignalR.Client.TypeScript.Generator/)\n\n### Install Using .NET Tool\n\nUse `TypedSignalR.Client.TypeScript.Generatorr`(CLI Tool) to generate TypeScript source code to provide strongly typed SignalR clients.\n`TypedSignalR.Client.TypeScript.Generatorr` can be easily installed using .NET Global Tools. You can use the installed tools with the command `dotnet tsts`(**T**yped**S**ignal**R**.Client.**T**ype**S**cript).\n\n```bash\n# install\n# TypedSignalR.Client.TypeScript CLI (dotnet tool) requires .NET 7, but your app TFM can use .NET 6, etc.\n$ dotnet tool install --global TypedSignalR.Client.TypeScript.Generator\n$ dotnet tsrts help\n\n# update\n$ dotnet tool update --global TypedSignalR.Client.TypeScript.Generator\n```\n\n## Usage\n\nFirst, add the following packages to your project. TypedSignalR.Client.TypeScript.Analyzer is optional, but recommended.\n\n```bash\n$ dotnet add package TypedSignalR.Client.TypeScript.Attributes\n$ dotnet add package TypedSignalR.Client.TypeScript.Analyzer (optional, but recommended.)\n$ dotnet add package Tapper.Analyzer (optional, but recommended.)\n```\n\nBy adding `TypedSignalR.Client.TypeScript.Attributes` package, you can use three attributes.\n\n- HubAttribute\n- ReceiverAttribute\n- TranspilationSourceAttribute\n\nThen, annotate `HubAttribute` and `ReceiverAttribute` to each interface definitions of Hub and Receiver of SignalR.\nAlso, annotate `TranspilationSourceAttribute` to user-defined types used in the interface definition of Hub and Receiver.\nAdding this attribute is relatively easy if you add the [TypedSignalR.Client.TypeScript.Analyzer](#analyzer) to your project.\n\n```cs\nusing Tapper;\nusing TypedSignalR.Client;\n\nnamespace App.Interfaces.Chat;\n\n[Hub] // \u003c- Add Attribute\npublic interface IChatHub\n{\n    Task Join(string username);\n    Task Leave();\n    Task\u003cIEnumerable\u003cstring\u003e\u003e GetParticipants();\n    Task SendMessage(string message);\n}\n\n[Receiver] // \u003c- Add Attribute\npublic interface IChatReceiver\n{\n    Task OnReceiveMessage(Message message);\n    Task OnLeave(string username, DateTime dateTime);\n    Task OnJoin(string username, DateTime dateTime);\n}\n\n[TranspilationSource] // \u003c- Add Attribute\npublic record Message(string Username, string Content, DateTime TimeStamp);\n```\n\nFinally, enter the following command.\nThis command analyzes C# and generates TypeScript code.\n\n```bash\n$ dotnet tsrts --project path/to/Project.csproj --output generated\n```\n\nThe generated code can be used as follows.\nThere are two important APIs that are generated.\n\n- `getHubProxyFactory`\n- `getReceiverRegister`\n\n```ts\nimport { HubConnectionBuilder } from \"@microsoft/signalr\";\nimport { getHubProxyFactory, getReceiverRegister } from \"./generated/TypedSignalR.Client\";\nimport { IChatReceiver } from \"./generated/TypedSignalR.Client/App.Interfaces.Chat\";\nimport { Message } from \"./generated/App.Interfaces.Chat\";\n\nconst connection = new HubConnectionBuilder()\n    .withUrl(\"https://example.com/hubs/chathub\")\n    .build();\n\nconst receiver: IChatReceiver = {\n    onReceiveMessage: (message: Message): Promise\u003cvoid\u003e =\u003e {...},\n    onLeave: (username: string, dateTime: string | Date): Promise\u003cvoid\u003e =\u003e {...},\n    onJoin: (username: string, dateTime: string | Date): Promise\u003cvoid\u003e =\u003e {...}\n}\n\n// The argument of getHubProxyFactory is a string literal type, not a string type.\n// Therefore, there is no need to worry about typos.\nconst hubProxy = getHubProxyFactory(\"IChatHub\")\n    .createHubProxy(connection);\n\n// Also, the argument of getReceiverRegister is a string literal type, not a string type.\n// Therefore, again, there is no need to worry about typos.\nconst subscription = getReceiverRegister(\"IChatReceiver\")\n    .register(connection, receiver)\n\nawait connection.start()\n\nawait hubProxy.join(username)\n\nconst participants = await hubProxy.getParticipants()\n\n// ...\n```\n\n\n## Supported Types\n\nTypedSignalR.Client.TypeScript uses a library named [nenoNaninu/Tapper](https://github.com/nenoNaninu/Tapper) to convert C# types to TypeScript types.\nPlease read [Tapper's README](https://github.com/nenoNaninu/Tapper/blob/main/README.md#built-in-supported-types) for details on the correspondence between C# types and TypeScript types.\nHere is a brief introduction of which types are supported.\n\n### Built-in Supported Types\n\n`bool` `byte` `sbyte` `char` `decimal` `double` `float` `int` `uint` `long` `ulong` `short` `ushort` `object` `string` `Uri` `Guid` `DateTime` `System.Nullable\u003cT\u003e` `byte[]` `T[]` `System.Array` `ArraySegment\u003cT\u003e` `List\u003cT\u003e` `LinkedList\u003cT\u003e` `Queue\u003cT\u003e` `Stack\u003cT\u003e` `HashSet\u003cT\u003e` `IEnumerable\u003cT\u003e` `IReadOnlyCollection\u003cT\u003e` `ICollection\u003cT\u003e` `IList\u003cT\u003e` `ISet\u003cT\u003e` `Dictionary\u003cTKey, TValue\u003e` `IDictionary\u003cTKey, TValue\u003e` `IReadOnlyDictionary\u003cTKey, TValue\u003e` `Tuple`\n\n### User Defined Types\n\nOf course, you can use user-defined types as well as Built-in Supported Types.\nTo transpile C# user-defined types to TypeScript types, annotate `TranspilationSourceAttribute`.\n\n```cs\nusing Tapper;\n\n[TranspilationSource] // \u003c- Add attribute!\npublic class CustomType\n{\n    public List\u003cint\u003e? List { get; }\n    public int Value { get; }\n    public Guid Id { get; }\n    public DateTime DateTime { get; }\n}\n\n[TranspilationSource] // \u003c- Add attribute!\npublic enum MyEnum\n{\n    Zero = 0,\n    One = 1,\n    Two = 1 \u003c\u003c 1,\n    Four = 1 \u003c\u003c 2,\n}\n\n[TranspilationSource] // \u003c- Add attribute!\npublic record CustomType2(float Value, DateTime ReleaseDate);\n```\n\n## Analyzer\nUser-defined types used in parameters and return values of methods defined within interfaces annotated with `Hub` or `Receiver` must be annotated with `TranspilationSource`.\nThe Analyzer checks in real-time whether this rule is followed. If not, the IDE will tell you.\n\n![analyzer](https://user-images.githubusercontent.com/27144255/170770137-28790bcf-08d1-403f-9625-2cdf6f390e76.gif)\n\n\n## Streaming Support\n\nSignalR supports both [server-to-client streaming and client-to-server streaming](https://docs.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-6.0).\n\nTypedSignalR.Client.TypeScript supports both server-to-client streaming and client-to-server streaming.\nIf you use `IAsyncEnumerable\u003cT\u003e`, `Task\u003cIAsyncEnumerable\u003cT\u003e\u003e`, or `Task\u003cChannelReader\u003cT\u003e\u003e` for the method return type, it is analyzed as server-to-client streaming.\nAnd if `IAsyncEnumerable\u003cT\u003e` or `ChannelReader\u003cT\u003e` is used in the method parameter, it is analyzed as client-to-server streaming.\n\nWhen using server-to-client streaming, a single `CancellationToken` can be used as a method parameter (Note: `CancellationToken` cannot be used as a parameter except for server-to-client streaming).\n\n```cs\n// C# source code\n[Hub]\npublic interface IMyStreamingHub\n{\n    // Server-to-Client streaming\n    // Return type : IAsyncEnumerable\u003cT\u003e or Task\u003cIAsyncEnumerable\u003cT\u003e\u003e or Task\u003cChannelReader\u003cT\u003e\u003e\n    // Parameter : CancellationToken can use.\n    Task\u003cChannelReader\u003cMyStreamItem\u003e\u003e ServerToClientStreaming(MyType instance, int init, CancellationToken cancellationToken);\n\n    // Client-to-Server streaming\n    // Return type : Task (not Task\u003cT\u003e)\n    // Parameter : IAsyncEnumerable\u003cT\u003e and ChannelReader\u003cT\u003e can use as stream from client to server.\n    Task ClientToServerStreaming(MyType instance, ChannelReader\u003cMyStreamItem\u003e stream);\n}\n```\n\n```ts\n// Usage in TypeScript\n// Parameters and return types are strongly typed by generated TypeScript code\n\nconst connection: HubConnection = ...;\n\nconst hubProxy = getHubProxyFactory(\"IMyStreamingHub\")\n    .createHubProxy(connection);\n\nawait connection.start();\n\nconst instance: MyType = ...;\n\n// subscribe server to client streaming message\nhubProxy.serverToClientStreaming(instance, 99) // IStreamResult\u003cMyStreamItem\u003e\n    .subscribe({\n        next: (value: MyStreamItem): void =\u003e {\n            console.log(value)\n        },\n        error: (err: any): void =\u003e {\n            console.log(err)\n        },\n        complete: (): void =\u003e {\n            console.log(\"complete\")\n        }\n    });\n\nconst subject = new Subject\u003cMyStreamItem\u003e(); // stream\n\n// set client to server stream\nawait hubProxy.clientToServerStreaming(instance, subject);\n\nconst item: MyStreamItem = ...;\nsubject.next(item); // write item to stream\nsubject.next(item);\nsubject.complete();\n```\n\n## Client Results Support\n\n.NET 7 and later, you can use [client results](https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-7.0#client-results).\n\nTypedSignalR.Client.TypeScript supports client results.\nIf you use `Task\u003cT\u003e` for the method return type in the receiver interface, you can use client results.\n\n```cs\n// C# source code\n[Receiver]\npublic interface IMyHubReceiver\n{\n    // Return type: Task\u003cT\u003e \n    Task\u003cGuid\u003e GetGuidFromClient();\n}\n```\n\n```ts\n// Usage in TypeScript\n// Parameters and return types are strongly typed by generated TypeScript code\n\nconst connection: HubConnection = ...;\n\nconst receiver: IMyHubReceiver = {\n    getGuidFromClient: (): Promise\u003cstring\u003e =\u003e {\n        // return value.\n        return Promise.resolve(\"ba3088bb-e7ea-4924-b01b-695e879bb166\");\n    }\n}\n\nconst subscription = getReceiverRegister(\"IMyHubReceiver\")\n    .register(connection, receiver);\n```\n\n## MessagePack Hub Protocol Support\n\nThis tool can generate TypeScript code to use the MessagePack Hub Protocol.\nWhen serializing a user-defined type, use a property name as a key (in other words, it should be serialized as a map, not an array).\nTherefore, apply `[MessagePackObject(true)]` to a user-defined type, or use `ContractlessStandardResolver`.\nThe default configuration for MessagePack Hub Protocol [includes the ContractlessStandardResolver](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/SignalR/common/Protocols.MessagePack/src/Protocol/MessagePackHubProtocol.cs#L73-L77).\n\n### Default Configuration in ASP.NET Core\n\nCalling `AddMessagePackProtocol` is required to use the MessagePack Hub Protocol.\nFor more information, please see [the official documentation](https://learn.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol).\n\n```cs\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddSignalR()\n    .AddJsonProtocol()\n    .AddMessagePackProtocol();\n```\n\nIf you do not set any options in `AddMessagePackProtocol`, use the following command.\n\n```bash\n$ dotnet tsrts --project path/to/Project.csproj --output generated --serializer MessagePack --naming-style none --enum name\n```\n\n### Recommended Configuration\n\nSignalR MessagePack Hub Protocol serializes an enum as a string by default.\nThe following configuration is required to serialize an enum as an integer value.\n\n```cs\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddSignalR()\n    .AddJsonProtocol()\n    .AddMessagePackProtocol(options =\u003e\n    {\n        options.SerializerOptions = MessagePackSerializerOptions.Standard\n            .WithResolver(ContractlessStandardResolver.Instance)\n            .WithSecurity(MessagePackSecurity.UntrustedData);\n    });\n```\n\nIf you set up the above configuration, use the following command\n\n```bash\n$ dotnet tsrts --project path/to/Project.csproj --output generated --serializer MessagePack --naming-style none\n```\n\n## Related Work\n- [nenoNaninu/TypedSignalR.Client](https://github.com/nenoNaninu/TypedSignalR.Client)\n  - C# Source Generator to create strongly typed SignalR clients.\n- [nenoNaninu/TypedSignalR.Client.DevTools](https://github.com/nenoNaninu/TypedSignalR.Client.DevTools)\n  - SignalR development tools inspired by SwaggerUI.\n- [nenoNaninu/Tapper](https://github.com/nenoNaninu/Tapper)\n  - A Tool Transpiling C# Type into TypeScript Type.\n- [nenoNaninu/AspNetCore.SignalR.OpenTelemetry](https://github.com/nenoNaninu/AspNetCore.SignalR.OpenTelemetry)\n  - SignalR instrumentation library for OpenTelemetry.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnenonaninu%2Ftypedsignalr.client.typescript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnenonaninu%2Ftypedsignalr.client.typescript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnenonaninu%2Ftypedsignalr.client.typescript/lists"}