Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@ridit/relay

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ridit/relay

One port. Any language. Monaco-ready LSP bridge.

latest
Source
npmnpm
Version
0.1.6
Version published
Weekly downloads
28
-90.79%
Maintainers
1
Weekly downloads
 
Created
Source

@ridit/relay

One port. Any language. Monaco-ready LSP bridge.

@ridit/relay multiplexes any number of language servers over a single WebSocket port using path-based routing. Drop it into any Monaco editor project and get completions, hover, diagnostics, go-to-definition, references, signature help, formatting, and code lenses — wired automatically.

ws://127.0.0.1:9721/python      → pylsp / pyright
ws://127.0.0.1:9721/typescript  → typescript-language-server
ws://127.0.0.1:9721/rust        → rust-analyzer

Why

The standard approach to Monaco + LSP is monaco-languageclient — but it pulls in vscode-languageclient and the full VSCode extension host model just to wire up completions. That's hundreds of kilobytes of VSCode abstractions built for a different runtime, bolted onto Monaco as an afterthought.

Relay uses only the primitives it actually needs — vscode-ws-jsonrpc for WebSocket framing and vscode-languageserver-protocol for LSP message types — then talks directly to Monaco's provider APIs. No extension host, no VSCode shims, no adapter layers.

It was extracted from Meridia — a from-scratch code editor — where we needed LSP that fit the stack instead of fighting it. If you're building an editor or any Monaco-based tool, relay gives you full LSP integration without the overhead.

Install

npm install @ridit/relay
# or
bun add @ridit/relay

Monaco is a peer dependency — install it separately if you haven't:

npm install monaco-editor

Usage

Server (Node / Bun)

The simplest case — command known at startup:

import { Server } from "@ridit/relay";

const server = new Server();

server.register({
  languageId: "python",
  command: "pyright-langserver",
  args: ["--stdio"],
});

server.register({
  languageId: "typescript",
  command: "typescript-language-server",
  args: ["--stdio"],
});

await server.start(9721);

Runtime resolution

Use resolve when the command must be discovered at runtime — for example, finding a Python interpreter before locating pylsp. resolve is called per connection, so if the language server isn't available the connection fails cleanly without crashing the process.

server.register({
  languageId: "python",
  resolve: () => {
    const python = findPythonInterpreter(); // your own logic
    if (!python) return null; // closes the WS with a clean error

    const pylsp = findPylsp(python);
    if (!pylsp) return null;

    return { command: pylsp.command, args: pylsp.args };
  },
});

If resolve returns null, the WebSocket is closed with code 1011 and a descriptive message — no process is spawned.

command and resolve are mutually exclusive. command takes priority if both are provided.

Client (Browser)

import * as monaco from "monaco-editor";
import { Client } from "@ridit/relay";

const editor = monaco.editor.create(document.getElementById("app")!, {
  automaticLayout: true,
});

const model = monaco.editor.createModel(
  "print('hello')",
  "python",
  monaco.Uri.file("/workspace/main.py"), // real path required
);

editor.setModel(model);

const client = new Client(monaco);
client.register({ languageId: "python" });
await client.start("/workspace");

Windows: pass the workspace path with backslashes — "C:\\workspace".

What gets wired automatically

Once client.start() is called, relay registers the following Monaco providers for each language:

FeatureProvider
CompletionsregisterCompletionItemProvider
HoverregisterHoverProvider
DiagnosticssetModelMarkers via publishDiagnostics
Go to DefinitionregisterDefinitionProvider
Find ReferencesregisterReferenceProvider
Signature HelpregisterSignatureHelpProvider
FormattingregisterDocumentFormattingEditProvider
Code LensesregisterCodeLensProvider (with usage counts)

API

new Server()

server.register(def: LspServerDefinition): this
server.start(port?: number): Promise<void>   // default: 9721
server.stop(): Promise<void>

LspServerDefinition

{
  languageId: string;    // e.g. "python", "typescript"

  // Option A — static: command resolved at startup
  command?: string;      // binary name or full path
  args?: string[];       // e.g. ["--stdio"]
  env?: Record<string, string>;

  // Option B — dynamic: command resolved per connection
  resolve?: () => { command: string; args?: string[] } | null;
}

new Client(monaco)

client.register(def: LspClientDefinition): this
client.start(workspacePath?: string, port?: number): Promise<void>
client.format_model(model: monaco.editor.ITextModel): Promise<boolean>
client.updateWorkspaceRoot(folderPath: string): Promise<void>
client.dispose(): Promise<void>

LspClientDefinition

{
  languageId: string;     // must match server registration
  extensions?: string[];  // file extensions to match, e.g. ["py", "pyw"]
}

Windows notes

On Windows, npm-installed language servers are .cmd wrappers. Relay resolves these automatically — pass the bare binary name and it will find the right executable:

server.register({
  languageId: "python",
  command: "pyright-langserver",
  args: ["--stdio"],
});

License

MIT

FAQs

Package last updated on 03 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts