import type { DiffOptions } from "@bindings/cob/DiffOptions";
import type { Diff } from "@bindings/diff/Diff";
import type { Stats } from "@bindings/diff/Stats";
import type { Commit } from "@bindings/repo/Commit";
import * as tauri from "@tauri-apps/api/core";
import { cached } from "@app/lib/cached";
export async function invoke<T = null>(
cmd: string,
args?: tauri.InvokeArgs,
options?: tauri.InvokeOptions,
): Promise<T> {
return withTestBackend<T>(tauri.invoke, cmd, args, options);
}
/**
* Invoking a Tauri command returned an error with the given message
*/
class InvokeError extends Error {
name = "InvokeError";
code: string;
constructor(message: string, code = "Unknown Error") {
super(message);
this.code = code;
Error.captureStackTrace?.(this, InvokeError);
}
}
async function withTestBackend<T>(
fn: (
cmd: string,
args?: tauri.InvokeArgs,
options?: tauri.InvokeOptions,
) => Promise<T>,
cmd: string,
args?: tauri.InvokeArgs,
options?: tauri.InvokeOptions,
) {
if (window.__TAURI_INTERNALS__) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return fn(cmd, args, options).catch((error: any) => {
if (typeof error === "object" && error !== null && "message" in error) {
throw new InvokeError(String(error.message), String(error.code));
} else {
throw new InvokeError(`Invalid error object: ${error}`);
}
});
} else {
return fetch(
`http://127.0.0.1:${import.meta.env.VITE_TEST_HTTP_API_PORT ?? 8081}/${cmd}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
},
).then(async response => {
if (response.status === 404) {
console.log("Got a 404 response:", response);
return null;
}
const json = await response.json();
if (!response.ok) {
if (typeof json === "object" && json !== null && "message" in json) {
throw new InvokeError(String(json.message), String(json.code));
} else {
throw new InvokeError(`Invalid error object: ${json}`);
}
}
return json;
});
}
}
async function getDiff(rid: string, options: DiffOptions): Promise<Diff> {
return withTestBackend(tauri.invoke, "get_diff", {
rid,
options,
});
}
export const cachedGetDiff = cached(
getDiff,
(...[rid, options]) =>
`get_diff:${rid}:${JSON.stringify(options, Object.keys(options).sort())}`,
{ max: 10_000 },
);
async function listCommits(
rid: string,
base: string,
head: string,
): Promise<Commit[]> {
return withTestBackend(tauri.invoke, "list_commits", { rid, base, head });
}
export const cachedListCommits = cached(
listCommits,
(...[rid, base, head]) => `list_commits:${rid}:${base}:${head}`,
{ max: 5_000 },
);
async function repoCommitCount(rid: string, head: string): Promise<number> {
return withTestBackend(tauri.invoke, "repo_commit_count", { rid, head });
}
export const cachedRepoCommitCount = cached(
repoCommitCount,
(...[rid, head]) => `repo_commit_count:${rid}:${head}`,
{ max: 5_000 },
);
async function diffStats(
rid: string,
base: string,
head: string,
): Promise<Stats> {
return withTestBackend(tauri.invoke, "diff_stats", {
rid,
base,
head,
});
}
export const cachedDiffStats = cached(
diffStats,
(...[rid, base, head]) => `diff_stats:${rid}:${base}:${head}`,
{ max: 10_000 },
);
async function getCommitDiff(
rid: string,
sha: string,
unified = 3,
highlight = true,
): Promise<Diff> {
return withTestBackend(tauri.invoke, "get_commit_diff", {
rid,
sha,
unified,
highlight,
});
}
// Commits are immutable, so SHA is a perfect cache key. Cap entries since
// each highlighted Diff can be sizeable.
export const cachedGetCommitDiff = cached(
getCommitDiff,
(...[rid, sha, unified, highlight]) =>
`get_commit_diff:${rid}:${sha}:${unified}:${highlight}`,
{ max: 100 },
);
export async function writeToClipboard(
text: string,
opts?: {
label?: string;
},
) {
if (window.__TAURI_INTERNALS__) {
await tauri.invoke("plugin:clipboard-manager|write_text", {
label: opts?.label,
text,
});
} else {
await navigator.clipboard.writeText(text);
}
}
export { InvokeError };