@sudobility/sider_client 0.0.2 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.d.ts +3 -2
- package/dist/core.js +3 -2
- package/dist/hooks.d.ts +22 -5
- package/dist/hooks.js +109 -19
- package/dist/index.d.ts +1 -3
- package/dist/index.js +2 -3
- package/dist/network/SiderClient.d.ts +31 -0
- package/dist/network/SiderClient.js +71 -0
- package/dist/network/SiderClient.test.d.ts +1 -0
- package/dist/network/SiderClient.test.js +49 -0
- package/dist/network/fetchClient.d.ts +9 -0
- package/dist/network/fetchClient.js +51 -0
- package/package.json +9 -6
- package/src/core.ts +4 -12
- package/src/hooks.ts +200 -20
- package/src/index.ts +2 -13
- package/src/network/SiderClient.test.ts +57 -0
- package/src/network/SiderClient.ts +109 -0
- package/src/network/fetchClient.ts +62 -0
- package/dist/client.d.ts +0 -17
- package/dist/client.js +0 -38
- package/dist/context.d.ts +0 -7
- package/dist/context.js +0 -13
- package/src/client.ts +0 -60
- package/src/context.tsx +0 -14
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { SiderClient } from "./network/SiderClient";
|
|
2
|
+
export { createFetchNetworkClient } from "./network/fetchClient";
|
|
3
|
+
export type * from "@sudobility/sider_types";
|
package/dist/core.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
// React-free entry
|
|
2
|
-
export
|
|
1
|
+
// React-free entry for service workers / non-React contexts.
|
|
2
|
+
export { SiderClient } from "./network/SiderClient";
|
|
3
|
+
export { createFetchNetworkClient } from "./network/fetchClient";
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
import { type UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
import type { NetworkClient } from "@sudobility/types";
|
|
1
3
|
import type { CaptureRequest } from "@sudobility/sider_types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
4
|
+
import { SiderClient } from "./network/SiderClient";
|
|
5
|
+
type QueryOpts<T> = Omit<UseQueryOptions<T>, "queryKey" | "queryFn">;
|
|
6
|
+
export declare function useSiteLookup(networkClient: NetworkClient, baseUrl: string, _token: string, origin: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["lookup"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").SiteLookupResult>, Error>;
|
|
7
|
+
export declare function useSites(networkClient: NetworkClient, baseUrl: string, _token: string, options?: QueryOpts<Awaited<ReturnType<SiderClient["listSites"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").Site[]>, Error>;
|
|
8
|
+
export declare function useSite(networkClient: NetworkClient, baseUrl: string, _token: string, siteId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getSite"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").Site>, Error>;
|
|
9
|
+
export declare function useStats(networkClient: NetworkClient, baseUrl: string, _token: string, options?: QueryOpts<Awaited<ReturnType<SiderClient["getStats"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").StatsResponse>, Error>;
|
|
10
|
+
export declare function useToolCatalog(networkClient: NetworkClient, baseUrl: string, _token: string, siteId: string | null, opts?: {
|
|
11
|
+
includeProvisional?: boolean;
|
|
12
|
+
includeFlagged?: boolean;
|
|
13
|
+
}, options?: QueryOpts<Awaited<ReturnType<SiderClient["getTools"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").ToolCatalog>, Error>;
|
|
14
|
+
export declare function useSiteTemplates(networkClient: NetworkClient, baseUrl: string, _token: string, siteId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteTemplates"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").EndpointTemplate[]>, Error>;
|
|
15
|
+
export declare function useSiteSlots(networkClient: NetworkClient, baseUrl: string, _token: string, siteId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteSlots"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").SecretSlot[]>, Error>;
|
|
16
|
+
export declare function useSiteGraph(networkClient: NetworkClient, baseUrl: string, _token: string, siteId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteGraph"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").EndpointGraphEdge[]>, Error>;
|
|
17
|
+
export declare function useToolDetail(networkClient: NetworkClient, baseUrl: string, _token: string, toolId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getToolDetail"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").ToolDetailResponse>, Error>;
|
|
18
|
+
export declare function useMe(networkClient: NetworkClient, baseUrl: string, token: string, options?: QueryOpts<Awaited<ReturnType<SiderClient["getMe"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").MeResponse>, Error>;
|
|
19
|
+
export declare function useMyCaptureBatches(networkClient: NetworkClient, baseUrl: string, token: string, siteId?: string, options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyCaptureBatches"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").MyCaptureBatchSummary[]>, Error>;
|
|
20
|
+
export declare function useMyCaptureBatch(networkClient: NetworkClient, baseUrl: string, token: string, batchId: string | null, options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyCaptureBatch"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").MyCaptureBatchDetail>, Error>;
|
|
21
|
+
export declare function useMyTools(networkClient: NetworkClient, baseUrl: string, token: string, options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyTools"]>>>): import("@tanstack/react-query").UseQueryResult<NoInfer<import("@sudobility/sider_types").MyToolEntry[]>, Error>;
|
|
22
|
+
export declare function useCaptureUpload(networkClient: NetworkClient, baseUrl: string, _token: string): import("@tanstack/react-query").UseMutationResult<import("@sudobility/sider_types").CaptureResponse, Error, CaptureRequest, unknown>;
|
|
23
|
+
export {};
|
package/dist/hooks.js
CHANGED
|
@@ -1,30 +1,120 @@
|
|
|
1
1
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { SiderClient } from "./network/SiderClient";
|
|
3
|
+
function client(networkClient, baseUrl) {
|
|
4
|
+
return new SiderClient(networkClient, baseUrl);
|
|
5
|
+
}
|
|
6
|
+
// --- public data: enabled regardless of token ------------------------------
|
|
7
|
+
export function useSiteLookup(networkClient, baseUrl, _token, origin, options) {
|
|
5
8
|
return useQuery({
|
|
6
9
|
queryKey: ["sider", "lookup", origin],
|
|
7
|
-
queryFn: () => client.lookup(origin),
|
|
8
|
-
|
|
10
|
+
queryFn: () => client(networkClient, baseUrl).lookup(origin),
|
|
11
|
+
...options,
|
|
12
|
+
enabled: !!origin && (options?.enabled ?? true),
|
|
9
13
|
});
|
|
10
14
|
}
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
export function useSites(networkClient, baseUrl, _token, options) {
|
|
16
|
+
return useQuery({
|
|
17
|
+
queryKey: ["sider", "sites"],
|
|
18
|
+
queryFn: () => client(networkClient, baseUrl).listSites(),
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function useSite(networkClient, baseUrl, _token, siteId, options) {
|
|
23
|
+
return useQuery({
|
|
24
|
+
queryKey: ["sider", "site", siteId],
|
|
25
|
+
queryFn: () => client(networkClient, baseUrl).getSite(siteId),
|
|
26
|
+
...options,
|
|
27
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function useStats(networkClient, baseUrl, _token, options) {
|
|
31
|
+
return useQuery({
|
|
32
|
+
queryKey: ["sider", "stats"],
|
|
33
|
+
queryFn: () => client(networkClient, baseUrl).getStats(),
|
|
34
|
+
...options,
|
|
35
|
+
});
|
|
14
36
|
}
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
export function useToolCatalog(networkClient, baseUrl, _token, siteId, opts, options) {
|
|
38
|
+
return useQuery({
|
|
39
|
+
queryKey: [
|
|
40
|
+
"sider",
|
|
41
|
+
"tools",
|
|
42
|
+
siteId,
|
|
43
|
+
opts?.includeProvisional ?? false,
|
|
44
|
+
opts?.includeFlagged ?? false,
|
|
45
|
+
],
|
|
46
|
+
queryFn: () => client(networkClient, baseUrl).getTools(siteId, opts),
|
|
47
|
+
...options,
|
|
48
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function useSiteTemplates(networkClient, baseUrl, _token, siteId, options) {
|
|
52
|
+
return useQuery({
|
|
53
|
+
queryKey: ["sider", "templates", siteId],
|
|
54
|
+
queryFn: () => client(networkClient, baseUrl).getSiteTemplates(siteId),
|
|
55
|
+
...options,
|
|
56
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function useSiteSlots(networkClient, baseUrl, _token, siteId, options) {
|
|
60
|
+
return useQuery({
|
|
61
|
+
queryKey: ["sider", "slots", siteId],
|
|
62
|
+
queryFn: () => client(networkClient, baseUrl).getSiteSlots(siteId),
|
|
63
|
+
...options,
|
|
64
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
65
|
+
});
|
|
18
66
|
}
|
|
19
|
-
export function
|
|
20
|
-
const client = useSiderClient();
|
|
67
|
+
export function useSiteGraph(networkClient, baseUrl, _token, siteId, options) {
|
|
21
68
|
return useQuery({
|
|
22
|
-
queryKey: ["sider", "
|
|
23
|
-
queryFn: () => client
|
|
24
|
-
|
|
69
|
+
queryKey: ["sider", "graph", siteId],
|
|
70
|
+
queryFn: () => client(networkClient, baseUrl).getSiteGraph(siteId),
|
|
71
|
+
...options,
|
|
72
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
25
73
|
});
|
|
26
74
|
}
|
|
27
|
-
export function
|
|
28
|
-
|
|
29
|
-
|
|
75
|
+
export function useToolDetail(networkClient, baseUrl, _token, toolId, options) {
|
|
76
|
+
return useQuery({
|
|
77
|
+
queryKey: ["sider", "tool", toolId],
|
|
78
|
+
queryFn: () => client(networkClient, baseUrl).getToolDetail(toolId),
|
|
79
|
+
...options,
|
|
80
|
+
enabled: !!toolId && (options?.enabled ?? true),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// --- per-user data: gated on the token -------------------------------------
|
|
84
|
+
export function useMe(networkClient, baseUrl, token, options) {
|
|
85
|
+
return useQuery({
|
|
86
|
+
queryKey: ["sider", "me", token ? "authed" : "anon"],
|
|
87
|
+
queryFn: () => client(networkClient, baseUrl).getMe(),
|
|
88
|
+
...options,
|
|
89
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export function useMyCaptureBatches(networkClient, baseUrl, token, siteId, options) {
|
|
93
|
+
return useQuery({
|
|
94
|
+
queryKey: ["sider", "my-batches", siteId ?? "all"],
|
|
95
|
+
queryFn: () => client(networkClient, baseUrl).getMyCaptureBatches(siteId),
|
|
96
|
+
...options,
|
|
97
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function useMyCaptureBatch(networkClient, baseUrl, token, batchId, options) {
|
|
101
|
+
return useQuery({
|
|
102
|
+
queryKey: ["sider", "my-batch", batchId],
|
|
103
|
+
queryFn: () => client(networkClient, baseUrl).getMyCaptureBatch(batchId),
|
|
104
|
+
...options,
|
|
105
|
+
enabled: !!token && !!batchId && (options?.enabled ?? true),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function useMyTools(networkClient, baseUrl, token, options) {
|
|
109
|
+
return useQuery({
|
|
110
|
+
queryKey: ["sider", "my-tools"],
|
|
111
|
+
queryFn: () => client(networkClient, baseUrl).getMyTools(),
|
|
112
|
+
...options,
|
|
113
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export function useCaptureUpload(networkClient, baseUrl, _token) {
|
|
117
|
+
return useMutation({
|
|
118
|
+
mutationFn: (body) => client(networkClient, baseUrl).uploadCapture(body),
|
|
119
|
+
});
|
|
30
120
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./context";
|
|
1
|
+
export * from "./core";
|
|
3
2
|
export * from "./hooks";
|
|
4
|
-
export type { CaptureRequest, CaptureResponse, PlannerStep, PlanStartResponse, PlanStepResponse, SiteLookupResult, StatsResponse, ToolCatalog, } from "@sudobility/sider_types";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @sudobility/sider_client —
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./context";
|
|
1
|
+
// @sudobility/sider_client — injected NetworkClient + React Query hooks for sider_api.
|
|
2
|
+
export * from "./core";
|
|
4
3
|
export * from "./hooks";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NetworkClient } from "@sudobility/types";
|
|
2
|
+
import type { CaptureRequest, CaptureResponse, EndpointGraphEdge, EndpointTemplate, MeResponse, MyCaptureBatchDetail, MyCaptureBatchSummary, MyToolEntry, SecretSlot, Site, SiteLookupResult, StatsResponse, ToolCatalog, ToolDetailResponse } from "@sudobility/sider_types";
|
|
3
|
+
/**
|
|
4
|
+
* Typed wrapper over the Sider API. Auth is the injected NetworkClient's
|
|
5
|
+
* concern (e.g. auth_lib's Firebase Bearer client); this class only builds
|
|
6
|
+
* URLs and unwraps the ApiResponse envelope.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SiderClient {
|
|
9
|
+
private readonly network;
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
constructor(network: NetworkClient, baseUrl: string);
|
|
12
|
+
private unwrap;
|
|
13
|
+
private get;
|
|
14
|
+
lookup(origin: string): Promise<SiteLookupResult>;
|
|
15
|
+
listSites(): Promise<Site[]>;
|
|
16
|
+
getSite(id: string): Promise<Site>;
|
|
17
|
+
getStats(): Promise<StatsResponse>;
|
|
18
|
+
getTools(siteId: string, opts?: {
|
|
19
|
+
includeProvisional?: boolean;
|
|
20
|
+
includeFlagged?: boolean;
|
|
21
|
+
}): Promise<ToolCatalog>;
|
|
22
|
+
uploadCapture(body: CaptureRequest): Promise<CaptureResponse>;
|
|
23
|
+
getMe(): Promise<MeResponse>;
|
|
24
|
+
getMyCaptureBatches(siteId?: string): Promise<MyCaptureBatchSummary[]>;
|
|
25
|
+
getMyCaptureBatch(id: string): Promise<MyCaptureBatchDetail>;
|
|
26
|
+
getMyTools(): Promise<MyToolEntry[]>;
|
|
27
|
+
getSiteTemplates(siteId: string): Promise<EndpointTemplate[]>;
|
|
28
|
+
getSiteSlots(siteId: string): Promise<SecretSlot[]>;
|
|
29
|
+
getSiteGraph(siteId: string): Promise<EndpointGraphEdge[]>;
|
|
30
|
+
getToolDetail(toolId: string): Promise<ToolDetailResponse>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrapper over the Sider API. Auth is the injected NetworkClient's
|
|
3
|
+
* concern (e.g. auth_lib's Firebase Bearer client); this class only builds
|
|
4
|
+
* URLs and unwraps the ApiResponse envelope.
|
|
5
|
+
*/
|
|
6
|
+
export class SiderClient {
|
|
7
|
+
network;
|
|
8
|
+
baseUrl;
|
|
9
|
+
constructor(network, baseUrl) {
|
|
10
|
+
this.network = network;
|
|
11
|
+
this.baseUrl = baseUrl;
|
|
12
|
+
}
|
|
13
|
+
unwrap(res) {
|
|
14
|
+
const body = res.data;
|
|
15
|
+
if (!body || body.success !== true) {
|
|
16
|
+
throw new Error(body?.error ?? `sider api error (${res.status})`);
|
|
17
|
+
}
|
|
18
|
+
return body.data;
|
|
19
|
+
}
|
|
20
|
+
async get(path) {
|
|
21
|
+
return this.unwrap(await this.network.get(`${this.baseUrl}${path}`));
|
|
22
|
+
}
|
|
23
|
+
lookup(origin) {
|
|
24
|
+
return this.get(`/api/v1/sites/lookup?origin=${encodeURIComponent(origin)}`);
|
|
25
|
+
}
|
|
26
|
+
listSites() {
|
|
27
|
+
return this.get("/api/v1/sites");
|
|
28
|
+
}
|
|
29
|
+
getSite(id) {
|
|
30
|
+
return this.get(`/api/v1/sites/${id}`);
|
|
31
|
+
}
|
|
32
|
+
getStats() {
|
|
33
|
+
return this.get("/api/v1/stats");
|
|
34
|
+
}
|
|
35
|
+
getTools(siteId, opts) {
|
|
36
|
+
const q = new URLSearchParams();
|
|
37
|
+
if (opts?.includeProvisional)
|
|
38
|
+
q.set("includeProvisional", "1");
|
|
39
|
+
if (opts?.includeFlagged)
|
|
40
|
+
q.set("includeFlagged", "1");
|
|
41
|
+
const qs = q.toString();
|
|
42
|
+
return this.get(`/api/v1/sites/${siteId}/tools${qs ? `?${qs}` : ""}`);
|
|
43
|
+
}
|
|
44
|
+
async uploadCapture(body) {
|
|
45
|
+
return this.unwrap(await this.network.post(`${this.baseUrl}/api/v1/capture`, body));
|
|
46
|
+
}
|
|
47
|
+
getMe() {
|
|
48
|
+
return this.get("/api/v1/me");
|
|
49
|
+
}
|
|
50
|
+
getMyCaptureBatches(siteId) {
|
|
51
|
+
return this.get(`/api/v1/me/capture-batches${siteId ? `?siteId=${encodeURIComponent(siteId)}` : ""}`);
|
|
52
|
+
}
|
|
53
|
+
getMyCaptureBatch(id) {
|
|
54
|
+
return this.get(`/api/v1/me/capture-batches/${id}`);
|
|
55
|
+
}
|
|
56
|
+
getMyTools() {
|
|
57
|
+
return this.get("/api/v1/me/tools");
|
|
58
|
+
}
|
|
59
|
+
getSiteTemplates(siteId) {
|
|
60
|
+
return this.get(`/api/v1/sites/${siteId}/templates`);
|
|
61
|
+
}
|
|
62
|
+
getSiteSlots(siteId) {
|
|
63
|
+
return this.get(`/api/v1/sites/${siteId}/slots`);
|
|
64
|
+
}
|
|
65
|
+
getSiteGraph(siteId) {
|
|
66
|
+
return this.get(`/api/v1/sites/${siteId}/graph`);
|
|
67
|
+
}
|
|
68
|
+
getToolDetail(toolId) {
|
|
69
|
+
return this.get(`/api/v1/tools/${toolId}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { SiderClient } from "./SiderClient";
|
|
3
|
+
function stubNetwork(data, ok = true) {
|
|
4
|
+
const calls = [];
|
|
5
|
+
const respond = (url) => {
|
|
6
|
+
calls.push(url);
|
|
7
|
+
return Promise.resolve({
|
|
8
|
+
ok,
|
|
9
|
+
status: ok ? 200 : 500,
|
|
10
|
+
statusText: ok ? "OK" : "ERR",
|
|
11
|
+
headers: {},
|
|
12
|
+
data: { success: ok, data, error: ok ? undefined : "boom" },
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
const client = {
|
|
16
|
+
request: (url) => respond(url),
|
|
17
|
+
get: (url) => respond(url),
|
|
18
|
+
post: (url) => respond(url),
|
|
19
|
+
put: (url) => respond(url),
|
|
20
|
+
delete: (url) => respond(url),
|
|
21
|
+
};
|
|
22
|
+
return { client, calls };
|
|
23
|
+
}
|
|
24
|
+
describe("SiderClient", () => {
|
|
25
|
+
test("builds URLs and unwraps the ApiResponse envelope", async () => {
|
|
26
|
+
const { client, calls } = stubNetwork([{ id: "s1" }]);
|
|
27
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
28
|
+
const sites = await sider.listSites();
|
|
29
|
+
expect(sites).toEqual([{ id: "s1" }]);
|
|
30
|
+
expect(calls[0]).toBe("http://localhost:8090/api/v1/sites");
|
|
31
|
+
});
|
|
32
|
+
test("encodes query params", async () => {
|
|
33
|
+
const { client, calls } = stubNetwork({ known: false });
|
|
34
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
35
|
+
await sider.lookup("https://a.example");
|
|
36
|
+
expect(calls[0]).toBe("http://localhost:8090/api/v1/sites/lookup?origin=https%3A%2F%2Fa.example");
|
|
37
|
+
});
|
|
38
|
+
test("getTools flags map to query params", async () => {
|
|
39
|
+
const { client, calls } = stubNetwork({ tools: [] });
|
|
40
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
41
|
+
await sider.getTools("s1", { includeProvisional: true });
|
|
42
|
+
expect(calls[0]).toBe("http://localhost:8090/api/v1/sites/s1/tools?includeProvisional=1");
|
|
43
|
+
});
|
|
44
|
+
test("throws the envelope error on failure", async () => {
|
|
45
|
+
const { client } = stubNetwork(null, false);
|
|
46
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
47
|
+
await expect(sider.getStats()).rejects.toThrow("boom");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NetworkClient } from "@sudobility/types";
|
|
2
|
+
type HeaderSource = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
3
|
+
/**
|
|
4
|
+
* Plain fetch-backed NetworkClient for non-React contexts (service worker,
|
|
5
|
+
* tests, Node scripts). `getHeaders` is merged into every request — e.g.
|
|
6
|
+
* `async () => ({ authorization: `Bearer ${token}` })`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createFetchNetworkClient(getHeaders?: HeaderSource): NetworkClient;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain fetch-backed NetworkClient for non-React contexts (service worker,
|
|
3
|
+
* tests, Node scripts). `getHeaders` is merged into every request — e.g.
|
|
4
|
+
* `async () => ({ authorization: `Bearer ${token}` })`.
|
|
5
|
+
*/
|
|
6
|
+
export function createFetchNetworkClient(getHeaders) {
|
|
7
|
+
async function request(url, options) {
|
|
8
|
+
const extra = typeof getHeaders === "function" ? await getHeaders() : (getHeaders ?? {});
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
method: options?.method ?? "GET",
|
|
11
|
+
headers: { "content-type": "application/json", ...extra, ...options?.headers },
|
|
12
|
+
body: options?.body ?? undefined,
|
|
13
|
+
signal: options?.signal ?? undefined,
|
|
14
|
+
});
|
|
15
|
+
const headers = {};
|
|
16
|
+
res.headers.forEach((v, k) => {
|
|
17
|
+
headers[k] = v;
|
|
18
|
+
});
|
|
19
|
+
let data;
|
|
20
|
+
try {
|
|
21
|
+
data = (await res.json());
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
data = undefined;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
success: res.ok,
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
ok: res.ok,
|
|
30
|
+
status: res.status,
|
|
31
|
+
statusText: res.statusText,
|
|
32
|
+
headers,
|
|
33
|
+
data: data,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
request,
|
|
38
|
+
get: (url, o) => request(url, { ...o, method: "GET" }),
|
|
39
|
+
post: (url, body, o) => request(url, {
|
|
40
|
+
...o,
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
43
|
+
}),
|
|
44
|
+
put: (url, body, o) => request(url, {
|
|
45
|
+
...o,
|
|
46
|
+
method: "PUT",
|
|
47
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
48
|
+
}),
|
|
49
|
+
delete: (url, o) => request(url, { ...o, method: "DELETE" }),
|
|
50
|
+
};
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/sider_client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sider frontend network library —
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Sider frontend network library — injected NetworkClient + React Query hooks.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "restricted"
|
|
@@ -25,17 +25,20 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
|
-
"typecheck": "tsc --noEmit"
|
|
29
|
-
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"@sudobility/sider_types": "^0.0.2"
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "bun test"
|
|
32
30
|
},
|
|
33
31
|
"peerDependencies": {
|
|
32
|
+
"@sudobility/sider_types": "^0.0.5",
|
|
33
|
+
"@sudobility/types": "^1.9.62",
|
|
34
34
|
"@tanstack/react-query": "^5",
|
|
35
35
|
"react": "^18 || ^19"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@sudobility/sider_types": "^0.0.5",
|
|
39
|
+
"@sudobility/types": "^1.9.62",
|
|
38
40
|
"@tanstack/react-query": "^5.62.7",
|
|
41
|
+
"@types/bun": "^1.2.23",
|
|
39
42
|
"@types/react": "^18.3.12",
|
|
40
43
|
"react": "^18.3.1",
|
|
41
44
|
"typescript": "^5.9.3"
|
package/src/core.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
// React-free entry
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
CaptureResponse,
|
|
6
|
-
PlannerStep,
|
|
7
|
-
PlanStartResponse,
|
|
8
|
-
PlanStepResponse,
|
|
9
|
-
SiteLookupResult,
|
|
10
|
-
StatsResponse,
|
|
11
|
-
ToolCatalog,
|
|
12
|
-
} from "@sudobility/sider_types";
|
|
1
|
+
// React-free entry for service workers / non-React contexts.
|
|
2
|
+
export { SiderClient } from "./network/SiderClient";
|
|
3
|
+
export { createFetchNetworkClient } from "./network/fetchClient";
|
|
4
|
+
export type * from "@sudobility/sider_types";
|
package/src/hooks.ts
CHANGED
|
@@ -1,36 +1,216 @@
|
|
|
1
|
-
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
1
|
+
import { useMutation, useQuery, type UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
import type { NetworkClient } from "@sudobility/types";
|
|
2
3
|
import type { CaptureRequest } from "@sudobility/sider_types";
|
|
3
|
-
import {
|
|
4
|
+
import { SiderClient } from "./network/SiderClient";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
type QueryOpts<T> = Omit<UseQueryOptions<T>, "queryKey" | "queryFn">;
|
|
7
|
+
|
|
8
|
+
function client(networkClient: NetworkClient, baseUrl: string): SiderClient {
|
|
9
|
+
return new SiderClient(networkClient, baseUrl);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// --- public data: enabled regardless of token ------------------------------
|
|
13
|
+
|
|
14
|
+
export function useSiteLookup(
|
|
15
|
+
networkClient: NetworkClient,
|
|
16
|
+
baseUrl: string,
|
|
17
|
+
_token: string,
|
|
18
|
+
origin: string | null,
|
|
19
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["lookup"]>>>,
|
|
20
|
+
) {
|
|
7
21
|
return useQuery({
|
|
8
22
|
queryKey: ["sider", "lookup", origin],
|
|
9
|
-
queryFn: () => client.lookup(origin),
|
|
10
|
-
|
|
23
|
+
queryFn: () => client(networkClient, baseUrl).lookup(origin as string),
|
|
24
|
+
...options,
|
|
25
|
+
enabled: !!origin && (options?.enabled ?? true),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useSites(
|
|
30
|
+
networkClient: NetworkClient,
|
|
31
|
+
baseUrl: string,
|
|
32
|
+
_token: string,
|
|
33
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["listSites"]>>>,
|
|
34
|
+
) {
|
|
35
|
+
return useQuery({
|
|
36
|
+
queryKey: ["sider", "sites"],
|
|
37
|
+
queryFn: () => client(networkClient, baseUrl).listSites(),
|
|
38
|
+
...options,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useSite(
|
|
43
|
+
networkClient: NetworkClient,
|
|
44
|
+
baseUrl: string,
|
|
45
|
+
_token: string,
|
|
46
|
+
siteId: string | null,
|
|
47
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getSite"]>>>,
|
|
48
|
+
) {
|
|
49
|
+
return useQuery({
|
|
50
|
+
queryKey: ["sider", "site", siteId],
|
|
51
|
+
queryFn: () => client(networkClient, baseUrl).getSite(siteId as string),
|
|
52
|
+
...options,
|
|
53
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useStats(
|
|
58
|
+
networkClient: NetworkClient,
|
|
59
|
+
baseUrl: string,
|
|
60
|
+
_token: string,
|
|
61
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getStats"]>>>,
|
|
62
|
+
) {
|
|
63
|
+
return useQuery({
|
|
64
|
+
queryKey: ["sider", "stats"],
|
|
65
|
+
queryFn: () => client(networkClient, baseUrl).getStats(),
|
|
66
|
+
...options,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useToolCatalog(
|
|
71
|
+
networkClient: NetworkClient,
|
|
72
|
+
baseUrl: string,
|
|
73
|
+
_token: string,
|
|
74
|
+
siteId: string | null,
|
|
75
|
+
opts?: { includeProvisional?: boolean; includeFlagged?: boolean },
|
|
76
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getTools"]>>>,
|
|
77
|
+
) {
|
|
78
|
+
return useQuery({
|
|
79
|
+
queryKey: [
|
|
80
|
+
"sider",
|
|
81
|
+
"tools",
|
|
82
|
+
siteId,
|
|
83
|
+
opts?.includeProvisional ?? false,
|
|
84
|
+
opts?.includeFlagged ?? false,
|
|
85
|
+
],
|
|
86
|
+
queryFn: () => client(networkClient, baseUrl).getTools(siteId as string, opts),
|
|
87
|
+
...options,
|
|
88
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function useSiteTemplates(
|
|
93
|
+
networkClient: NetworkClient,
|
|
94
|
+
baseUrl: string,
|
|
95
|
+
_token: string,
|
|
96
|
+
siteId: string | null,
|
|
97
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteTemplates"]>>>,
|
|
98
|
+
) {
|
|
99
|
+
return useQuery({
|
|
100
|
+
queryKey: ["sider", "templates", siteId],
|
|
101
|
+
queryFn: () => client(networkClient, baseUrl).getSiteTemplates(siteId as string),
|
|
102
|
+
...options,
|
|
103
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useSiteSlots(
|
|
108
|
+
networkClient: NetworkClient,
|
|
109
|
+
baseUrl: string,
|
|
110
|
+
_token: string,
|
|
111
|
+
siteId: string | null,
|
|
112
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteSlots"]>>>,
|
|
113
|
+
) {
|
|
114
|
+
return useQuery({
|
|
115
|
+
queryKey: ["sider", "slots", siteId],
|
|
116
|
+
queryFn: () => client(networkClient, baseUrl).getSiteSlots(siteId as string),
|
|
117
|
+
...options,
|
|
118
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
11
119
|
});
|
|
12
120
|
}
|
|
13
121
|
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
|
|
122
|
+
export function useSiteGraph(
|
|
123
|
+
networkClient: NetworkClient,
|
|
124
|
+
baseUrl: string,
|
|
125
|
+
_token: string,
|
|
126
|
+
siteId: string | null,
|
|
127
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getSiteGraph"]>>>,
|
|
128
|
+
) {
|
|
129
|
+
return useQuery({
|
|
130
|
+
queryKey: ["sider", "graph", siteId],
|
|
131
|
+
queryFn: () => client(networkClient, baseUrl).getSiteGraph(siteId as string),
|
|
132
|
+
...options,
|
|
133
|
+
enabled: !!siteId && (options?.enabled ?? true),
|
|
134
|
+
});
|
|
17
135
|
}
|
|
18
136
|
|
|
19
|
-
export function
|
|
20
|
-
|
|
21
|
-
|
|
137
|
+
export function useToolDetail(
|
|
138
|
+
networkClient: NetworkClient,
|
|
139
|
+
baseUrl: string,
|
|
140
|
+
_token: string,
|
|
141
|
+
toolId: string | null,
|
|
142
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getToolDetail"]>>>,
|
|
143
|
+
) {
|
|
144
|
+
return useQuery({
|
|
145
|
+
queryKey: ["sider", "tool", toolId],
|
|
146
|
+
queryFn: () => client(networkClient, baseUrl).getToolDetail(toolId as string),
|
|
147
|
+
...options,
|
|
148
|
+
enabled: !!toolId && (options?.enabled ?? true),
|
|
149
|
+
});
|
|
22
150
|
}
|
|
23
151
|
|
|
24
|
-
|
|
25
|
-
|
|
152
|
+
// --- per-user data: gated on the token -------------------------------------
|
|
153
|
+
|
|
154
|
+
export function useMe(
|
|
155
|
+
networkClient: NetworkClient,
|
|
156
|
+
baseUrl: string,
|
|
157
|
+
token: string,
|
|
158
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getMe"]>>>,
|
|
159
|
+
) {
|
|
26
160
|
return useQuery({
|
|
27
|
-
queryKey: ["sider", "
|
|
28
|
-
queryFn: () => client
|
|
29
|
-
|
|
161
|
+
queryKey: ["sider", "me", token ? "authed" : "anon"],
|
|
162
|
+
queryFn: () => client(networkClient, baseUrl).getMe(),
|
|
163
|
+
...options,
|
|
164
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
30
165
|
});
|
|
31
166
|
}
|
|
32
167
|
|
|
33
|
-
export function
|
|
34
|
-
|
|
35
|
-
|
|
168
|
+
export function useMyCaptureBatches(
|
|
169
|
+
networkClient: NetworkClient,
|
|
170
|
+
baseUrl: string,
|
|
171
|
+
token: string,
|
|
172
|
+
siteId?: string,
|
|
173
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyCaptureBatches"]>>>,
|
|
174
|
+
) {
|
|
175
|
+
return useQuery({
|
|
176
|
+
queryKey: ["sider", "my-batches", siteId ?? "all"],
|
|
177
|
+
queryFn: () => client(networkClient, baseUrl).getMyCaptureBatches(siteId),
|
|
178
|
+
...options,
|
|
179
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function useMyCaptureBatch(
|
|
184
|
+
networkClient: NetworkClient,
|
|
185
|
+
baseUrl: string,
|
|
186
|
+
token: string,
|
|
187
|
+
batchId: string | null,
|
|
188
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyCaptureBatch"]>>>,
|
|
189
|
+
) {
|
|
190
|
+
return useQuery({
|
|
191
|
+
queryKey: ["sider", "my-batch", batchId],
|
|
192
|
+
queryFn: () => client(networkClient, baseUrl).getMyCaptureBatch(batchId as string),
|
|
193
|
+
...options,
|
|
194
|
+
enabled: !!token && !!batchId && (options?.enabled ?? true),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function useMyTools(
|
|
199
|
+
networkClient: NetworkClient,
|
|
200
|
+
baseUrl: string,
|
|
201
|
+
token: string,
|
|
202
|
+
options?: QueryOpts<Awaited<ReturnType<SiderClient["getMyTools"]>>>,
|
|
203
|
+
) {
|
|
204
|
+
return useQuery({
|
|
205
|
+
queryKey: ["sider", "my-tools"],
|
|
206
|
+
queryFn: () => client(networkClient, baseUrl).getMyTools(),
|
|
207
|
+
...options,
|
|
208
|
+
enabled: !!token && (options?.enabled ?? true),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function useCaptureUpload(networkClient: NetworkClient, baseUrl: string, _token: string) {
|
|
213
|
+
return useMutation({
|
|
214
|
+
mutationFn: (body: CaptureRequest) => client(networkClient, baseUrl).uploadCapture(body),
|
|
215
|
+
});
|
|
36
216
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
// @sudobility/sider_client —
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./context";
|
|
1
|
+
// @sudobility/sider_client — injected NetworkClient + React Query hooks for sider_api.
|
|
2
|
+
export * from "./core";
|
|
4
3
|
export * from "./hooks";
|
|
5
|
-
export type {
|
|
6
|
-
CaptureRequest,
|
|
7
|
-
CaptureResponse,
|
|
8
|
-
PlannerStep,
|
|
9
|
-
PlanStartResponse,
|
|
10
|
-
PlanStepResponse,
|
|
11
|
-
SiteLookupResult,
|
|
12
|
-
StatsResponse,
|
|
13
|
-
ToolCatalog,
|
|
14
|
-
} from "@sudobility/sider_types";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { NetworkClient, NetworkResponse } from "@sudobility/types";
|
|
3
|
+
import { SiderClient } from "./SiderClient";
|
|
4
|
+
|
|
5
|
+
function stubNetwork(data: unknown, ok = true): { client: NetworkClient; calls: string[] } {
|
|
6
|
+
const calls: string[] = [];
|
|
7
|
+
const respond = <T>(url: string): Promise<NetworkResponse<T>> => {
|
|
8
|
+
calls.push(url);
|
|
9
|
+
return Promise.resolve({
|
|
10
|
+
ok,
|
|
11
|
+
status: ok ? 200 : 500,
|
|
12
|
+
statusText: ok ? "OK" : "ERR",
|
|
13
|
+
headers: {},
|
|
14
|
+
data: { success: ok, data, error: ok ? undefined : "boom" } as T,
|
|
15
|
+
} as NetworkResponse<T>);
|
|
16
|
+
};
|
|
17
|
+
const client: NetworkClient = {
|
|
18
|
+
request: (url) => respond(url),
|
|
19
|
+
get: (url) => respond(url),
|
|
20
|
+
post: (url) => respond(url),
|
|
21
|
+
put: (url) => respond(url),
|
|
22
|
+
delete: (url) => respond(url),
|
|
23
|
+
};
|
|
24
|
+
return { client, calls };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("SiderClient", () => {
|
|
28
|
+
test("builds URLs and unwraps the ApiResponse envelope", async () => {
|
|
29
|
+
const { client, calls } = stubNetwork([{ id: "s1" }]);
|
|
30
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
31
|
+
const sites = await sider.listSites();
|
|
32
|
+
expect(sites as unknown).toEqual([{ id: "s1" }]);
|
|
33
|
+
expect(calls[0]).toBe("http://localhost:8090/api/v1/sites");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("encodes query params", async () => {
|
|
37
|
+
const { client, calls } = stubNetwork({ known: false });
|
|
38
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
39
|
+
await sider.lookup("https://a.example");
|
|
40
|
+
expect(calls[0]).toBe(
|
|
41
|
+
"http://localhost:8090/api/v1/sites/lookup?origin=https%3A%2F%2Fa.example",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("getTools flags map to query params", async () => {
|
|
46
|
+
const { client, calls } = stubNetwork({ tools: [] });
|
|
47
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
48
|
+
await sider.getTools("s1", { includeProvisional: true });
|
|
49
|
+
expect(calls[0]).toBe("http://localhost:8090/api/v1/sites/s1/tools?includeProvisional=1");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("throws the envelope error on failure", async () => {
|
|
53
|
+
const { client } = stubNetwork(null, false);
|
|
54
|
+
const sider = new SiderClient(client, "http://localhost:8090");
|
|
55
|
+
await expect(sider.getStats()).rejects.toThrow("boom");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { NetworkClient, NetworkResponse } from "@sudobility/types";
|
|
2
|
+
import type {
|
|
3
|
+
ApiResponse,
|
|
4
|
+
CaptureRequest,
|
|
5
|
+
CaptureResponse,
|
|
6
|
+
EndpointGraphEdge,
|
|
7
|
+
EndpointTemplate,
|
|
8
|
+
MeResponse,
|
|
9
|
+
MyCaptureBatchDetail,
|
|
10
|
+
MyCaptureBatchSummary,
|
|
11
|
+
MyToolEntry,
|
|
12
|
+
SecretSlot,
|
|
13
|
+
Site,
|
|
14
|
+
SiteLookupResult,
|
|
15
|
+
StatsResponse,
|
|
16
|
+
ToolCatalog,
|
|
17
|
+
ToolDetailResponse,
|
|
18
|
+
} from "@sudobility/sider_types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Typed wrapper over the Sider API. Auth is the injected NetworkClient's
|
|
22
|
+
* concern (e.g. auth_lib's Firebase Bearer client); this class only builds
|
|
23
|
+
* URLs and unwraps the ApiResponse envelope.
|
|
24
|
+
*/
|
|
25
|
+
export class SiderClient {
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly network: NetworkClient,
|
|
28
|
+
private readonly baseUrl: string,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
private unwrap<T>(res: NetworkResponse<ApiResponse<T>>): T {
|
|
32
|
+
const body = res.data;
|
|
33
|
+
if (!body || body.success !== true) {
|
|
34
|
+
throw new Error(body?.error ?? `sider api error (${res.status})`);
|
|
35
|
+
}
|
|
36
|
+
return body.data as T;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private async get<T>(path: string): Promise<T> {
|
|
40
|
+
return this.unwrap(await this.network.get<ApiResponse<T>>(`${this.baseUrl}${path}`));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
lookup(origin: string): Promise<SiteLookupResult> {
|
|
44
|
+
return this.get(`/api/v1/sites/lookup?origin=${encodeURIComponent(origin)}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
listSites(): Promise<Site[]> {
|
|
48
|
+
return this.get("/api/v1/sites");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getSite(id: string): Promise<Site> {
|
|
52
|
+
return this.get(`/api/v1/sites/${id}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getStats(): Promise<StatsResponse> {
|
|
56
|
+
return this.get("/api/v1/stats");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getTools(
|
|
60
|
+
siteId: string,
|
|
61
|
+
opts?: { includeProvisional?: boolean; includeFlagged?: boolean },
|
|
62
|
+
): Promise<ToolCatalog> {
|
|
63
|
+
const q = new URLSearchParams();
|
|
64
|
+
if (opts?.includeProvisional) q.set("includeProvisional", "1");
|
|
65
|
+
if (opts?.includeFlagged) q.set("includeFlagged", "1");
|
|
66
|
+
const qs = q.toString();
|
|
67
|
+
return this.get(`/api/v1/sites/${siteId}/tools${qs ? `?${qs}` : ""}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async uploadCapture(body: CaptureRequest): Promise<CaptureResponse> {
|
|
71
|
+
return this.unwrap(
|
|
72
|
+
await this.network.post<ApiResponse<CaptureResponse>>(`${this.baseUrl}/api/v1/capture`, body),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getMe(): Promise<MeResponse> {
|
|
77
|
+
return this.get("/api/v1/me");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getMyCaptureBatches(siteId?: string): Promise<MyCaptureBatchSummary[]> {
|
|
81
|
+
return this.get(
|
|
82
|
+
`/api/v1/me/capture-batches${siteId ? `?siteId=${encodeURIComponent(siteId)}` : ""}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getMyCaptureBatch(id: string): Promise<MyCaptureBatchDetail> {
|
|
87
|
+
return this.get(`/api/v1/me/capture-batches/${id}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getMyTools(): Promise<MyToolEntry[]> {
|
|
91
|
+
return this.get("/api/v1/me/tools");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getSiteTemplates(siteId: string): Promise<EndpointTemplate[]> {
|
|
95
|
+
return this.get(`/api/v1/sites/${siteId}/templates`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getSiteSlots(siteId: string): Promise<SecretSlot[]> {
|
|
99
|
+
return this.get(`/api/v1/sites/${siteId}/slots`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getSiteGraph(siteId: string): Promise<EndpointGraphEdge[]> {
|
|
103
|
+
return this.get(`/api/v1/sites/${siteId}/graph`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getToolDetail(toolId: string): Promise<ToolDetailResponse> {
|
|
107
|
+
return this.get(`/api/v1/tools/${toolId}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NetworkClient, NetworkRequestOptions, NetworkResponse } from "@sudobility/types";
|
|
2
|
+
|
|
3
|
+
type HeaderSource =
|
|
4
|
+
| Record<string, string>
|
|
5
|
+
| (() => Record<string, string> | Promise<Record<string, string>>);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Plain fetch-backed NetworkClient for non-React contexts (service worker,
|
|
9
|
+
* tests, Node scripts). `getHeaders` is merged into every request — e.g.
|
|
10
|
+
* `async () => ({ authorization: `Bearer ${token}` })`.
|
|
11
|
+
*/
|
|
12
|
+
export function createFetchNetworkClient(getHeaders?: HeaderSource): NetworkClient {
|
|
13
|
+
async function request<T>(
|
|
14
|
+
url: string,
|
|
15
|
+
options?: NetworkRequestOptions | null,
|
|
16
|
+
): Promise<NetworkResponse<T>> {
|
|
17
|
+
const extra = typeof getHeaders === "function" ? await getHeaders() : (getHeaders ?? {});
|
|
18
|
+
const res = await fetch(url, {
|
|
19
|
+
method: options?.method ?? "GET",
|
|
20
|
+
headers: { "content-type": "application/json", ...extra, ...options?.headers },
|
|
21
|
+
body: options?.body ?? undefined,
|
|
22
|
+
signal: options?.signal ?? undefined,
|
|
23
|
+
});
|
|
24
|
+
const headers: Record<string, string> = {};
|
|
25
|
+
res.headers.forEach((v, k) => {
|
|
26
|
+
headers[k] = v;
|
|
27
|
+
});
|
|
28
|
+
let data: T | undefined;
|
|
29
|
+
try {
|
|
30
|
+
data = (await res.json()) as T;
|
|
31
|
+
} catch {
|
|
32
|
+
data = undefined;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
success: res.ok,
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
ok: res.ok,
|
|
38
|
+
status: res.status,
|
|
39
|
+
statusText: res.statusText,
|
|
40
|
+
headers,
|
|
41
|
+
data: data as T,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
request,
|
|
47
|
+
get: (url, o) => request(url, { ...o, method: "GET" }),
|
|
48
|
+
post: (url, body, o) =>
|
|
49
|
+
request(url, {
|
|
50
|
+
...o,
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
53
|
+
}),
|
|
54
|
+
put: (url, body, o) =>
|
|
55
|
+
request(url, {
|
|
56
|
+
...o,
|
|
57
|
+
method: "PUT",
|
|
58
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
59
|
+
}),
|
|
60
|
+
delete: (url, o) => request(url, { ...o, method: "DELETE" }),
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { CaptureRequest, CaptureResponse, Site, SiteLookupResult, StatsResponse, ToolCatalog } from "@sudobility/sider_types";
|
|
2
|
-
export interface SiderClientConfig {
|
|
3
|
-
baseUrl: string;
|
|
4
|
-
/** Supplies auth headers (dev uid now, Firebase bearer later). */
|
|
5
|
-
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
6
|
-
}
|
|
7
|
-
export declare class SiderClient {
|
|
8
|
-
private readonly config;
|
|
9
|
-
constructor(config: SiderClientConfig);
|
|
10
|
-
private req;
|
|
11
|
-
lookup(origin: string): Promise<SiteLookupResult>;
|
|
12
|
-
getSite(id: string): Promise<Site>;
|
|
13
|
-
listSites(): Promise<Site[]>;
|
|
14
|
-
getStats(): Promise<StatsResponse>;
|
|
15
|
-
getTools(siteId: string, includeProvisional?: boolean): Promise<ToolCatalog>;
|
|
16
|
-
uploadCapture(body: CaptureRequest): Promise<CaptureResponse>;
|
|
17
|
-
}
|
package/dist/client.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// SiderClient — the typed transport over sider_api. Usable in any JS context
|
|
2
|
-
// (React or a service worker); the React hooks in ./hooks wrap it. It unwraps
|
|
3
|
-
// the ApiResponse envelope and throws on error.
|
|
4
|
-
export class SiderClient {
|
|
5
|
-
config;
|
|
6
|
-
constructor(config) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
}
|
|
9
|
-
async req(path, init) {
|
|
10
|
-
const auth = (await this.config.getAuthHeaders?.()) ?? {};
|
|
11
|
-
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
12
|
-
...init,
|
|
13
|
-
headers: { "content-type": "application/json", ...auth, ...(init?.headers ?? {}) },
|
|
14
|
-
});
|
|
15
|
-
const json = (await res.json());
|
|
16
|
-
if (!json.success)
|
|
17
|
-
throw new Error(json.error);
|
|
18
|
-
return json.data;
|
|
19
|
-
}
|
|
20
|
-
lookup(origin) {
|
|
21
|
-
return this.req(`/api/v1/sites/lookup?origin=${encodeURIComponent(origin)}`);
|
|
22
|
-
}
|
|
23
|
-
getSite(id) {
|
|
24
|
-
return this.req(`/api/v1/sites/${id}`);
|
|
25
|
-
}
|
|
26
|
-
listSites() {
|
|
27
|
-
return this.req(`/api/v1/sites`);
|
|
28
|
-
}
|
|
29
|
-
getStats() {
|
|
30
|
-
return this.req(`/api/v1/stats`);
|
|
31
|
-
}
|
|
32
|
-
getTools(siteId, includeProvisional = false) {
|
|
33
|
-
return this.req(`/api/v1/sites/${siteId}/tools${includeProvisional ? "?includeProvisional=1" : ""}`);
|
|
34
|
-
}
|
|
35
|
-
uploadCapture(body) {
|
|
36
|
-
return this.req(`/api/v1/capture`, { method: "POST", body: JSON.stringify(body) });
|
|
37
|
-
}
|
|
38
|
-
}
|
package/dist/context.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import { SiderClient } from "./client";
|
|
3
|
-
export declare function SiderClientProvider({ client, children }: {
|
|
4
|
-
client: SiderClient;
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
}): import("react").JSX.Element;
|
|
7
|
-
export declare function useSiderClient(): SiderClient;
|
package/dist/context.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
3
|
-
import { SiderClient } from "./client";
|
|
4
|
-
const SiderClientContext = createContext(null);
|
|
5
|
-
export function SiderClientProvider({ client, children }) {
|
|
6
|
-
return _jsx(SiderClientContext.Provider, { value: client, children: children });
|
|
7
|
-
}
|
|
8
|
-
export function useSiderClient() {
|
|
9
|
-
const c = useContext(SiderClientContext);
|
|
10
|
-
if (!c)
|
|
11
|
-
throw new Error("useSiderClient must be used within a <SiderClientProvider>");
|
|
12
|
-
return c;
|
|
13
|
-
}
|
package/src/client.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// SiderClient — the typed transport over sider_api. Usable in any JS context
|
|
2
|
-
// (React or a service worker); the React hooks in ./hooks wrap it. It unwraps
|
|
3
|
-
// the ApiResponse envelope and throws on error.
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
ApiResponse,
|
|
7
|
-
CaptureRequest,
|
|
8
|
-
CaptureResponse,
|
|
9
|
-
Site,
|
|
10
|
-
SiteLookupResult,
|
|
11
|
-
StatsResponse,
|
|
12
|
-
ToolCatalog,
|
|
13
|
-
} from "@sudobility/sider_types";
|
|
14
|
-
|
|
15
|
-
export interface SiderClientConfig {
|
|
16
|
-
baseUrl: string;
|
|
17
|
-
/** Supplies auth headers (dev uid now, Firebase bearer later). */
|
|
18
|
-
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class SiderClient {
|
|
22
|
-
constructor(private readonly config: SiderClientConfig) {}
|
|
23
|
-
|
|
24
|
-
private async req<T>(path: string, init?: RequestInit): Promise<T> {
|
|
25
|
-
const auth = (await this.config.getAuthHeaders?.()) ?? {};
|
|
26
|
-
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
27
|
-
...init,
|
|
28
|
-
headers: { "content-type": "application/json", ...auth, ...(init?.headers ?? {}) },
|
|
29
|
-
});
|
|
30
|
-
const json = (await res.json()) as ApiResponse<T>;
|
|
31
|
-
if (!json.success) throw new Error(json.error);
|
|
32
|
-
return json.data;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
lookup(origin: string) {
|
|
36
|
-
return this.req<SiteLookupResult>(`/api/v1/sites/lookup?origin=${encodeURIComponent(origin)}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getSite(id: string) {
|
|
40
|
-
return this.req<Site>(`/api/v1/sites/${id}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
listSites() {
|
|
44
|
-
return this.req<Site[]>(`/api/v1/sites`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getStats() {
|
|
48
|
-
return this.req<StatsResponse>(`/api/v1/stats`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getTools(siteId: string, includeProvisional = false) {
|
|
52
|
-
return this.req<ToolCatalog>(
|
|
53
|
-
`/api/v1/sites/${siteId}/tools${includeProvisional ? "?includeProvisional=1" : ""}`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
uploadCapture(body: CaptureRequest) {
|
|
58
|
-
return this.req<CaptureResponse>(`/api/v1/capture`, { method: "POST", body: JSON.stringify(body) });
|
|
59
|
-
}
|
|
60
|
-
}
|
package/src/context.tsx
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
-
import { SiderClient } from "./client";
|
|
3
|
-
|
|
4
|
-
const SiderClientContext = createContext<SiderClient | null>(null);
|
|
5
|
-
|
|
6
|
-
export function SiderClientProvider({ client, children }: { client: SiderClient; children: ReactNode }) {
|
|
7
|
-
return <SiderClientContext.Provider value={client}>{children}</SiderClientContext.Provider>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function useSiderClient(): SiderClient {
|
|
11
|
-
const c = useContext(SiderClientContext);
|
|
12
|
-
if (!c) throw new Error("useSiderClient must be used within a <SiderClientProvider>");
|
|
13
|
-
return c;
|
|
14
|
-
}
|