5 releases (3 breaking)
Uses new Rust 2024
| new 0.4.0 | Apr 11, 2026 |
|---|---|
| 0.3.2 | Apr 8, 2026 |
| 0.3.1 | Apr 5, 2026 |
| 0.2.1 | Apr 5, 2026 |
| 0.1.0 | Apr 5, 2026 |
#1094 in Concurrency
137 downloads per month
Used in 7 crates
(5 directly)
30KB
250 lines
corsa
Rust bindings, orchestration layers, and Node bindings for typescript-go over stdio.
[!WARNING] This repository is still evolving. The local Rust and Node API/LSP surfaces are now hardened for production-style use, but distributed orchestration remains behind the
experimental-distributedcargo feature and some upstream-facing endpoints remain explicitly experimental.
[!IMPORTANT]
corsais intentionally built around upstream-supportedtypescript-goworkflows. We followtsgo's recommended stdio/API/LSP integration points, keepref/typescript-goas an exact upstream checkout, and preserve a strictno forks, no patchespolicy.
What This Is
corsa is a multi-crate workspace for talking to typescript-go from Rust and Node.js without patching upstream, with a Rust-backed native FFI layer that exposes tsgo API, virtual-document, and utils surfaces across C-family and other native languages.
In practice, that means:
- use
typescript-gothrough the interfaces it already intends consumers to use - track upstream by exact commit so behavior is reproducible and auditable
- never maintain a fork and never carry local patches against upstream
tsgo - implement hot paths in Rust, keep them zero-cost and high-performance, and
expose them to JS through
napi-rsso end users can author custom plugins and custom rules in JS/TS
Current focus:
- Full Rust-side stdio bindings for the tsgo API
- stdio LSP bindings with virtual-file support
- zero-cost-lean hot paths with msgpack-first defaults
napi-rsbindings that surface Rust performance to JS/TS authoring workflows- Rust-backed
tsgoAPI,utils, and virtual-document bindings for C, C++, Go, Zig, C#, Swift, and MoonBit - local multi-process orchestration, cache reuse, and experimental replicated state
- strict upstream pinning by exact
typescript-gocommit - regression tests and benchmarks against the real pinned upstream server
Current Status
- License: MIT
- Upstream policy:
ref/typescript-gois pinned and tracked by exact commit, with no local patching - Default API transport:
SyncMsgpackStdio - Runtime: custom in-house runtime, no
tokio - Fast-path bias:
CompactString,SmallVec,bumpalo,memchr,phf,FxHash - JS toolchain: Vite+ (
vp) with vp-managed Node24, pnpm10,oxfmt, andoxlint - Repo automation:
scripts/*.tsexecuted directly through Node24with--strip-types - Node bindings:
@corsa-bind/napi(src/bindings/nodejs/corsa_node) andcorsa-oxlint(src/bindings/nodejs/typescript_oxlint) (public npm packages that still expect a caller-managedtypescript-goexecutable) - Distributed orchestration:
experimental-distributedcargo feature - TS benchmark project:
bench - Example workspace:
examples - Default request timeout:
30s - Default graceful shutdown timeout:
2s - Default outbound queue capacity:
256 - Unstable upstream endpoints such as
printNodeare opt-in - Structured event sink:
TsgoObserver/TsgoEvent
Pinned upstream at the time of writing:
- Repository:
https://github.com/microsoft/typescript-go.git - Commit:
9c19dee6ab88ae11444837f16efa16a6b3dc9f59 - Lock file:
tsgo_ref.lock.toml
Workspace Layout
corsa_core: shared errors, process handles, and fast-path primitivescorsa_jsonrpc: stdio JSON-RPC framing and connection managementcorsa_client: typed tsgo stdio client bindings for JSON-RPC and msgpackcorsa_lsp: LSP client support plus virtual-document overlayscorsa_orchestrator: local orchestration, caching, and experimental replicated state / Raft corecorsa_runtime: lightweight custom runtime and task primitivescorsa_ref: exact upstream pin, sync, and verification toolingcorsa: top-level facade crate, mock server, and native benchmark binariessrc/bindings/c/corsa_ffi: shared C ABI over the Rustcorsa_client::ApiClient,corsa_core::utils, andcorsa_lsp::VirtualDocumentsurfacessrc/bindings/cpp,src/bindings/go,src/bindings/zig,src/bindings/csharp,src/bindings/swift,src/bindings/moonbit: thin language bindings layered on top ofcorsa_ffisrc/bindings/nodejs/corsa_node:napi-rsnative bindings and the@corsa-bind/napiTypeScript wrapper packagesrc/bindings/nodejs/typescript_oxlint: type-aware Oxlint rule framework powered bytsgobench: Vitest benchmark project for the Node bindingexamples: curatedexamples/nodejs,examples/rust, andexamples/typescript_oxlintflows from minimal start to real-project runs
For a detailed architecture walkthrough, design strategy, and implementation tips, see docs/project_guide.md. For deployment-oriented defaults, supported scope, and release checks, see docs/production_readiness.md. For support guarantees, compatibility, and semver expectations, see docs/support_policy.md. For distribution decisions and release dry-runs, see docs/release_guide.md. For dependency-policy and release-hardening expectations, see docs/supply_chain_policy.md.
Once trusted publishing is bootstrapped, a release is cut from main with:
vp run -w release minor
Quick Start
Enter the Nix dev shell first. It includes the toolchains for every binding
target under src/bindings:
nix develop
vp install
The Nix shell itself is authored in flake.tnix and compiled
to flake.nix with tnix:
tnix check-project .
tnix compile ./flake.tnix -o ./flake.nix
flake.tnix is intentionally kept as a thin tnix entrypoint, while the full
outputs implementation lives in nix/flake-outputs.nix.
That split avoids current tnix edges around some larger flake constructs.
See docs/tnix_notes.md for the current limitations and
the reasoning behind the layout.
Sync and verify the pinned upstream checkout:
vp run -w sync_ref
vp run -w verify_ref
Build everything through Vite+:
vp install
vp run -w build
vp check
Repository automation scripts now assume Node 24 so they can run TypeScript
directly through node --strip-types. The published npm packages themselves
still target Node 22+.
Examples
The repository now ships executable examples for Rust, @corsa-bind/napi, and
corsa-oxlint under examples/, from
minimal virtual-document edits up through checker-query walkthroughs and
opt-in upstream printer flows.
Run the smoke-tested examples with:
vp run -w examples_smoke
Run only the Rust smoke examples with:
vp run -w examples_rust_smoke
Run only the Node / TypeScript smoke examples with:
vp run -w examples_node_smoke
Run the real pinned-tsgo examples with:
vp run -w sync_ref
vp run -w verify_ref
vp run -w build_tsgo
vp run -w examples_real
Run the experimental distributed Rust example with:
vp run -w examples_rust_experimental
Type-Aware Oxlint
corsa-oxlint lets us write Oxlint JS plugins with a compact, self-hosted
type-aware authoring model while sourcing type information from the pinned
tsgo binary. The heavy lifting stays in Rust, then napi-rs binds
that implementation into JS so end users can keep writing custom plugins and
custom rules in JS/TS.
import { OxlintUtils } from "corsa-oxlint";
const createRule = OxlintUtils.RuleCreator((name) => `https://example.com/rules/${name}`);
export const noStringPlusNumber = createRule({
name: "no-string-plus-number",
meta: {
type: "problem",
docs: {
description: "forbid string + number",
requiresTypeChecking: true,
},
messages: {
unexpected: "string plus number is forbidden",
},
schema: [],
},
defaultOptions: [],
create(context) {
const services = OxlintUtils.getParserServices(context);
const checker = services.program.getTypeChecker();
return {
BinaryExpression(node) {
if (node.operator !== "+") {
return;
}
const left = checker.getTypeAtLocation(node.left);
const right = checker.getTypeAtLocation(node.right);
if (!left || !right) {
return;
}
if (
checker.typeToString(checker.getBaseTypeOfLiteralType(left) ?? left) === "string" &&
checker.typeToString(checker.getBaseTypeOfLiteralType(right) ?? right) === "number"
) {
context.report({ node, messageId: "unexpected" });
}
},
};
},
});
The rule-side type-aware config lives under settings.typescriptOxlint. Package
details and caveats are documented in src/bindings/nodejs/typescript_oxlint/README.md.
corsa-oxlint/rules exposes a TS-native type-aware rule set and plugin:
import { typescriptOxlintPlugin } from "corsa-oxlint/rules";
export default [
{
plugins: {
typescript: typescriptOxlintPlugin,
},
rules: {
"typescript/no-floating-promises": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/restrict-plus-operands": ["error", { allowNumberAndString: false }],
},
},
];
The rule framework is self-hosted and does not depend on third-party
TypeScript lint helper packages. Upstream tsgolint/internal/rules is now
used as a parity target and drift oracle rather than as a runtime bridge.
Example
use corsa::{
api::{ApiClient, ApiSpawnConfig},
runtime::block_on,
};
fn main() -> Result<(), corsa::TsgoError> {
block_on(async {
let client = ApiClient::spawn(
ApiSpawnConfig::new(".cache/tsgo")
.with_cwd("ref/typescript-go/_packages/api"),
)
.await?;
let init = client.initialize().await?;
println!("{}", init.current_directory);
client.close().await?;
Ok(())
})
}
Benchmarks
The repo ships two benchmark layers:
- Native Rust benchmark:
vp run -w bench_native - Native profiling benchmark:
vp run -w bench_native_profile - Node binding benchmark:
vp run -w bench_ts corsa-oxlintchecker benchmark:vp test bench --config ./vite.config.ts bench/src/typescript_oxlint.bench.tscorsa-oxlintnative-rule benchmark:vp test bench --config ./vite.config.ts bench/src/typescript_oxlint_rules.bench.ts- Combined benchmark + budget guard:
vp run -w bench
The TS benchmark writes machine-readable output to .cache/bench_ts.json.
The native benchmark writes machine-readable output to .cache/bench_native.json.
The native profiling benchmark writes machine-readable output to .cache/bench_native_profile.json.
The native Rust benchmark uses the real pinned tsgo binary through bench_real_tsgo.
Latest native measurements are documented in docs/performance.md.
Benchmarking rationale, implementation notes, and usage tips are documented in docs/benchmarking_guide.md.
CI structure, local reproduction steps, and troubleshooting notes are documented in docs/ci_guide.md.
On the pinned upstream commit and bundled datasets, msgpack was consistently faster than async JSON-RPC, which is why ApiSpawnConfig::new() defaults to SyncMsgpackStdio.
Regression Strategy
The repository is intentionally aggressive about change detection because typescript-go is still unstable.
cargo test --workspaceincludes mock-server integration tests, policy tests, and real-tsgo regression tests when.cache/tsgois availablesrc/bindings/rust/corsa/tests/real_tsgo_baseline.rslocks a real-server API summary to the pinned upstream commitsrc/bindings/rust/corsa/tests/real_tsgo_regression.rschecks both transports against the real pinned tsgo binary- the real-tsgo regression suite includes a hot-path guard that fails if msgpack falls too far behind JSON-RPC on the same machine
vp run -w bench_nativeandvp run -w bench_tsgive repeatable transport-level measurements for Rust and Nodevp run -w bench_verifyregenerates both reports and fails if benchmark samples disappear or hot-path budgets regresscorsa_refenforces detached-HEAD exact-commit verification forref/typescript-go- CI structure and local reproduction notes live in
docs/ci_guide.md
Upstream Tracking
typescript-go is under heavy development, so reproducibility is treated as a hard requirement.
- exact commit metadata lives in
tsgo_ref.lock.toml - sync and drift tooling lives in
docs/tsgo_dependency.md - CI and local reproduction details live in
docs/ci_guide.md ref/typescript-gomust remain on detachedHEAD- dirty upstream worktrees fail verification
Known Limitations
- Public APIs are still
0.x, so compatibility should be treated as conservative rather than frozen. printNodeis disabled by default because the pinned upstreamtsgocommit can panic ininternal/printeron real project data; opt in only when you accept that risk.- The distributed layer currently includes an in-process Raft core; full network transport between nodes is not finished yet.
- Some binary API surfaces are still exposed as opaque encoded payloads rather than fully decoded Rust AST types.
Development
Useful commands:
vp install
vp fmt
vp lint
vp test
vp check
vp run -w build
vp run -w bench
vp run -w bench_native
vp run -w bench_native_profile
vp run -w bench_ts
vp run -w bench_verify
vp test run --config ./vite.config.ts
vp test bench --config ./vite.config.ts --outputJson .cache/bench_ts.json
vp run -w sync_ref
vp run -w verify_ref
Dependencies
~165–480KB
~10K SLoC