feat(8.0): GraphAccelerationProvider contract — the native graph-engine seam
Defines the optional native graph-acceleration provider (cor 3.0) that
brainy feature-detects and routes brain.graph.* / related({node}) /
find({connected}) to, falling back to pure-TS adjacency when absent. This is
the published seam cor wires its native build against (mirrors the existing
VersionedIndexProvider pattern: defined in plugin.ts, exported from index.ts,
implemented externally, feature-detected at runtime).
Contract (locked cross-team in the design thread):
- GraphAccelerationProvider: traverse, edgesForNode, graphCursorOpen/Next/Close,
pageRank, connectedComponents, shortestPath, neighborhoodSample, topByDegree.
Every read op is generation-aware (optional trailing generation?: bigint) so
db.asOf(g).graph.* resolves historically; omitted = now.
- Subgraph: columnar wire format (BigInt64Array node/edge columns, Uint16Array
edgeTypes, optional Uint8Array nodeDepth, truncated flag) — parallel typed
arrays, never array-of-objects; brainy maps u64<->UUID lazily for rendered rows.
- OpaqueIdSet: a find() universe forwarded opaquely into traverse/search for the
zero-crossing query->expand fusion (brainy never inspects it; the provider
version-tags + validates the envelope).
- VectorIndexProvider.search gains allowedIds?: OpaqueIdSet | ReadonlySet<string>
(predicate pushdown — recovers filtered recall lost to post-filtering).
- EntityIdMapperProvider gains entityIntsToUuids(BigInt64Array) — the bigint batch
reverse resolver for Subgraph.nodes; implemented on the JS mapper (TS fallback).
All new public types exported from index.ts. Test: entityIntsToUuids round-trip
+ order-preservation + empty-int sentinel.
This commit is contained in:
parent
682e786cc3
commit
a3d6fdb8b3
4 changed files with 391 additions and 1 deletions
321
src/plugin.ts
321
src/plugin.ts
|
|
@ -287,6 +287,298 @@ export interface GraphIndexProvider {
|
|||
getAllRelationshipCounts(): Map<string, number>
|
||||
}
|
||||
|
||||
// ============= Graph Acceleration (optional native engine) =============
|
||||
|
||||
/**
|
||||
* Opaque serialized id-set — a cor-internal roaring payload (a Node `Buffer` at
|
||||
* runtime when the native engine is present), passed straight from a `find()`
|
||||
* universe into `traverse` / `VectorIndexProvider.search` with NO id
|
||||
* materialization in TypeScript (the O(1)-crossing query→expand win). Brainy
|
||||
* NEVER inspects it; the provider version-tags the envelope and throws on a
|
||||
* format mismatch (it owns integrity + the id-space-width guarantee). Typed as
|
||||
* `Uint8Array` (which a Node `Buffer` satisfies) so the universal build stays
|
||||
* browser-safe.
|
||||
*/
|
||||
export type OpaqueIdSet = Uint8Array
|
||||
|
||||
/** Direction of a graph traversal relative to each frontier node. */
|
||||
export type GraphTraversalDirection = 'in' | 'out' | 'both'
|
||||
|
||||
/**
|
||||
* Columnar subgraph — the shared wire format for every native graph read
|
||||
* (`traverse`, `edgesForNode`, cursor chunks). Parallel typed arrays, never an
|
||||
* array-of-objects, so the NAPI boundary transfers them near-zero-copy and
|
||||
* Brainy maps u64↔UUID **lazily** (only for the rows a caller actually renders,
|
||||
* via `EntityIdMapperProvider.entityIntsToUuids` + `GraphIndexProvider.verbIntsToIds`).
|
||||
* The three `edge*` arrays are parallel (index `i` is one edge); `nodes` /
|
||||
* `nodeDepth` are parallel.
|
||||
*/
|
||||
export interface Subgraph {
|
||||
/** Discovered entity ints; resolve via `entityIntsToUuids`. */
|
||||
nodes: BigInt64Array
|
||||
/** Hop distance of each node from the nearest seed (parallel to `nodes`). Present iff `includeDepth`. */
|
||||
nodeDepth?: Uint8Array
|
||||
/** Edge source entity ints. */
|
||||
edgeSources: BigInt64Array
|
||||
/** Edge target entity ints. */
|
||||
edgeTargets: BigInt64Array
|
||||
/** Edge verb ints; resolve via `verbIntsToIds`. */
|
||||
edgeVerbInts: BigInt64Array
|
||||
/** Edge verb-type indices (stable TypeIdx; resolve via `TypeUtils.getVerbFromIndex`). */
|
||||
edgeTypes: Uint16Array
|
||||
/**
|
||||
* `true` when a `maxNodes` / `maxEdges` cap truncated the result. The returned
|
||||
* rows are the deterministic BFS-order prefix; page the remainder with a
|
||||
* `graphCursor` (traverse stays stateless/bounded — no continuation token).
|
||||
*/
|
||||
truncated?: boolean
|
||||
/** When `truncated`, how many nodes/edges were cut (for "+N more" affordances). */
|
||||
truncatedNodeCount?: number
|
||||
truncatedEdgeCount?: number
|
||||
}
|
||||
|
||||
/** Knobs shared by the bounded traversals. */
|
||||
export interface TraverseOptions {
|
||||
/** Max hop distance from any seed. */
|
||||
depth: number
|
||||
/** Edge direction to follow (default `'both'`). */
|
||||
direction?: GraphTraversalDirection
|
||||
/** Restrict traversed edges to these verb-type indices (TypeIdx). */
|
||||
verbTypes?: number[]
|
||||
/** Restrict traversed edges to these subtypes. */
|
||||
subtypes?: string[]
|
||||
/**
|
||||
* Visibility tiers to EXCLUDE from the frontier (default: `['internal','system']`,
|
||||
* matching `related()`). Pass `[]` for an all-tiers (admin) view.
|
||||
*/
|
||||
excludeVisibility?: string[]
|
||||
/** Cap on returned nodes (deterministic BFS-order prefix; sets `Subgraph.truncated`). */
|
||||
maxNodes?: number
|
||||
/** Cap on returned edges. */
|
||||
maxEdges?: number
|
||||
/** Include the edge arrays (`false` = nodes-only reachability). Default `true`. */
|
||||
includeEdges?: boolean
|
||||
/** Populate `Subgraph.nodeDepth`. Default `false`. */
|
||||
includeDepth?: boolean
|
||||
}
|
||||
|
||||
/** Options for the both-direction single-node edge read. */
|
||||
export interface EdgesForNodeOptions {
|
||||
/** Edge direction (default `'both'`). */
|
||||
direction?: GraphTraversalDirection
|
||||
verbTypes?: number[]
|
||||
subtypes?: string[]
|
||||
excludeVisibility?: string[]
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/** Opaque server-side cursor handle (TTL-bounded; pinned to a generation at open). */
|
||||
export type GraphCursorHandle = string
|
||||
|
||||
/** Options for opening a streaming whole-graph (or seeded) cursor. */
|
||||
export interface GraphCursorOptions {
|
||||
direction?: GraphTraversalDirection
|
||||
excludeVisibility?: string[]
|
||||
/** `'light'` = ids/types only, no metadata-join columns (viz default); `'full'` = with joins. */
|
||||
projection?: 'light' | 'full'
|
||||
/** Seed set to bound the walk; omitted = the whole graph. */
|
||||
seeds?: bigint[] | OpaqueIdSet
|
||||
/**
|
||||
* Resume token from a prior chunk — used to continue after a `SnapshotExpired`
|
||||
* (the pinned generation's TTL lapsed): reopen with this to resume the walk.
|
||||
*/
|
||||
cursor?: string
|
||||
}
|
||||
|
||||
/** One streamed chunk of a graph cursor walk. */
|
||||
export interface GraphCursorChunk {
|
||||
/** The chunk's nodes + edges, columnar. */
|
||||
subgraph: Subgraph
|
||||
/** Opaque resume token (pass to `graphCursorOpen({ cursor })` after `SnapshotExpired`). */
|
||||
cursor?: string
|
||||
/** `true` once the walk is exhausted; `graphCursorNext` should not be called again. */
|
||||
done: boolean
|
||||
}
|
||||
|
||||
/** Per-node scalar scores (pageRank, topByDegree); `scores[i]` belongs to `nodeInts[i]`. */
|
||||
export interface GraphScores {
|
||||
nodeInts: BigInt64Array
|
||||
scores: Float64Array
|
||||
}
|
||||
|
||||
/** Connected-component labelling; `componentIds[i]` is the component of `nodeInts[i]`. */
|
||||
export interface GraphComponents {
|
||||
nodeInts: BigInt64Array
|
||||
componentIds: Uint32Array
|
||||
componentCount: number
|
||||
}
|
||||
|
||||
/** A shortest path: the node sequence + the edges between them (or `null` if unreachable). */
|
||||
export interface GraphPath {
|
||||
nodeInts: BigInt64Array
|
||||
edgeVerbInts: BigInt64Array
|
||||
/** Sum of edge weights for a weighted path; hop count for an unweighted one. */
|
||||
cost: number
|
||||
}
|
||||
|
||||
export interface PageRankOptions {
|
||||
/** Damping factor (default 0.85). */
|
||||
damping?: number
|
||||
/** Max power-iterations (default ~20). */
|
||||
maxIterations?: number
|
||||
/** Convergence tolerance. */
|
||||
tolerance?: number
|
||||
excludeVisibility?: string[]
|
||||
/** Return only the top-K by score (default: all). */
|
||||
topK?: number
|
||||
}
|
||||
|
||||
export interface ComponentsOptions {
|
||||
/** `'weak'` ignores direction (default); `'strong'` honors it. */
|
||||
mode?: 'weak' | 'strong'
|
||||
excludeVisibility?: string[]
|
||||
}
|
||||
|
||||
export interface ShortestPathOptions {
|
||||
direction?: GraphTraversalDirection
|
||||
/** `'edge'` = weighted by edge weight; `'hops'` = unweighted BFS (default `'hops'`). */
|
||||
weight?: 'edge' | 'hops'
|
||||
verbTypes?: number[]
|
||||
excludeVisibility?: string[]
|
||||
/** Abandon the search past this many hops. */
|
||||
maxDepth?: number
|
||||
}
|
||||
|
||||
export interface NeighborhoodSampleOptions {
|
||||
depth: number
|
||||
direction?: GraphTraversalDirection
|
||||
/** Max neighbors to sample per node (random, seeded for reproducibility). */
|
||||
fanout: number
|
||||
/** RNG seed so a given (seeds, opts, seed) yields a stable sample for viz. */
|
||||
seed?: number
|
||||
excludeVisibility?: string[]
|
||||
maxNodes?: number
|
||||
}
|
||||
|
||||
export interface TopByDegreeOptions {
|
||||
direction?: GraphTraversalDirection
|
||||
topK: number
|
||||
excludeVisibility?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The `'graphAcceleration'` provider — an OPTIONAL native graph engine (the
|
||||
* cor 3.0 acceleration layer). Brainy feature-detects it on the registered
|
||||
* providers: when present, the public `brain.graph.*` surface and the
|
||||
* `related({ node })` / `neighbors({ depth })` / `find({ connected })` paths
|
||||
* route here; when absent, Brainy serves the same operations from its pure-TS
|
||||
* adjacency fallback (correct, small-graph-scale). It is SEPARATE from the
|
||||
* required {@link GraphIndexProvider} (which stays the bigint-adjacency
|
||||
* contract) so "optional native acceleration" is explicit at the seam.
|
||||
*
|
||||
* **Generation-aware (8.0 time-travel).** Every read takes an optional trailing
|
||||
* `generation?: bigint`; omitted = the current generation ("now"). With a
|
||||
* generation, the provider resolves the graph **as of** that generation
|
||||
* (`db.asOf(g).graph.*`), reading its generation-filtered adjacency rather than
|
||||
* the now-only fast path.
|
||||
*
|
||||
* **Columnar + opaque-id-set.** Reads return the columnar {@link Subgraph};
|
||||
* `traverse` / sample accept seeds as `bigint[]` OR an {@link OpaqueIdSet}
|
||||
* (a `find()` universe forwarded with no id materialization — the query→expand
|
||||
* fusion). Visibility tiers are excluded at the index by default.
|
||||
*/
|
||||
export interface GraphAccelerationProvider {
|
||||
/** `false` until the native engine has loaded; Brainy probes before routing. */
|
||||
readonly isInitialized: boolean
|
||||
|
||||
/**
|
||||
* @description Bounded multi-hop expansion from `seeds`, returning the reachable
|
||||
* subgraph. One call replaces Brainy's per-hop BFS round-trips.
|
||||
* @param seeds - Start nodes as entity ints, OR an {@link OpaqueIdSet} (a
|
||||
* `find()` universe — the frontier is intersected in id-space, zero crossing).
|
||||
* @param options - Depth, direction, edge/visibility filters, and caps.
|
||||
* @param generation - Optional as-of generation (omitted = now).
|
||||
* @returns The reachable {@link Subgraph} (BFS-order; `truncated` set if capped).
|
||||
*/
|
||||
traverse(seeds: bigint[] | OpaqueIdSet, options: TraverseOptions, generation?: bigint): Promise<Subgraph>
|
||||
|
||||
/**
|
||||
* @description All edges incident to one node, both directions, as structure +
|
||||
* cor-indexed fields (Brainy hydrates full edge metadata lazily — the provider
|
||||
* cannot recover verb-id strings from its interned form).
|
||||
* @param nodeInt - The node's interned entity int.
|
||||
* @param options - Direction, edge/visibility filters, limit.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns A {@link Subgraph} of the node's incident edges.
|
||||
*/
|
||||
edgesForNode(nodeInt: bigint, options: EdgesForNodeOptions, generation?: bigint): Promise<Subgraph>
|
||||
|
||||
/**
|
||||
* @description Open a snapshot-consistent streaming walk of the whole graph (or a
|
||||
* seeded region) for O(N) viz loads. Pins a generation at open; the walk reads
|
||||
* as-of that pin (no dup/skip under concurrent writes). Handles are TTL-bounded —
|
||||
* `graphCursorNext` after expiry rejects with `SnapshotExpired`; reopen with the
|
||||
* last chunk's `cursor` to resume. Call `graphCursorClose` on normal completion.
|
||||
* @param options - Direction, visibility, projection mode, optional seeds / resume cursor.
|
||||
* @param generation - Optional explicit as-of generation to pin (else pins current).
|
||||
* @returns A handle for `graphCursorNext` / `graphCursorClose`.
|
||||
*/
|
||||
graphCursorOpen(options: GraphCursorOptions, generation?: bigint): Promise<GraphCursorHandle>
|
||||
/**
|
||||
* @description Pull the next chunk from an open cursor.
|
||||
* @param handle - From `graphCursorOpen`.
|
||||
* @param chunkSize - Target number of nodes/edges in this chunk.
|
||||
* @returns The next {@link GraphCursorChunk}; `done: true` when exhausted.
|
||||
*/
|
||||
graphCursorNext(handle: GraphCursorHandle, chunkSize: number): Promise<GraphCursorChunk>
|
||||
/**
|
||||
* @description Release an open cursor's pinned generation and server-side state.
|
||||
* @param handle - From `graphCursorOpen`.
|
||||
*/
|
||||
graphCursorClose(handle: GraphCursorHandle): Promise<void>
|
||||
|
||||
/**
|
||||
* @description PageRank over the visible graph (hidden tiers excluded by default,
|
||||
* so importance reflects the public view).
|
||||
* @param options - Damping, iterations, tolerance, visibility, optional top-K.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns Per-node scores.
|
||||
*/
|
||||
pageRank(options: PageRankOptions, generation?: bigint): Promise<GraphScores>
|
||||
/**
|
||||
* @description Connected components over the visible graph.
|
||||
* @param options - Weak/strong, visibility.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns Per-node component labels + component count.
|
||||
*/
|
||||
connectedComponents(options: ComponentsOptions, generation?: bigint): Promise<GraphComponents>
|
||||
/**
|
||||
* @description Shortest path between two nodes (weighted by edge weight or hop count).
|
||||
* @param fromInt - Start node int.
|
||||
* @param toInt - End node int.
|
||||
* @param options - Direction, weighting, filters, max depth.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns The path, or `null` if `toInt` is unreachable from `fromInt`.
|
||||
*/
|
||||
shortestPath(fromInt: bigint, toInt: bigint, options: ShortestPathOptions, generation?: bigint): Promise<GraphPath | null>
|
||||
/**
|
||||
* @description Bounded, randomly-sampled neighborhood expansion (capped fan-out per
|
||||
* node) — a representative subgraph for dense-graph viz. Seeded for reproducibility.
|
||||
* @param seeds - Start nodes (ints or an {@link OpaqueIdSet}).
|
||||
* @param options - Depth, fan-out, RNG seed, visibility, node cap.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns The sampled {@link Subgraph}.
|
||||
*/
|
||||
neighborhoodSample(seeds: bigint[] | OpaqueIdSet, options: NeighborhoodSampleOptions, generation?: bigint): Promise<Subgraph>
|
||||
/**
|
||||
* @description The top-K nodes by degree over the visible graph.
|
||||
* @param options - Direction, K, visibility.
|
||||
* @param generation - Optional as-of generation.
|
||||
* @returns Per-node degree scores (descending).
|
||||
*/
|
||||
topByDegree(options: TopByDegreeOptions, generation?: bigint): Promise<GraphScores>
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional capability interface for index providers that maintain versioned
|
||||
* (generation-aware) internal state — the provider-side half of Brainy 8.0's
|
||||
|
|
@ -411,7 +703,20 @@ export interface VectorIndexProvider {
|
|||
queryVector: Vector,
|
||||
k?: number,
|
||||
filter?: (id: string) => Promise<boolean>,
|
||||
options?: { rerank?: { multiplier: number }; candidateIds?: string[] }
|
||||
options?: {
|
||||
rerank?: { multiplier: number }
|
||||
candidateIds?: string[]
|
||||
/**
|
||||
* Predicate-pushdown universe — restrict the result to this id-set, applied
|
||||
* INSIDE the beam walk (walk traverses all nodes, collects only allowed), which
|
||||
* recovers the filtered recall that post-filtering loses. A native provider with
|
||||
* cor as the metadata index gets the `find()` universe as an {@link OpaqueIdSet}
|
||||
* (zero id materialization); the pure-JS path gets a `ReadonlySet<string>`. Absent
|
||||
* = no restriction. Optional — a provider that doesn't pushdown ignores it and
|
||||
* falls back to `filter`.
|
||||
*/
|
||||
allowedIds?: OpaqueIdSet | ReadonlySet<string>
|
||||
}
|
||||
): Promise<Array<[string, number]>>
|
||||
size(): number
|
||||
clear(): void
|
||||
|
|
@ -437,6 +742,20 @@ export interface EntityIdMapperProvider {
|
|||
clear(): Promise<void>
|
||||
getAllIntIds(): number[]
|
||||
intsIterableToUuids(ints: Iterable<number>): string[]
|
||||
/**
|
||||
* @description Batch reverse-resolve u64 entity ints → UUID strings — the
|
||||
* bigint counterpart of {@link EntityIdMapperProvider.intsIterableToUuids},
|
||||
* for the native graph engine ({@link GraphAccelerationProvider}, whose
|
||||
* `Subgraph.nodes` is a `BigInt64Array`). Brainy resolves lazily — only the
|
||||
* rows a caller renders — but viz/export render many at once, so one batch
|
||||
* crossing beats N. Order-preserving (one entry per input). A native mapper
|
||||
* resolves every int from its binary int↔uuid store; the JS fallback resolves
|
||||
* assigned ints and yields `''` for a never-assigned int (which does not occur
|
||||
* for graph-engine results, since those only reference assigned ints).
|
||||
* @param nodeInts - Entity ints as returned in a {@link Subgraph}.
|
||||
* @returns One UUID per input, in order.
|
||||
*/
|
||||
entityIntsToUuids(nodeInts: BigInt64Array): string[]
|
||||
readonly size: number
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue