Compare commits

...

2 Commits

Author SHA1 Message Date
Aiden Cline 111f5858d9 refactor(tui): simplify rpc failure propagation 2026-06-04 11:45:42 -05:00
Aiden Cline 45efcc893c fix(tui): reject worker rpc failures 2026-06-04 11:42:13 -05:00
2 changed files with 52 additions and 8 deletions
+21 -8
View File
@@ -6,8 +6,14 @@ export function listen(rpc: Definition) {
onmessage = async (evt) => {
const parsed = JSON.parse(evt.data)
if (parsed.type === "rpc.request") {
const result = await rpc[parsed.method](parsed.input)
postMessage(JSON.stringify({ type: "rpc.result", result, id: parsed.id }))
try {
const result = await rpc[parsed.method](parsed.input)
postMessage(JSON.stringify({ type: "rpc.result", result, id: parsed.id }))
} catch (error) {
postMessage(
JSON.stringify({ type: "rpc.error", error: error instanceof Error ? error.message : String(error), id: parsed.id }),
)
}
}
}
}
@@ -20,15 +26,22 @@ export function client<T extends Definition>(target: {
postMessage: (data: string) => void | null
onmessage: ((this: Worker, ev: MessageEvent<any>) => any) | null
}) {
const pending = new Map<number, (result: any) => void>()
const pending = new Map<number, { resolve: (result: any) => void; reject: (error: any) => void }>()
const listeners = new Map<string, Set<(data: any) => void>>()
let id = 0
target.onmessage = async (evt) => {
const parsed = JSON.parse(evt.data)
if (parsed.type === "rpc.result") {
const resolve = pending.get(parsed.id)
if (resolve) {
resolve(parsed.result)
const request = pending.get(parsed.id)
if (request) {
request.resolve(parsed.result)
pending.delete(parsed.id)
}
}
if (parsed.type === "rpc.error") {
const request = pending.get(parsed.id)
if (request) {
request.reject(new Error(parsed.error))
pending.delete(parsed.id)
}
}
@@ -44,8 +57,8 @@ export function client<T extends Definition>(target: {
return {
call<Method extends keyof T>(method: Method, input: Parameters<T[Method]>[0]): Promise<ReturnType<T[Method]>> {
const requestId = id++
return new Promise((resolve) => {
pending.set(requestId, resolve)
return new Promise((resolve, reject) => {
pending.set(requestId, { resolve, reject })
target.postMessage(JSON.stringify({ type: "rpc.request", method, input, id: requestId }))
})
},
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, test } from "bun:test"
import { Rpc } from "@/util/rpc"
type TestRpc = {
fail(input: undefined): Promise<void>
}
type Target = Parameters<typeof Rpc.client<TestRpc>>[0]
describe("Rpc", () => {
test("rejects pending calls when the worker reports an error", async () => {
const target: Target = {
postMessage(data) {
const request = JSON.parse(data)
target.onmessage?.call(
{} as Worker,
{
data: JSON.stringify({
type: "rpc.error",
id: request.id,
error: "boom",
}),
} as MessageEvent<any>,
)
},
onmessage: null,
}
await expect(Rpc.client<TestRpc>(target).call("fail", undefined)).rejects.toThrow("boom")
})
})