[Refactoring Guru: Adapter](https://refactoring.guru/design-patterns/adapter) [[Projects/kkrpc|kkrpc]] is a RPC protocol I developed for Kunkun, that's what `kk` stands for. Also see the blog [[Blogs/kkrpc|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. ```ts title="interface.ts" export interface IoInterface { name: string; read(): Promise<Buffer | Uint8Array | string | null>; // Reads input write(data: string): Promise<void>; // Writes output } ``` For example, [[Projects/kkrpc|kkrpc]] provides `NodeIo` adapter, `DenoIo` adapter, `TauriShellStdio` adapter. Here is an example: [TauriShellStdio](https://github.com/kunkunsh/kkrpc/blob/2ae006336b3c60982a80a14df9c4adac1b71f8b2/packages/kkrpc/src/adapters/tauri.ts#L4) ```ts 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](https://github.com/kunkunsh/kkrpc/blob/2ae006336b3c60982a80a14df9c4adac1b71f8b2/packages/kkrpc/src/adapters/deno.ts#L9) ```ts 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. ![[Design Pattern/Notes/structural/adapter#Analogy|adapter]]