feat(8.0): brain.graph.export() + noun-walk cursor + noun visibility hydration

brain.graph.export() streams the WHOLE graph in one O(N+E) pass — node chunks
then edge chunks, async-iterable — the right primitive for visualizing all data
(vs. paging per node). Native snapshot-consistent graphCursor when present, else
a TS cursor walk over nouns + verbs. Building it surfaced two latent noun bugs,
both fixed here (each a correctness win beyond export):

- getNounsWithPagination IGNORED its cursor ('offset-based, cursor planned') —
  the noun mirror of the verb bug fixed in the cursor-pagination commit. Latent
  because the only multi-page consumer (aggregate backfill) uses one big page; a
  small chunkSize needed page 2 and, trusting the returned-but-ignored nextCursor,
  re-fetched page 0 forever (infinite loop). Ported the proven verb cursor:
  opaque cn1:<shard>:<id> token, stable within-shard id order, resume-after,
  O(N) at any chunk size. (Generalized verbIdFromVectorPath → idFromVectorPath.)
- hydrateNounWithMetadata DROPPED 'visibility' (noun mirror of the Fix #1 verb
  hydration bug) — so getNouns-fed visibility filters saw nothing and leaked
  system (VFS root) / internal nodes. Now hydrated.

- New: GraphApi.export + GraphExportOptions (chunkSize, includeInternal/System,
  includeNodes/Edges). hydrateNativeSubgraph extracted + shared by subgraph+export.

Test: graph-export.test.ts — full-graph completeness incl. isolated nodes,
default-hides-internal, node/edge include toggles, chunkSize chunking (the
case that exposed the cursor hang). Full gate green.
This commit is contained in:
David Snelling 2026-06-21 10:22:12 -07:00
parent 8c2b57a488
commit c2a84c9d1f
4 changed files with 369 additions and 75 deletions

View file

@ -821,13 +821,29 @@ export interface SubgraphOptions {
hydrateNodes?: boolean
}
/**
* Options for {@link GraphApi.export}.
*/
export interface GraphExportOptions {
/** Nodes/edges per streamed chunk (default `1000`). */
chunkSize?: number
/** Also include `internal`-visibility nodes/edges (hidden by default). */
includeInternal?: boolean
/** Also include `system`-visibility nodes/edges (hidden by default). */
includeSystem?: boolean
/** Stream the nodes (default `true`; set `false` to stream only edges). */
includeNodes?: boolean
/** Stream the edges (default `true`; set `false` to stream only nodes). */
includeEdges?: boolean
}
/**
* The `brain.graph` namespace graph-shaped reads over the knowledge graph.
* Routes to a native {@link import('../plugin.js').GraphAccelerationProvider}
* when one is registered, otherwise serves the same results from Brainy's
* pure-TS adjacency (correct at small/medium scale).
*
* (Grows with the engine: `subgraph` ships first; `export` / `rank` / `path` /
* (Grows with the engine: `subgraph` + `export` ship first; `rank` / `path` /
* `communities` follow.)
*/
export interface GraphApi<T = any> {
@ -843,6 +859,21 @@ export interface GraphApi<T = any> {
* // view.nodes = the 2-hop neighborhood; view.edges = the relations among them
*/
subgraph(seeds: string | string[], options?: SubgraphOptions): Promise<GraphView<T>>
/**
* @description Stream the WHOLE graph in one O(N+E) pass as a sequence of
* {@link GraphView} chunks the right primitive for visualizing all data
* (vs. paging per node). Node chunks come first, then edge chunks; assemble
* them on the consumer side. Backed by cursor pagination, so a full walk is
* O(N+E) at any chunk size (no re-scan).
* @param options - Chunk size, visibility, and node/edge inclusion toggles.
* @returns An async-iterable of graph chunks.
* @example
* for await (const chunk of brain.graph.export()) {
* addNodes(chunk.nodes); addEdges(chunk.edges)
* }
*/
export(options?: GraphExportOptions): AsyncIterable<GraphView<T>>
}
// ============= Batch Operations =============