Skip to content

Commit 5d71d1e

Browse files
Merge pull request #19 from offendingcommit/feat/scoped-client-foundation
feat(api): add scoped multi-instance query client
2 parents 2d7937e + de8db4b commit 5d71d1e

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import type { Instance } from "@/lib/config";
3+
import { createScopedClient } from "./scopedClient";
4+
5+
function err(e: unknown): never {
6+
throw new Error(typeof e === "object" ? JSON.stringify(e) : String(e));
7+
}
8+
9+
// Query keys are scoped by instance.id so caches never collide across columns.
10+
const CK = {
11+
workspaces: (instId: string, page: number, size: number) =>
12+
["compare", instId, "workspaces", page, size] as const,
13+
peers: (instId: string, wsId: string, page: number, size: number) =>
14+
["compare", instId, "peers", wsId, page, size] as const,
15+
peerRepresentation: (instId: string, wsId: string, pId: string) =>
16+
["compare", instId, "peer-representation", wsId, pId] as const,
17+
peerCard: (instId: string, wsId: string, pId: string) =>
18+
["compare", instId, "peer-card", wsId, pId] as const,
19+
};
20+
21+
export function useScopedWorkspaces(instance: Instance, page = 1, pageSize = 20) {
22+
return useQuery({
23+
queryKey: CK.workspaces(instance.id, page, pageSize),
24+
queryFn: async () => {
25+
const client = createScopedClient(instance);
26+
const { data, error } = await client.POST("/v3/workspaces/list", {
27+
params: { query: { page, page_size: pageSize } },
28+
body: {},
29+
});
30+
return data ?? err(error);
31+
},
32+
});
33+
}
34+
35+
export function useScopedPeers(instance: Instance, workspaceId: string, page = 1, pageSize = 20) {
36+
return useQuery({
37+
queryKey: CK.peers(instance.id, workspaceId, page, pageSize),
38+
queryFn: async () => {
39+
const client = createScopedClient(instance);
40+
const { data, error } = await client.POST("/v3/workspaces/{workspace_id}/peers/list", {
41+
params: { path: { workspace_id: workspaceId }, query: { page, page_size: pageSize } },
42+
body: {},
43+
});
44+
return data ?? err(error);
45+
},
46+
enabled: Boolean(workspaceId),
47+
});
48+
}
49+
50+
export function useScopedPeerRepresentation(
51+
instance: Instance,
52+
workspaceId: string,
53+
peerId: string,
54+
) {
55+
return useQuery({
56+
queryKey: CK.peerRepresentation(instance.id, workspaceId, peerId),
57+
queryFn: async () => {
58+
const client = createScopedClient(instance);
59+
const { data, error } = await client.POST(
60+
"/v3/workspaces/{workspace_id}/peers/{peer_id}/representation",
61+
{
62+
params: { path: { workspace_id: workspaceId, peer_id: peerId } },
63+
body: { max_conclusions: 20 },
64+
},
65+
);
66+
return data ?? err(error);
67+
},
68+
enabled: Boolean(workspaceId) && Boolean(peerId),
69+
});
70+
}
71+
72+
export function useScopedPeerCard(instance: Instance, workspaceId: string, peerId: string) {
73+
return useQuery({
74+
queryKey: CK.peerCard(instance.id, workspaceId, peerId),
75+
queryFn: async () => {
76+
const client = createScopedClient(instance);
77+
const { data, error } = await client.GET(
78+
"/v3/workspaces/{workspace_id}/peers/{peer_id}/card",
79+
{ params: { path: { workspace_id: workspaceId, peer_id: peerId } } },
80+
);
81+
return data ?? err(error);
82+
},
83+
enabled: Boolean(workspaceId) && Boolean(peerId),
84+
});
85+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import createClient from "openapi-fetch";
2+
import type { Instance } from "@/lib/config";
3+
import { httpFetch } from "@/lib/http";
4+
import type { paths } from "./schema.d.ts";
5+
6+
export type ScopedClient = ReturnType<typeof createClient<paths>>;
7+
8+
/**
9+
* Create an openapi-fetch client bound to a specific instance. Use for views
10+
* that need to query non-active instances (e.g. side-by-side comparison).
11+
* For single-instance access, prefer `client.current` which tracks the active
12+
* instance via localStorage.
13+
*/
14+
export function createScopedClient(instance: Instance): ScopedClient {
15+
const headers: Record<string, string> = { "Content-Type": "application/json" };
16+
if (instance.token) headers.Authorization = `Bearer ${instance.token}`;
17+
return createClient<paths>({ baseUrl: instance.baseUrl, headers, fetch: httpFetch });
18+
}

0 commit comments

Comments
 (0)