- graph_load_from_operations: SQLite read-only, schema-detect (type_ref/type, from_entity/source, to_entity/target, name/type, weight, updated_at). - 16-color indigo palette por hash FNV1a32 del nombre de tipo. user_data por nodo es FNV1a64(entity.id) — deterministico entre cargas. - Label pool interno: metadata.name (JSON simple) > entities.name > id. - graph_free libera nodes/edges/types/rel_types/labels/strdup'd names via arena_map (GraphData* -> arena). - Streaming pull-based con tiebreak (updated_at, id) y crecimiento x2 de capacidad. Tipos nuevos descubiertos en stream se anaden a types. - Tests: fixture in-memory (3 entity types, 2 rel types, 10 entities, 15 relations) + smoke contra apps/script_navegador/operations.db. - Issue movido a completed/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.3 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, framework, params, output
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | framework | params | output | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| graph_sources | function | cpp | viz | 1.0.0 | impure | bool graph_load_from_operations(const char* db_path, GraphData* out, GraphLoadStats* stats) | Lectores de grafos para GraphData con firma uniforme GraphLoadFn. Primera implementacion: operations.db (entities + relations) con variante streaming pull-based |
|
|
false | error_go_core | true |
|
cpp/tests/test_graph_sources.cpp | cpp/functions/viz/graph_sources.cpp | imgui |
|
true si la carga fue correcta. false con stats->errors > 0 y error_msg poblado en error. Tras un retorno true: out->nodes/edges apuntan a memoria interna; out->types/rel_types listan los tipos descubiertos con color por hash del nombre y SHAPE_CIRCLE/EDGE_SOLID por defecto. user_data por nodo es FNV1a64 del entity.id (deterministico). label_idx apunta a metadata.name si existe, else entity.name, else entity.id. Streaming: graph_stream_operations_open captura MAX(updated_at, id) actuales; graph_stream_pull devuelve cuantas filas append y crece capacity en x2 si hace falta |
graph_sources
Lectores de grafos para GraphData (issue 0049g). Disenado como un set de funciones con la misma firma GraphLoadFn para que anadir un backend nuevo (JSON, JSONL, GraphML, Neo4j export, etc.) sea declarar otra funcion compatible — el resto del codigo (apps, viewport, force layout, renderer) no cambia.
API
typedef bool (*GraphLoadFn)(const char* uri, GraphData* out, GraphLoadStats* stats);
bool graph_load_from_operations(const char* db_path, GraphData* out, GraphLoadStats* stats);
void graph_free(GraphData* graph);
const char* graph_label(const GraphData* graph, uint32_t label_idx);
// Streaming pull-based (poll cada N ms desde el caller).
GraphStreamSource* graph_stream_operations_open(const char* db_path, int poll_ms);
int graph_stream_pull(GraphStreamSource*, GraphData*);
void graph_stream_close(GraphStreamSource*);
Mapeo operations.db → GraphData
operations.db es la BD de cada app del registry. La funcion detecta el schema via PRAGMA table_info:
| Concepto | Columna preferida | Fallback |
|---|---|---|
| Tipo de entidad | entities.type_ref |
entities.type |
| Source / target de relacion | relations.from_entity / relations.to_entity |
relations.source / relations.target |
| Tipo de relacion | relations.type |
relations.name |
| Etiqueta de nodo | metadata.name (JSON) |
entities.name o entities.id |
| Tiebreak streaming | (updated_at, id) |
id solo |
Cada valor distinto de type_ref produce un EntityType con color tomado de un palette de 16 (FNV1a32 sobre el nombre → palette[h & 0xF]), shape = SHAPE_CIRCLE, default_size = 6.0, icon_id = 0. La app consumidora puede sobreescribir esa apariencia via types.yaml (issue 0049k).
user_data por nodo es FNV1a64(entity.id) — deterministico entre cargas y util como handle estable para joins con metadata externa.
Errores
La funcion no usa excepciones. En error:
- Retorna
false. stats->errors >= 1.stats->error_msgcontiene un texto corto ("open: ...","missing table: entities","entities: missing type_ref/type column", ...).
Relaciones con from_entity / to_entity que apunten a entities inexistentes se cuentan en stats->errors y se descartan — el grafo resultante es siempre consistente.
Streaming
El streaming es pull-based: el caller decide la cadencia y llama graph_stream_pull cuando quiere chequear cambios. La fuente guarda (MAX(updated_at), MAX(id)) al abrir y avanza el cursor con tiebreak (updated_at, id):
WHERE (updated_at > ?) OR (updated_at = ? AND id > ?)
ORDER BY updated_at, id
graph_stream_pull crece la capacidad de nodes/edges en x2 si hace falta — el caller no necesita pre-allocar. Tipos nuevos descubiertos durante el stream se anaden al final de types. Operaciones inversas (deletes) no se propagan — para reset, el caller debe graph_free y recargar.
Memoria
graph_load_from_operations aloca toda la memoria que cuelga del GraphData devuelto (incluido el string pool de labels y los nombres de tipos via strdup). El caller libera todo con graph_free(graph). La asociacion GraphData* → arena se mantiene en un mapa estatico interno; graph_label y graph_stream_pull lo consultan para acceder al pool.
Ejemplo
GraphData g{};
graph::GraphLoadStats s{};
if (!graph::graph_load_from_operations("apps/script_navegador/operations.db", &g, &s)) {
fprintf(stderr, "load failed: %s\n", s.error_msg);
return 1;
}
printf("nodes=%d edges=%d types=%d rel_types=%d\n",
s.nodes_loaded, s.edges_loaded, s.types_discovered, s.rel_types_discovered);
// Render con graph_renderer + force layout via graph_force_layout.
// ...
// Watcher en otro hilo:
auto* src = graph::graph_stream_operations_open("apps/script_navegador/operations.db", 500);
while (running) {
sleep_ms(500);
int n = graph::graph_stream_pull(src, &g);
if (n > 0) printf("appended %d new rows\n", n);
}
graph::graph_stream_close(src);
graph::graph_free(&g);
Notas
- v1.0 (2026-04-29, issue 0049g): primera version. Lector sincrono + streaming poll-based. Tests con fixture in-memory: 10 entities + 15 relations, 3 entity types, 2 relation types. Determinismo del
user_datay resolucion de aristas verificados. - La firma
GraphLoadFnesta diseniada para futuros backends. Anadir uno (ej.graph_load_from_jsonl) consiste en declarar una funcion con la misma firma; nada en el resto del pipeline (renderer, layout, viewport) cambia.