Skip to content

A high-performance TypeScript implementation of RSocket, ready for browsers. #308

Open

Description

@CKATEPTb

A high-performance TypeScript implementation of RSocket

Hi everyone,

I would like to share rsocket-ts, a high-performance, browser-ready implementation of RSocket 1.0 for TypeScript:

https://github.com/CKATEPTb/rsocket-ts

The project started as an alternative browser requester, but has since evolved into a modular RSocket stack covering frames, shared protocol mechanics, clients, servers, TCP, WebSocket, and browser-specific connection recovery.

It is not intended to diminish the work done in rsocket-js. The goal is to provide another implementation designed around modern TypeScript, Web Platform APIs, a small public API, and reactive programming.

Packages

The repository contains five independently published packages:

  • rsocket-browser: browser-first WebSocket requester with reconnect and browser wake handling;
  • rsocket-client-ts: requester for WebSocket and Node TCP;
  • rsocket-server-ts: responder for WebSocket and Node TCP;
  • rsocket-core-ts: shared protocol, transport, fragmentation, Resume, and backpressure mechanics;
  • rsocket-frames-ts: RSocket frame, MIME type, metadata, authentication, and binary codecs.

For a browser application:

npm install rsocket-browser

For a Node.js requester supporting WebSocket and TCP:

npm install rsocket-client-ts

For a TypeScript RSocket server:

npm install rsocket-server-ts

All required lower-level packages are installed automatically.

TypeScript-first design

The implementation is written in TypeScript and publishes generated declarations.

Types are propagated through:

  • SETUP data and metadata MIME codecs;
  • interaction payloads;
  • request and response controllers;
  • per-request MIME overrides;
  • routing and composite metadata;
  • request context on the server.

The public API intentionally exposes only the classes and types required by application code. Internal sessions, transports, dispatchers, and protocol state remain private.

Browser-ready binary implementation

The protocol implementation uses Uint8Array, ArrayBuffer, Blob, and other standard JavaScript APIs instead of Node.js Buffer.

This means browser applications do not need Buffer polyfills, while the same binary implementation can also be used by Node.js TCP and WebSocket transports.

All packages are ESM-only, side-effect free where applicable, and licensed under MIT.

Client and server support

The requester and responder implement all four primary RSocket interaction models:

  • fire-and-forget;
  • request-response;
  • request-stream;
  • request-channel.

Request-stream and request-channel follow Reactive Streams demand and cancellation semantics.

The requester sends REQUEST_N only when the subscriber requests data. Request-channel applies backpressure independently in both directions, so outbound items are sent only after the responder grants demand.

Reactor-style API

Requester operations return Mono or Flux values from reactor-core-ts.

Operations are lazy and begin only when subscribed, awaited through block(), or consumed with for await...of.

import {RSocket} from "rsocket-browser";
import {WellKnownMimeType} from "rsocket-frames-ts";

const route = (name: string) =>
  WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.toMetadata([name]);

const socket = new RSocket("wss://api.example.com/rsocket", {
  setup: {
    keepAlive: 20_000,
    lifetime: 90_000,
    fragmentSize: 16 * 1024,
    mimetype: {
      data: WellKnownMimeType.APPLICATION_JSON,
      metadata: WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA
    }
  }
});

const connected = await socket.connect().block();
if (!connected) throw new Error("RSocket connection did not open");

const response = await socket
  .requestResponse({id: 42}, route("account.find"))
  .block();

console.log(response?.data);
connected.disconnect();

Typed metadata and authentication

Metadata is created through protocol MIME codecs instead of manually assembled strings or byte buffers.

The implementation supports:

  • routing metadata;
  • composite metadata;
  • simple authentication;
  • bearer authentication;
  • persistent request metadata;
  • per-request metadata overrides;
  • METADATA_PUSH.

For example, persistent bearer authentication can be added and removed without affecting request routing:

import {
  WellKnownAuthType,
  WellKnownMimeType
} from "rsocket-frames-ts";

const authMime = WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION;

socket.metadataUpdate((metadata) => {
  metadata.set(
    authMime,
    WellKnownAuthType.BEARER.auth("access-token")
  );
});

socket.metadataUpdate((metadata) => {
  metadata.remove(authMime);
});

When composite metadata is used, persistent authentication and request-specific routing are merged by MIME type.

Declarative client controllers

Routes can be represented as typed controller classes:

import {RequestResponseController} from "rsocket-browser";

interface SignInRequest {
  login: string;
  password: string;
}

interface TokenResponse {
  token: string;
}

class SignInController extends RequestResponseController<
  SignInRequest,
  TokenResponse
> {
  protected readonly route = "account.sign-in";
}

const token = await socket
  .process(SignInController, {
    login: "user@example.com",
    password: "secret"
  })
  .map((response) => response.token)
  .block();

Equivalent controller classes are available for fire-and-forget, request-stream, and request-channel.

Declarative TypeScript server

rsocket-server-ts provides the same controller-oriented approach for responders:

import {
  RequestResponseController,
  RSocketServer
} from "rsocket-server-ts";

class FindAccountController extends RequestResponseController<
  {id: number},
  {id: number; name: string}
> {
  protected readonly route = "account.find";

  override handle(request: {id: number}) {
    return {
      id: request.id,
      name: "Ada"
    };
  }
}

const server = new RSocketServer({
  controllers: [FindAccountController],
  resume: {
    ttlMs: 5 * 60_000
  }
});

const listener = await server.listenTcp({
  host: "127.0.0.1",
  port: 7000
}).block();

For WebSocket servers, sockets accepted by an HTTP or WebSocket framework can be passed directly to server.acceptWebSocket(...).

Protocol features

The client and server include:

  • RSocket 1.0 frame validation;
  • TCP and WebSocket transports;
  • keepalive and lifetime enforcement;
  • Reactive Streams backpressure;
  • request cancellation;
  • configurable fragmentation and transparent reassembly;
  • Resume tokens and byte-position tracking;
  • replay of unacknowledged frames;
  • Resume rejection and fresh SETUP fallback;
  • lease support;
  • metadata push;
  • stream-scoped and connection-scoped errors;
  • frame and lifecycle logging.

The browser package additionally reacts to online, focus, visibility, page restore, and mobile sleep/wake signals when reconnecting.

Testing

Each package has an independent test suite.

The repository tests:

  • every interaction model;
  • TCP and WebSocket transports separately;
  • browser-to-server communication;
  • backpressure in both directions;
  • fragmentation and reassembly;
  • Resume and replay positions;
  • keepalive and lifetime behavior;
  • metadata and routing;
  • protocol errors and cancellation;
  • resource cleanup and retained state;
  • package architecture and public API boundaries.

The requester and responder are tested against each other over real transport adapters rather than relying on Java or Spring test servers.

Feedback, interoperability reports, performance results, and protocol-level review are welcome.

Activity

  1. linkser commented on Jul 20, 2026

    @linkser

    It seems that fragmentation support has not been implemented yet?

  2. CKATEPTb commented on Jul 20, 2026

    @CKATEPTb
    Author

    It seems that fragmentation support has not been implemented yet?

    I just did some additional tests to check the fragmentation and it is fully implemented, you don't have to manage it manually on the client side. rsocket-browser manages fragmentation itself and fully supports it.

  3. linkser commented on Jul 21, 2026

    @linkser

    It seems that fragmentation support has not been implemented yet?

    I just did some additional tests to check the fragmentation and it is fully implemented, you don't have to manage it manually on the client side. rsocket-browser manages fragmentation itself and fully supports it.

    Take a look at this example.It looks like frame fragmentation is not being performed

      const dataArr = [];
      for (let i = 0; i< 35536; i++) {
        dataArr.push(0);
      }
      const response = await connection
      .requestResponse(dataArr, route("hello"))
      .block();
      console.log(response?.data);
    Image
  4. CKATEPTb commented on Jul 21, 2026

    @CKATEPTb
    Author

    Take a look at this example.It looks like frame fragmentation is not being performed

    Judging by the screenshot you sent, you did not enable fragmentation support on your server. In spring, for example, fragmentation is disabled by default.

  5. linkser commented on Jul 22, 2026

    @linkser

    Take a look at this example.It looks like frame fragmentation is not being performed

    Judging by the screenshot you sent, you did not enable fragmentation support on your server. In spring, for example, fragmentation is disabled by default.

    I couldn't find any Spring configuration related to fragmentation, nor any documentation stating that it is disabled by default. Also, when I use rsocket-js with fragmentation enabled on the client side, the server is able to handle the fragmented frames correctly.

    the code may be

    const transportOpts = {
        url: 'ws:' + serverUrl.value,
        debug: true,
        lengthPrefixedFrames: false
      }
    
      const setup = {
        dataMimeType: WellKnownMimeType.APPLICATION_JSON.toString(),
        keepAlive: 10000,
        lifetime: 90000,
        metadataMimeType: WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.string,
      }
    
      const errorHandler = (e) => {
        console.log(e);
      }
    
      const fragmentation = {
        maxOutboundFragmentSize: 32000
      }
    
      const transport = new WebsocketClientTransport(transportOpts);
      const client = new RSocketConnector({setup, transport, fragmentation});
      rSocket = await client.connect();
    
    const dataArr = [];
      for (let i = 0; i< 35536; i++) {
        dataArr.push(0);
      }
    
       rSocket.requestResponse({
         data: Buffer.from(dataArr),
         metadata: encodeRoute("hello"),
       },{
         onError: (e) => {
           console.log(e);
         },
         onNext: (payload, isComplete) => {
           console.log(payload);
         },
         onComplete: () => {
           console.log('complete')
         },
         onExtension: () => {},
       });

    the message is fragmented into frames of approximately 32 KB
    Image

  6. CKATEPTb commented on Jul 22, 2026

    @CKATEPTb
    Author

    I couldn't find any Spring configuration related to fragmentation, nor any documentation stating that it is disabled by default. Also, when I use rsocket-js with fragmentation enabled on the client side, the server is able to handle the fragmented frames correctly.

    I'll look in more detail in the near future and come back here with the result.

  7. changed the title [-]Browser-first TypeScript RSocket client using Uint8Array: rsocket-browser[/-] [+]A high-performance TypeScript implementation of RSocket, ready for browsers.[/+] on Jul 28, 2026
  8. CKATEPTb commented on Jul 28, 2026

    @CKATEPTb
    Author

    It seems that fragmentation support has not been implemented yet?

    Hi! Since our last conversation, I have finished implementing what I originally set out to build.
    The project is no longer just an alternative RSocket client. It is now a complete TypeScript implementation of both the RSocket client and server, organized as a monorepo with separate packages for frames, shared protocol logic, TCP/WebSocket clients, the server, and browser-specific functionality:
    https://github.com/CKATEPTb/rsocket-ts
    I also investigated the fragmentation issue you reported and added integration tests covering large fragmented payloads between the client and server. Fragmentation and reassembly should now work transparently without requiring manual payload splitting.
    Thank you for raising the issue and helping improve the implementation. Good luck with your project!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions