Generate the proxy wire types with ts-rs (@yaakapp-internal/send-proxy)

This commit is contained in:
Gregory Schier
2026-08-17 08:43:59 -07:00
parent 8e466fc1a0
commit d4007d3499
13 changed files with 166 additions and 62 deletions
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"@yaakapp-internal/models": "^1.0.0",
"@yaakapp-internal/rpc-schema": "^1.0.0",
"@yaakapp-internal/send-proxy": "^1.0.0",
"@yaakapp-internal/web": "^1.0.0",
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
+6 -56
View File
@@ -1,19 +1,11 @@
/**
* The wire to the send proxy: where it is, what goes up, and what comes back.
* The wire to the send proxy: where it is, and how to read what comes back.
*
* These shapes mirror `crates-server/yaak-send-proxy/src/wire.rs` by hand. The
* proxy is a separate binary with its own release cadence, so the contract is
* written down on both sides rather than generated across them; a change to one
* is a change to the other, and the frame `type` tags are the versioning.
* The shapes themselves are generated from `crates-server/yaak-send-proxy/src/wire.rs`
* into `@yaakapp-internal/send-proxy`, so the two sides cannot drift silently.
*/
import type {
Cookie,
HttpRequest,
HttpResponseEventData,
HttpResponseHeader,
HttpSendSettings,
} from "@yaakapp-internal/models";
import type { Frame } from "@yaakapp-internal/send-proxy";
/* ------------------------------- location -------------------------------- */
@@ -33,54 +25,12 @@ export function proxySendUrl(): string {
return `${proxyBaseUrl()}/v1/http/send`;
}
/* --------------------------------- up ------------------------------------ */
/** The body of `POST /v1/http/send`. */
export interface ProxyRequestBody {
/** The rendered request, in the model shape (see `wire.rs` `SendRequest.request`). */
request: HttpRequest;
settings: HttpSendSettings;
/** The jar's cookies to start from, or `null` for no jar at all. */
cookies: Cookie[] | null;
}
/* -------------------------------- down ----------------------------------- */
export interface ProxySendResponse {
type: "response";
status: number;
statusReason: string | null;
url: string;
remoteAddr: string | null;
version: string | null;
headers: HttpResponseHeader[];
requestHeaders: HttpResponseHeader[];
contentLength: number | null;
elapsedHeaders: number;
elapsedDns: number;
}
export type ProxyFrame =
/** A timeline event in the `http_response_event.event` shape. */
| { type: "event"; event: HttpResponseEventData }
| ProxySendResponse
/** A body chunk, decompressed, base64. */
| { type: "body"; data: string }
| {
type: "done";
elapsed: number;
contentLength: number;
contentLengthCompressed: number;
cookies: Cookie[] | null;
}
| { type: "error"; message: string; cookies: Cookie[] | null };
/**
* Yield frames from an NDJSON stream as they arrive. A partial trailing line is
* held until its newline comes; anything left when the stream ends is dropped,
* because a frame without its newline is a frame the proxy didn't finish writing.
*/
export async function* readFrames(stream: ReadableStream<Uint8Array>): AsyncGenerator<ProxyFrame> {
export async function* readFrames(stream: ReadableStream<Uint8Array>): AsyncGenerator<Frame> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let buffer = "";
@@ -93,7 +43,7 @@ export async function* readFrames(stream: ReadableStream<Uint8Array>): AsyncGene
while (newline !== -1) {
const line = buffer.slice(0, newline);
buffer = buffer.slice(newline + 1);
if (line.trim() !== "") yield JSON.parse(line) as ProxyFrame;
if (line.trim() !== "") yield JSON.parse(line) as Frame;
newline = buffer.indexOf("\n");
}
}
+4 -4
View File
@@ -31,8 +31,8 @@ import type {
HttpResponseEventData,
HttpSendSettings,
} from "@yaakapp-internal/models";
import type { Frame, SendRequest } from "@yaakapp-internal/send-proxy";
import type { WorkerConnection } from "./connection";
import type { ProxyFrame, ProxyRequestBody, ProxySendResponse } from "./proxy";
import { proxySendUrl, readFrames } from "./proxy";
/* -------------------------------- shapes --------------------------------- */
@@ -106,7 +106,7 @@ async function runSend(
const timeline = new TimelineWriter(db, response.id, response.workspaceId);
timeline.push(prepared.settingEvents);
const body: ProxyRequestBody = {
const body: SendRequest = {
request: prepared.request,
settings: prepared.settings,
cookies: prepared.cookieJar?.cookies ?? null,
@@ -139,7 +139,7 @@ async function runSend(
const chunks: Uint8Array[] = [];
let received = 0;
let lastProgress = startedAt;
let terminal: ProxyFrame | null = null;
let terminal: Frame | null = null;
for await (const frame of readFrames(res.body)) {
switch (frame.type) {
@@ -201,7 +201,7 @@ async function runSend(
});
}
function headOf(frame: ProxySendResponse): ResponsePatch {
function headOf(frame: Extract<Frame, { type: "response" }>): ResponsePatch {
return {
state: "connected",
status: frame.status,