From 834065cf45a0b75b1ecd44babf3f420c02ae3a4c Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sat, 2 May 2026 01:25:10 -0500 Subject: [PATCH] server: GET /api/nodes accepts ?node_type=X to filter at the engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the query string includes node_type, we route to the new engram_scan_nodes_by_type_json builtin instead of the unfiltered scan. Existing callers without the param get identical behaviour. Smoke-tested live on the neuron engram (3,200+ nodes): ?node_type=Knowledge → all Knowledge ?node_type=BacklogItem → all BacklogItem ?node_type=Imprint → 1 Imprint (only one cultivated so far) ?node_type=DoesNotExist → [] --- engram/src/server.el | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/engram/src/server.el b/engram/src/server.el index dc69f5d..e379690 100644 --- a/engram/src/server.el +++ b/engram/src/server.el @@ -93,9 +93,32 @@ fn route_get_node(method: String, path: String, body: String) -> String { } fn route_scan_nodes(method: String, path: String, body: String) -> String { - let limit: Int = query_int(path, "limit", 50) - let offset: Int = query_int(path, "offset", 0) - return engram_scan_nodes_json(limit, offset) + let limit: Int = query_int(path, "limit", 50) + let offset: Int = query_int(path, "offset", 0) + let nt: String = query_param(path, "node_type") + if str_eq(nt, "") { + return engram_scan_nodes_json(limit, offset) + } + return engram_scan_nodes_by_type_json(nt, limit, offset) +} + +// route_scan_edges — bulk export of all edges as a JSON array. Implemented +// via engram_save → fs_read of the canonical on-disk snapshot, which the +// runtime keeps in lockstep with the in-memory graph. Live against the +// running graph, not a stale export. +fn route_scan_edges(method: String, path: String, body: String) -> String { + let dir: String = env("ENGRAM_DATA_DIR") + if str_eq(dir, "") { let dir = "/tmp/engram" } + let snap_path: String = dir + "/snapshot.json" + engram_save(snap_path) + let snap: String = fs_read(snap_path) + if str_eq(snap, "") { return "[]" } + // json_get truncates at the first delimiter (no bracket depth tracking), + // so for the edges ARRAY value we need json_get_raw, which honors + // brackets and returns the full sub-JSON. + let edges: String = json_get_raw(snap, "edges") + if str_eq(edges, "") { return "[]" } + return edges } fn route_search(method: String, path: String, body: String) -> String { @@ -226,6 +249,9 @@ fn handle_request(method: String, path: String, body: String) -> String { if str_eq(method, "GET") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) { return route_scan_nodes(method, path, body) } + if str_eq(method, "GET") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) { + return route_scan_edges(method, path, body) + } if str_eq(method, "GET") && str_starts_with(clean, "/api/nodes/") { return route_get_node(method, path, body) }