Refactoring Guru: Adapter kkrpc is a RPC protocol I developed for Kunkun, that’s what kk stands for. Also see the blog kkrpc. Its bidirectional RPC channel that support many environments

  1. stdio
    1. deno
    2. bun
    3. node
  2. iframe
  3. web worker
  4. browser main thread
  5. Chrome Extension
  6. Tauri
  7. http
  8. WebSocket As long as you can setup a connection between 2 environments, kkrpc supports it. e.g. postMessage, stdio (stdin and stdout).

So how does all of these environments communicate?

The answer is adapter.

Each environment has an adapter that implements the following interface.

interface.ts
export interface IoInterface {
  name: string;
  read(): Promise<Buffer | Uint8Array | string | null>; // Reads input
  write(data: string): Promise<void>; // Writes output
}

For example, kkrpc provides NodeIo adapter, DenoIo adapter, TauriShellStdio adapter. Here is an example: TauriShellStdio

import {
  Child,
  EventEmitter,
  type OutputEvents,
} from "@tauri-apps/plugin-shell";
import type { IoInterface } from "../interface";
 
export class TauriShellStdio implements IoInterface {
  name = "tauri-shell-stdio";
  constructor(
    private readStream: EventEmitter<OutputEvents<string>>, // stdout of child process
    private childProcess: Child
  ) {}
 
  read(): Promise<string | Uint8Array | null> {
    return new Promise((resolve, reject) => {
      this.readStream.on("data", (chunk) => {
        resolve(chunk);
      });
    });
  }
  async write(data: string): Promise<void> {
    return this.childProcess.write(data + "\n");
  }
}

Another example: DenoIo

import { Buffer } from "node:buffer";
import type { IoInterface } from "../interface.ts";
 
/**
 * Stdio implementation for Deno
 * Deno doesn't have `process` object, and have a completely different stdio API,
 * This implementation wrap Deno's `Deno.stdin` and `Deno.stdout` to follow StdioInterface
 */
export class DenoIo implements IoInterface {
  private reader: ReadableStreamDefaultReader<Uint8Array>;
  name = "deno-io";
 
  constructor(
    private readStream: ReadableStream<Uint8Array> // private writeStream: WritableStream<Uint8Array>
  ) {
    this.reader = this.readStream.getReader();
    // const writer = this.writeStream.getWriter()
    // const encoder = new TextEncoder()
 
    // writer.write(encoder.encode("hello"))
  }
 
  async read(): Promise<Buffer | null> {
    const { value, done } = await this.reader.read();
    if (done) {
      return null; // End of input
    }
    return Buffer.from(value);
  }
 
  write(data: string): Promise<void> {
    const encoder = new TextEncoder();
    const encodedData = encoder.encode(data + "\n");
    Deno.stdout.writeSync(encodedData);
    return Promise.resolve();
  }
}

To support bidirectional communication the adapter should be able to read from and write to the channel. As long as the environments translate their data to what the channel understands, the other side will understand. Adapters are like human translators, they must be able to listen (read) and speak (write) a language to communicate.

Analogy

Here is an analogy. A Chinese and and French president are having a meeting, there is a Chinese translator who know Chinese and English, and a French translator who know French and English. Then the 2 translators can communicate through English. In this analogy, English language is the bidirectional channel. The 2 translators are the adapters, and the 2 presidents are the 2 environments.

graph TD
    subgraph Environment1[Environment 1]
        ChinesePresident[Chinese President] <--> ChineseTranslator[Chinese Translator]
    end

    subgraph ChannelLayer[Bidirectional Channel]
        English[English Language]
    end

    subgraph Environment2[Environment 2]
        FrenchTranslator[French Translator] <--> FrenchPresident[French President]
    end

    ChineseTranslator <--> English
    English <--> FrenchTranslator

    style ChinesePresident fill:#f9d5e5,stroke:#333
    style FrenchPresident fill:#f9d5e5,stroke:#333
    style ChineseTranslator fill:#d5e8f9,stroke:#333
    style FrenchTranslator fill:#d5e8f9,stroke:#333
    style English fill:#d5f9e8,stroke:#333
    style ChannelLayer fill:#f5f5f5,stroke-dasharray: 5 5
Link to original