Skip to content

Loading Data

const everyone = await gom.loadAll(PersonCareer);
const one = await gom.load(PersonCareer, uuid); // undefined when there is none
const open = await gom.loadAll(Issue, (q) => ({ where: q.state.eq('open') }));
const raw = await gom.loadAll(Issue, 'n.state = $state', { state: 'open' }); // a predicate of your own, over n

count counts exactly what loadAll would return for the same type and filter:

await gom.count(Issue);
await gom.count(Issue, (q) => ({ where: q.state.eq('open') }));
await gom.count(RaisedAndAssignedIssue); // only the issues that meet the view's required relationships

A fragment is counted by its labels. A view counts only the roots that meet its required relationships and paths, so the number equals loadAll(...).length, not a count of nodes.

loadNearest runs an approximate nearest-neighbour search and returns each hit with a similarity score. It works on a view, by its root’s embedding, and on a fragment:

@NodeFragment({ labels: ['Proposition'] })
export class PropositionNode {
@NodeId() id: string;
text: string;
@VectorIndex({ similarity: 'COSINE' }) embedding?: number[];
}
const hits: Scored<PropositionView>[] = await gom.loadNearest(PropositionView, queryEmbedding, { topK: 20 });
hits.forEach((it) => console.log(it.score, it.value.proposition.text));

The searched embedding is the fragment’s @VectorIndex field: inferred when there is one, and named with property when there are several. The score is a similarity, higher meaning more similar, on every engine, so threshold means the same thing everywhere.

Option Meaning
topK How many to return. Without searchK, also how many the index is asked for.
searchK How many the index is asked for, at least topK. topK then trims after the filter.
threshold The least score a hit may have.
where A condition applied after the search: references, or a predicate over n with bindings.
partitionLabel Search the index of another label over the same property: one index per corpus or tenant.
property Which @VectorIndex field, when the fragment has several.
depth maxDepth overrides for a view.

topK is the index’s k, not a promised count. A view’s required relationships, threshold and where filter after the search, so a search can return fewer than topK.

k is also the search’s beam. On Lucene-backed engines the result queue is the HNSW candidate queue, so a small k can miss a vector that really is nearest. searchK widens the search without widening the result:

// search with a beam of 200, and return the best 40 that pass the filter
await gom.loadNearest(PropositionView, queryEmbedding, {
topK: 40,
searchK: 200,
where: (q) => [q.proposition.contextId.eq(ctx), q.mentions.any((m) => m.resolvedId.eq(entityId))]
});

A filtered search dilutes: at 22% selectivity, 40 asked for returns about 9, the nearest overall that happen to pass rather than the nearest of those that pass. searchK mitigates that. A per-partition index avoids it: with one vector index for each partition label, partitionLabel searches inside the partition, which is filtered by construction.

loadMatching is the full-text mirror of loadNearest. The relevance is scaled to [0, 1], the best hit being 1:

@NodeFragment({ labels: ['Chunk'] })
export class ChunkNode {
@NodeId() id: string;
@FullTextIndex() text: string;
}
await gom.loadMatching(ChunkNode, 'graph databases', { topK: 20 });
await gom.loadMatching(ChunkView, 'graph databases', { topK: 20, threshold: 0.5, where: (q) => q.chunk.sectionId.eq('s1') });

The index is the fragment’s @FullTextIndex: on a property for one field, or on the class, @FullTextIndex({ properties: ['title', 'body'] }), for several. Name one with property when the fragment declares more than one. The text is the engine’s full-text language (Lucene on Neo4j): escape user input yourself.