Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Open-Rx (ORx) : TS to .NET Sidecar Communication

MIT License


Welcome to the ORx PoC!

Hey there! Ever hit that wall where rewriting a rock-solid .NET library just to stay "pure JS" feels like insanity? Or maybe you're staring at legacy cryptographic code that's been battle-tested for years, and the thought of porting it makes you want to cry into your coffee. Yeah, we've all been there – sometimes you just need TypeScript's flexibility and .NET's raw power without losing your mind.

Maybe you're dealing with legacy .NET libraries that you can't (or don't want to) rewrite, or you need cryptographic signing that's already battle-tested in C#. Or perhaps you're building a Node.js app that needs to tap into Windows-specific APIs without dragging the whole app into .NET-land. Sound familiar?

Here's the thing: this pattern isn't some wild experiment – it's proven in production. VS Code itself uses TypeScript for the editor while spawning .NET processes for features like OmniSharp (C# IntelliSense) and debugging adapters. GitHub's Copilot extension? Same deal – TypeScript orchestration with heavyweight AI inference running in separate processes. Electron apps do this all the time when they need OS-level integrations. The sidecar pattern lets each technology do what it does best, without forcing everything into a single runtime.

So here's what I thought: why not create a minimal, no-BS template that shows exactly how to wire this up? This PoC strips away the domain-specific stuff (originally built for electronic prescriptions in healthcare, but that's beside the point) and gives you the bare essentials: TypeScript spawning a .NET sidecar, managing its lifecycle, and communicating cleanly across Linux and Windows. Clone it, adapt it, solve your own interop headaches. If this saves you a day of Stack Overflow rabbit holes, I'll call it a win. Let's dive in!


What This PoC Does

Here's the backstory: I originally built this for Japan's electronic prescription system (yeah, healthcare tech – fun times). The catch? There was this digital signature library that started life as a Java implementation, then got ported to .NET, and I absolutely had to use it. No alternatives, no shortcuts – it was the only game in town for compliance reasons.

But here's where it got interesting. I wanted to run this whole thing on Linux (because vendor lock-in is the worst), but I also needed a UI layer that didn't feel like a 2005 Windows Forms throwback. So I thought: why not split it? Use TypeScript for the flexible, cross-platform UI stuff, and let .NET handle the heavyweight cryptographic signing under the hood. Spoiler alert: it worked beautifully.

Once I proved the pattern worked, I realized this setup isn't just useful for prescription signatures – it's a universal solution for any TS + .NET interop scenario. So I stripped out all the healthcare-specific bits (JAHIS standards, HPKI cards, XML schemas – you don't need to know any of that) and distilled it down to the bare essentials: how to spawn a .NET sidecar from TypeScript, keep them in sync, and handle cross-platform quirks gracefully.

What you're looking at:

  • TypeScript Side (ts-app): Acts as the "launcher" or main app. It spawns the .NET executable as a subprocess, manages its lifecycle (including graceful shutdown when the parent exits), and handles platform-specific paths (Linux vs. Windows).
  • .NET Side (dotnet-sidecar): A minimal console app that simulates a long-running service. It monitors stdin for parent disconnection (a key sidecar survival pattern), logs its activity, and shuts down cleanly when told to.

No fancy IPC protocols (no gRPC, no message queues) – just stdin/stdout and process signals. Because boring, obvious solutions are often the ones that actually survive production.


How It Works

Here's what happens when you run this PoC:

sequenceDiagram
    participant TS as TypeScript<br/>(launcher.ts)
    participant OS as OS Process Manager
    participant NET as .NET Sidecar<br/>(Program.cs)
    
    TS->>OS: spawn("dotnet-sidecar") with stdin/stdout pipes
    OS->>NET: Start new process
    
    activate NET
    NET->>NET: Task.Run: Start stdin monitor
    NET-->>TS: stdout: "Sidecar started. PID: X"
    NET-->>TS: stdout: "gRPC server listening..."
    NET->>NET: Main thread: while(true) await Task.Delay(1000)
    
    Note over TS,NET: Both processes running independently
    
    TS->>TS: Demo timer (5s) expires
    TS->>TS: process.exit(0)
    TS->>OS: Terminate and close pipes
    
    Note over NET: stdin monitoring task detects closure
    NET->>NET: Console.In.Peek() returns -1
    NET-->>TS: stdout: "Parent disconnected. Shutting down."
    NET->>NET: Environment.Exit(0)
    deactivate NET
    
    OS->>OS: Clean up process resources
Loading

Key mechanism: The .NET sidecar runs two things simultaneously:

  1. Main thread: An infinite loop (while(true) await Task.Delay(1000)) simulating long-running work
  2. Background task: Continuously monitors Console.In.Peek() for stdin closure

When the TypeScript parent exits, the OS closes the stdin pipe. The background task detects this (Peek() returns -1) and calls Environment.Exit(0), terminating the entire process. No heartbeats, no polling – just OS-level pipe behavior doing the heavy lifting.


Key wins from this PoC

  • Cross-OS Compatibility: Tested on Ubuntu (Linux) and Windows – the .NET binary runs without a hitch on both.
  • Subprocess Magic: Uses Node.js's child_process to spawn and communicate with the .NET app via stdin/stdout. No gRPC or fancy IPC yet – keeping it simple for verification.
  • Build Artifacts: The ts-app/bin folder contains platform-specific executables (e.g., for Linux and Windows), making it easy to distribute or containerize.

In short: If TS can ping .NET and get a pong back, we're golden for expanding to real JAHIS data handling and XAdES signing down the line.


Project Structure

Here's the file tree for quick navigation:

.
├── dotnet-sidecar
│   ├── Program.cs          # The .NET console app entry point
│   ├── bin                 # Build outputs (Debug/Release)
│   ├── dotnet-sidecar.csproj  # .NET project file
│   └── obj                 # Intermediate build files
└── ts-app
    ├── bin                 # Compiled TS binaries (Linux/Windows executables)
    ├── launcher.ts         # Main TS script to launch and communicate with .NET
    ├── node_modules        # NPM dependencies
    ├── package-lock.json   # Locked dependencies
    ├── package.json        # TS project config and scripts
    └── tsconfig.json       # TypeScript compiler options

Getting Started

Want to run this yourself? It's straightforward – no external deps beyond Node.js and .NET SDK.

Prerequisites

Setup and Run

  1. Clone the Repo:

    git clone https://github.com/sakai-sktech/open-rx.git
    cd open-rx
  2. Build the .NET Sidecar:

    • Navigate to dotnet-sidecar:

      cd dotnet-sidecar
    • Build for Linux (if on Linux or for cross-platform deployment):

      dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -o ../ts-app/bin/linux
    • Build for Windows (if on Windows or for cross-platform deployment):

      dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o ../ts-app/bin/win
    • This creates a self-contained, single-file executable in ts-app/bin/linux/ or ts-app/bin/win/ (e.g., dotnet-sidecar for Linux, dotnet-sidecar.exe for Windows).

    • The --self-contained flag bundles the .NET runtime, so end users don't need .NET installed.

    • On Linux, you may need to grant execute permissions: chmod +x ../ts-app/bin/linux/dotnet-sidecar

  3. Build the TS App:

    • Navigate to ts-app:

      cd ../ts-app
      npm install
      tsc  # Or use `npm run build` if scripted
    • This compiles launcher.ts to JS, and you can package it with pkg or similar for binaries in bin/ (as in your setup).

  4. Run the PoC:

    • From the project root directory, execute:

      npx ts-node ts-app/launcher.ts
    • Expected output:

      [TS] Launching sidecar: /home/sakai/dev/ukhpkimock/ts-app/bin/linux/dotnet-sidecar
      [TS] Application running. Will exit in 5 seconds...
      [.NET]: [.NET] Sidecar started. PID: 91939
      [.NET]: [.NET] OS: Unix 6.14.0.36
      [.NET]: [.NET] gRPC server listening... (simulated)
      [TS] Demo complete. Exiting.
      [TS] Shutting down. Terminating sidecar...
    • Note: You may see a deprecation warning (node:xxxxx) [DEP0180] DeprecationWarning - this is harmless and can be ignored.

    • The demo automatically exits after 5 seconds, gracefully terminating the .NET sidecar.

    • Test on both Linux and Windows to confirm cross-platform compatibility.

If things go sideways (e.g., path issues), check console logs – we've kept error handling verbose for debugging.

Common Issues:

  • "dotnet-sidecar not found": Ensure you've built the .NET sidecar in step 2
  • "Permission denied": On Linux, the binary may need execute permissions: chmod +x ts-app/bin/linux/dotnet-sidecar
  • TypeScript errors: Run npm install in the ts-app directory to ensure all dependencies are installed

Why This Matters

This isn't just about spawning processes – it's about freedom of choice. Want to leverage battle-tested .NET libraries without abandoning your TypeScript ecosystem? Need to escape vendor lock-in by running on Linux? This pattern gives you both, without compromise.

The sidecar approach means you're not rewriting working code just to fit a technology mandate. You're composing solutions from the best tools available, regardless of runtime. That's what sustainable software looks like.


What's Next?

This PoC is intentionally minimal – it's a template, not a complete solution. The goal here is to show you the plumbing works, so you can use it as a starting point for your own projects.

Want to see this pattern in production? Check out Real-World Examples – OmniSharp, Prisma, GitLens, and more use this exact architecture.

Got a use case where TypeScript's flexibility meets .NET's power? Maybe you need to call a legacy .NET library from a modern Node.js app, or vice versa. This setup gives you a proven pattern for:

  • Spawning platform-specific binaries from TS
  • Managing parent-child process lifecycles
  • Handling cross-platform paths and signals
  • Building self-contained executables for easy distribution

Take this, tweak it, build something cool. That's what open source is all about, right? If you end up using this in a project (especially in healthcare or other regulated industries), I'd love to hear about it – drop a note or open a discussion!

And hey, if you spot ways to improve this PoC, PRs are always welcome. Let's make inter-process communication less painful for everyone.


Contributing

Feel free to fork, PR, or open issues! We're all about open-source vibes in healthcare tech.


License

This project is licensed under the MIT License - see the LICENSE file for details.


Thanks for checking this out! If this PoC sparks ideas or saves you time, star the repo or drop a note. Let’s make cross-runtime interop boring, reliable, and lock-in-free.

Releases

Packages

Contributors

Languages