Skip to content

Commit

Permalink
feat: Add simple coffee machine example for TypeScript docs
Browse files Browse the repository at this point in the history
  • Loading branch information
pojntfx committed Feb 21, 2024
1 parent 93dbb58 commit b2dec17
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 0 deletions.
177 changes: 177 additions & 0 deletions ts/bin/panrpc-example-websocket-coffee-client-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/* eslint-disable no-console */
import { exit, stdin, stdout } from "process";
import { createInterface } from "readline/promises";
// eslint-disable-next-line import/no-extraneous-dependencies
import { JSONParser } from "@streamparser/json-whatwg";
// eslint-disable-next-line import/no-extraneous-dependencies
import { WebSocket } from "ws";
import { ILocalContext, IRemoteContext, Registry } from "../index";

class RemoteControl {
// eslint-disable-next-line class-methods-use-this
async SetCoffeeMachineBrewing(ctx: ILocalContext, brewing: boolean) {
if (brewing) {
console.log("Coffee machine is now brewing");
} else {
console.log("Coffee machine has stopped brewing");
}
}
}

class CoffeeMachine {
// eslint-disable-next-line class-methods-use-this
async BrewCoffee(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ctx: IRemoteContext,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
variant: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
size: number
): Promise<number> {
return 0;
}
}

let clients = 0;

const registry = new Registry(
new RemoteControl(),
new CoffeeMachine(),

{
onClientConnect: () => {
clients++;

console.log(clients, "coffee machines connected");
},
onClientDisconnect: () => {
clients--;

console.log(clients, "coffee machines connected");
},
}
);

(async () => {
console.log(`Enter one of the following numbers followed by <ENTER> to brew a coffee:
- 1: Brew small Cafè Latte
- 2: Brew large Cafè Latte
- 3: Brew small Americano
- 4: Brew large Americano`);

const rl = createInterface({ input: stdin, output: stdout });

// eslint-disable-next-line no-constant-condition
while (true) {
const line =
// eslint-disable-next-line no-await-in-loop
await rl.question("");

// eslint-disable-next-line no-await-in-loop
await registry.forRemotes(async (remoteID, remote) => {
switch (line) {
case "1":
case "2":
try {
// eslint-disable-next-line no-await-in-loop
const res = await remote.BrewCoffee(
undefined,
"latte",
line === "1" ? 100 : 200
);

console.log("Remaining water:", res, "ml");
} catch (e) {
console.error(`Couldn't brew coffee: ${e}`);
}

break;

case "3":
case "4":
try {
// eslint-disable-next-line no-await-in-loop
const res = await remote.BrewCoffee(
undefined,
"americano",
line === "3" ? 100 : 200
);

console.log("Remaining water:", res, "ml");
} catch (e) {
console.error(`Couldn't brew coffee: ${e}`);
}

break;

default:
console.log(`Unknown letter ${line}, ignoring input`);
}
});
}
})();

const socket = new WebSocket("ws://localhost:1337");

socket.addEventListener("error", (e) => {
console.error("Disconnected with error:", e);

exit(1);
});
socket.addEventListener("close", () => exit(0));

await new Promise<void>((res, rej) => {
socket.addEventListener("open", () => res());
socket.addEventListener("error", rej);
});

const encoder = new WritableStream({
write(chunk) {
socket.send(JSON.stringify(chunk));
},
});

const parser = new JSONParser({
paths: ["$"],
separator: "",
});
const parserWriter = parser.writable.getWriter();
const parserReader = parser.readable.getReader();
const decoder = new ReadableStream({
start(controller) {
parserReader
.read()
.then(async function process({ done, value }) {
if (done) {
controller.close();

return;
}

controller.enqueue(value?.value);

parserReader
.read()
.then(process)
.catch((e) => controller.error(e));
})
.catch((e) => controller.error(e));
},
});
socket.addEventListener("message", (m) => parserWriter.write(m.data as string));
socket.addEventListener("close", () => {
parserReader.cancel();
parserWriter.abort();
});

registry.linkStream(
encoder,
decoder,

(v) => v,
(v) => v
);

console.log("Connected to localhost:1337");
123 changes: 123 additions & 0 deletions ts/bin/panrpc-example-websocket-coffee-server-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-disable no-console */
// eslint-disable-next-line import/no-extraneous-dependencies
import { JSONParser } from "@streamparser/json-whatwg";
// eslint-disable-next-line import/no-extraneous-dependencies
import { WebSocketServer } from "ws";
import { ILocalContext, IRemoteContext, Registry } from "../index";

class CoffeeMachine {
constructor(private supportedVariants: string[], private waterLevel: number) {
this.BrewCoffee = this.BrewCoffee.bind(this);
}

async BrewCoffee(
ctx: ILocalContext,
variant: string,
size: number
): Promise<number> {
if (!this.supportedVariants.includes(variant)) {
throw new Error("unsupported variant");
}

if (this.waterLevel - size < 0) {
throw new Error("not enough water");
}

console.log("Brewing coffee variant", variant, "in size", size, "ml");

await new Promise((r) => {
setTimeout(r, 5000);
});

this.waterLevel -= size;

return this.waterLevel;
}
}

class RemoteControl {
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
async SetCoffeeMachineBrewing(ctx: IRemoteContext, brewing: boolean) {}
}

let clients = 0;

const registry = new Registry(
new CoffeeMachine(["latte", "americano"], 1000),
new RemoteControl(),

{
onClientConnect: () => {
clients++;

console.log(clients, "remote controls connected");
},
onClientDisconnect: () => {
clients--;

console.log(clients, "remote controls connected");
},
}
);

const server = new WebSocketServer({
host: "localhost",
port: 1337,
});

server.on("connection", (socket) => {
socket.addEventListener("error", (e) => {
console.error("Remote control disconnected with error:", e);
});

const encoder = new WritableStream({
write(chunk) {
socket.send(JSON.stringify(chunk));
},
});

const parser = new JSONParser({
paths: ["$"],
separator: "",
});
const parserWriter = parser.writable.getWriter();
const parserReader = parser.readable.getReader();
const decoder = new ReadableStream({
start(controller) {
parserReader
.read()
.then(async function process({ done, value }) {
if (done) {
controller.close();

return;
}

controller.enqueue(value?.value);

parserReader
.read()
.then(process)
.catch((e) => controller.error(e));
})
.catch((e) => controller.error(e));
},
});
socket.addEventListener("message", (m) =>
parserWriter.write(m.data as string)
);
socket.addEventListener("close", () => {
parserReader.cancel();
parserWriter.abort();
});

registry.linkStream(
encoder,
decoder,

(v) => v,
(v) => v
);
});

console.log("Listening on localhost:1337");

0 comments on commit b2dec17

Please sign in to comment.