@schlessera/brain-ui-server 0.6.2 → 0.7.0
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/app.d.ts.map +1 -1
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -1
- package/dist/graph/reader.d.ts +71 -0
- package/dist/graph/reader.d.ts.map +1 -0
- package/dist/graph/reader.js +522 -0
- package/dist/graph/reader.js.map +1 -0
- package/dist/routes/graph.d.ts +256 -0
- package/dist/routes/graph.d.ts.map +1 -0
- package/dist/routes/graph.js +100 -0
- package/dist/routes/graph.js.map +1 -0
- package/package.json +3 -3
- package/src/app.ts +2 -0
- package/src/graph/reader.ts +667 -0
- package/src/routes/graph.ts +112 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_STALE_DAYS,
|
|
4
|
+
GraphNotFoundError,
|
|
5
|
+
GraphUnavailableError,
|
|
6
|
+
MAX_DISCOVERY_DEPTH,
|
|
7
|
+
MAX_NEIGHBORHOOD_DEPTH,
|
|
8
|
+
getClusters,
|
|
9
|
+
getDiscovery,
|
|
10
|
+
getGraphMeta,
|
|
11
|
+
getMaintenance,
|
|
12
|
+
getNeighborhood,
|
|
13
|
+
} from "../graph/reader.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `/graph/meta` always answers 200 — an unavailable graph is a described state,
|
|
17
|
+
* not an error, and the client needs `reason` to pick its empty state. The
|
|
18
|
+
* subgraph endpoints instead refuse with 503 rather than returning an empty
|
|
19
|
+
* graph that would read as "your repo has no links".
|
|
20
|
+
*/
|
|
21
|
+
function errorResponse(err: unknown): {
|
|
22
|
+
body: { error: string; reason?: string; param?: string };
|
|
23
|
+
status: 400 | 404 | 503 | 500;
|
|
24
|
+
} {
|
|
25
|
+
if (err instanceof GraphUnavailableError) {
|
|
26
|
+
return { body: { error: "graph_unavailable", reason: err.reason }, status: 503 };
|
|
27
|
+
}
|
|
28
|
+
if (err instanceof GraphNotFoundError) {
|
|
29
|
+
return { body: { error: "not_found" }, status: 404 };
|
|
30
|
+
}
|
|
31
|
+
console.error("[graph]", err);
|
|
32
|
+
return { body: { error: err instanceof Error ? err.message : "internal_error" }, status: 500 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Returns null when the parameter is present but not an integer in range. */
|
|
36
|
+
function intParam(raw: string | undefined, min: number, max: number, fallback: number): number | null {
|
|
37
|
+
if (raw === undefined || raw === "") return fallback;
|
|
38
|
+
if (!/^-?\d+$/.test(raw)) return null;
|
|
39
|
+
const value = Number.parseInt(raw, 10);
|
|
40
|
+
return value >= min && value <= max ? value : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function directionParam<T extends string>(raw: string | undefined, allowed: readonly T[], fallback: T): T | null {
|
|
44
|
+
if (raw === undefined || raw === "") return fallback;
|
|
45
|
+
return (allowed as readonly string[]).includes(raw) ? (raw as T) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function invalid(param: string) {
|
|
49
|
+
return { error: "invalid_param", param } as const;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const graphRoutes = new Hono()
|
|
53
|
+
.get("/graph/meta", (c) => c.json(getGraphMeta()))
|
|
54
|
+
|
|
55
|
+
.get("/graph/clusters", (c) => {
|
|
56
|
+
const rawCommunity = c.req.query("community");
|
|
57
|
+
let community: number | undefined;
|
|
58
|
+
if (rawCommunity !== undefined && rawCommunity !== "") {
|
|
59
|
+
if (!/^\d+$/.test(rawCommunity)) return c.json(invalid("community"), 400);
|
|
60
|
+
community = Number.parseInt(rawCommunity, 10);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return c.json(getClusters({ community, includeIsolates: c.req.query("isolates") === "1" }));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const { body, status } = errorResponse(err);
|
|
67
|
+
return c.json(body, status);
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
.get("/graph/neighborhood", (c) => {
|
|
72
|
+
const center = c.req.query("center");
|
|
73
|
+
if (!center) return c.json({ error: "missing_center" }, 400);
|
|
74
|
+
const depth = intParam(c.req.query("depth"), 1, MAX_NEIGHBORHOOD_DEPTH, 1);
|
|
75
|
+
if (depth === null) return c.json(invalid("depth"), 400);
|
|
76
|
+
const direction = directionParam(c.req.query("direction"), ["in", "out", "both"] as const, "both");
|
|
77
|
+
if (direction === null) return c.json(invalid("direction"), 400);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
return c.json(getNeighborhood({ center, depth, direction }));
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const { body, status } = errorResponse(err);
|
|
83
|
+
return c.json(body, status);
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
.get("/graph/discovery", (c) => {
|
|
88
|
+
const root = c.req.query("root") || undefined;
|
|
89
|
+
const direction = directionParam(c.req.query("direction"), ["out", "both"] as const, "out");
|
|
90
|
+
if (direction === null) return c.json(invalid("direction"), 400);
|
|
91
|
+
const maxDepth = intParam(c.req.query("maxDepth"), 1, MAX_DISCOVERY_DEPTH, MAX_DISCOVERY_DEPTH);
|
|
92
|
+
if (maxDepth === null) return c.json(invalid("maxDepth"), 400);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return c.json(getDiscovery({ root, direction, maxDepth }));
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const { body, status } = errorResponse(err);
|
|
98
|
+
return c.json(body, status);
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
.get("/graph/maintenance", (c) => {
|
|
103
|
+
const staleDays = intParam(c.req.query("staleDays"), 1, 3650, DEFAULT_STALE_DAYS);
|
|
104
|
+
if (staleDays === null) return c.json(invalid("staleDays"), 400);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return c.json(getMaintenance({ staleDays }));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const { body, status } = errorResponse(err);
|
|
110
|
+
return c.json(body, status);
|
|
111
|
+
}
|
|
112
|
+
});
|