Library API reference
manni graph is a CLI first, and everything it does is reachable as a library. The package publishes three entry points, and which one you want depends on where the code runs.
| Import | Platform | What it carries |
|---|---|---|
@hawkeyexl/manni | Node | The whole family. graph is one namespace on it. |
@hawkeyexl/manni/graph/runtime | Browser or Node | Retrieval over a built graph. No node: imports, no dependencies, one file. |
@hawkeyexl/manni/graph/embed | Node | The local embedders, behind the optional @huggingface/transformers peer. |
The split is not cosmetic. A node: import reaching the runtime’s module graph
is a released bug rather than a type error. Nothing in tsc knows which half
it is compiling. Anything the runtime needs from Node is taken as an argument
instead.
The graph namespace
Section titled “The graph namespace”import { graph } from "@hawkeyexl/manni";
const result = await graph.runBuild({ paths: ["docs"] });console.log(result.outPath, result.docs, result.quads);Each namespace is one tool, so a name cannot collide with a sibling’s and a reader can see which tool it belongs to.
Command cores
Section titled “Command cores”Every verb is a function that takes options and returns a report. They do no CLI plumbing and read no argv, so they can be called directly and tested directly.
| Export | Purpose |
|---|---|
runBuild | Derive the graph and write Turtle. Returns { outPath, docs, quads, warnings }. |
runFill | Propose and write graph: fields through a provider. |
runQuery | Match a triple pattern against a built graph. |
runStats | Counts, orphans, broken links, hubs, and coverage. |
runSearch | Rank nodes for a text query, with renderSearch for the report. |
runTraverse | Walk the graph from a node, with renderTraverse for the report. |
runEmbed | Compute vector sidecars, with renderEmbed and the lower-level embedText. |
runExport | Reserialize into jsonld, iirds, or search. |
runBuild takes a BuildOptions and returns a BuildResult. Two of their
fields carry the content strategy:
| Field | Type | Purpose |
|---|---|---|
BuildOptions.authoring | boolean, optional | Overrides graph.authoring. false leaves author-facing content out, as --no-authoring does. |
BuildResult.strategyRecords | number | How many strategy records the build typed. 0 when none is in the build. |
const readers = await graph.runBuild({ collection: ["site"], authoring: false });console.log(readers.strategyRecords); // 0GraphError is the operational failure the CLI maps to exit 2. Catch it to tell
“manni graph could not run” from “the graph did not pass”.
The pipeline
Section titled “The pipeline”The stages runBuild composes, each usable on its own.
| Export | Purpose |
|---|---|
analyzeDoc | One document to a DocModel: frontmatter, headings with slugs and nesting, links with their resolution, images, code blocks. |
deriveGraph | DocModel[] to Quad[]. This is the vocabulary mapping, and the place a new predicate is decided. |
collectGitHistory | Revision history and commit agents, from git. |
emitTurtle, emitJsonLd, emitRdfXml | Serializers. The Turtle one is canonical and hand-written rather than a library’s writer, because byte-identical output is the product contract. |
loadGraph, storeToQuads, expandTerm, compactIri | Read a built graph back, and move between full IRIs and prefixed names. |
buildSearchIndex, emitSearchIndex, partitionByLanguage, documentsByLanguage | The search artifacts. |
projectPackage, writeZip | The iiRDS container. |
Every node gets a deterministic IRI, so there are no blank nodes and two runs agree.
resolveBaseIri, normalizeDocPath, conceptSlug, encodeSegment, and the
minters mintDocIri, mintSectionIri, mintConceptIri, mintSchemeIri,
mintProductIri, mintAgentIri, mintBuildActivityIri, mintGraphIri.
Coverage
Section titled “Coverage”manni graph stats measures how much of the vocabulary a corpus actually fills.
The lists it measures against are exported, so a dashboard reads the same
definition the gate does rather than a copy of it.
| Export | Purpose |
|---|---|
COVERAGE_FIELDS | Every measured document-level field, each with the IRI whose presence counts as filled. |
COVERAGE_FIELD_NAMES | The same list, names only. These are the keys a per-field stats.coverageThreshold map accepts. |
SECTION_COVERAGE_FIELDS | The section-level rows, a subset of the above. Reported, never gated. |
CoverageField | The row type. |
import { graph } from "@hawkeyexl/manni";
for (const field of graph.COVERAGE_FIELDS) { console.log(field.field, field.iri);}There are thirteen measured document-level fields. Two predicates are left unmeasured on purpose, and Coverage says which and why.
The vocabulary
Section titled “The vocabulary”| Export | Purpose |
|---|---|
NS, PREFIXES | The namespace IRIs and their prefixes, including graph: itself. |
frontmatterSchema, FRONTMATTER_SCHEMA_ID | The page vocabulary a graph: block is validated against, bundled into the build. |
TOPIC_TYPE_IRIS, SOFTWARE_LIFECYCLE_IRIS, SOFTWARE_SUBJECT_IRIS | The iiRDS maps behind type, about-product-lifecycle and about-product-aspect. |
GRAPH_NOT_APPLICABLE_TO_VARIANT, GRAPH_NOT_SOFTWARE_SUBJECT | The two negative-scope predicates. |
Config and documents
Section titled “Config and documents”loadConfig, loadRunConfig, parseConfig and defaultConfig, with the
GraphConfig, DeriveSource and FillField types. discoverFiles and
resolveDocumentSet turn paths, collections and excludes into the document
set a verb reads.
applyGraphFields, existingGraphFields, existingMetaProvenance and
frontmatterKind are the frontmatter writer fill uses, if you need to write
a graph: block yourself.
Inference
Section titled “Inference”selectProvider, constructProvider, makeProvider, providerSpecFor,
resolveProviderIdentity and assertProviderSelection resolve a provider the
way graph fill does. MockProvider is re-exported from
@hawkeyexl/inference so a test can
drive the offline seam without a second dependency. manni reimplements no
provider, no inference cache, and no price table.
The browser runtime
Section titled “The browser runtime”@hawkeyexl/manni/graph/runtime is the retrieval half, built platform: neutral
and shipped as one file you can drop in with a script tag. It loads a
graph.jsonld, walks it with scope rules honored, resolves node text, and
assembles the bundle an inference engine consumes.
It never generates. It returns { context, citations, trace } and stops.
Wiring a model is the host’s job, and every result carries the trace that
produced it.
import { GraphIndex, traverse, createFetchResolver, assemble } from "@hawkeyexl/manni/graph/runtime";
const graph = GraphIndex.fromJsonLd(await (await fetch("/graph/graph.jsonld")).text());const { nodes, trace } = traverse(graph, { seeds: ["https://example.com/graph/doc/docs/configuration.md"], depth: 2, variant: "SP-X100",});const resolver = createFetchResolver(graph, { baseUrl: "/raw/", trace });const bundle = await assemble(resolver, nodes, { trace, maxChars: 12000 });| Area | Exports |
|---|---|
| The graph | GraphIndex, GraphNode, Value |
| Walking | traverse, impact, reverseReferences, resolveVariant, resolveSubject, scopeExclusion |
| Text | createFetchResolver, sliceSection, sectionOwnText, documentPreamble, documentSectionOrder, sectionOccurrence, sectionOccurrences, splitFragment |
| Assembly | assemble, with RetrievalBundle, Citation, ContextBlock, Refusal |
| Search | createLexicalIndex, createVectorIndex, findEntry, rrfMerge, searchIndexDigest |
| Vectors | encodeVectorIndex, decodeVectorIndex, normalize, VectorIndexError, VectorMismatchError |
| Localization | parseLocalizations, emitLocalizations, searchIndexFilename, vectorIndexFilename, LOCALIZATIONS_FILENAME, UNDETERMINED |
| Tracing | createTrace, reachedNodes, QueryTrace, Hop, ScopeExclusion |
| RDF/JS | matchQuads, rdfjsQuads, namedNode, literal, defaultGraph |
sliceSection is worth one line of its own. Heading text is not unique, so
slicing a specific section node needs its occurrence. Without one it silently
returns the first same-titled heading’s body.
Served passages
Section titled “Served passages”createRetriever serves the chunk artifact
to a script, with each passage’s proof. It returns data and calls no model
besides the query embedder.
The in-docs playground
runs lookup, retrieve, traverse and impact against this site’s artifact.
It shows the call and its returned values, with an optional WebLLM model to
answer questions about the result in the browser.
import { createRetriever } from "@hawkeyexl/manni/graph/runtime";
const docs = await createRetriever({ manifest: "/graph/chunks.json" });const result = await docs.lookup("How do I rotate the encryption key?");| Option | Type | Default | Meaning |
|---|---|---|---|
manifest | URL string, or a parsed manifest | required | chunks.json. The graph, the search index and the rows resolve from its directory. |
store | VectorStore | the static store over the rows’ vectors | Pass createHttpVectorStore(...) for a hosted vector DB. |
chunks | ChunkSource | the chunk files the manifest names | Pass your own to read rows from a vector DB by id. |
embed | an embedder, or false | graph/embed with each language’s model | false forces lexical entry. A model loads once, when a language that needs it is first queried. |
fetch | function | the global fetch | For hosts without one, or for tests. |
The four verbs are the CLI’s, over the same artifact, and they answer alike.
| Member | Same as | Returns |
|---|---|---|
lookup(question) | graph lookup | the lookup result |
retrieve(question, { lang, variant, softwareSubject, depth, maxChars, mode, where, withhold }) | graph retrieve | a bundle |
traverse(node, { depth, predicates, reverse, lang, variant, softwareSubject, limit, maxChars, where, withhold }) | graph traverse --context | a bundle |
impact(node, { depth, lang, variant, softwareSubject, limit, maxChars, where, withhold }) | graph traverse --impact --context | a bundle |
manifest | n/a | the parsed manifest |
Every option is optional. where is a list of { field, value }, and every
condition must hold, as with --where. withhold lists drifted, failing
or both, as --withhold does. It tightens the floor the artifact was exported
with, for that call, and never lowers it. lookup serves the exported floor.
A refusal comes back in the result.
Two errors throw instead:
StaleArtifactError, when the graph or a row was built from another graph than the manifest names.ModelMismatchError, when the embedder is not the model the queried language’s rows were embedded with. A language is checked only when it is queried.
Each language in chunks.json names its own model, and the question is
embedded with the queried language’s. A host that cannot load that model
falls back to lexical entry. So does a language with no model, when another
language has one. trace.entry.fallback then reads "lexical". The default embedder loads from
the graph/embed file beside the runtime, so a bundler that does not ship it
gets the fallback.
The rows are read lazily, one language at a time.
The seams
Section titled “The seams”A host supplies two seams, and manni ships an implementation of each.
interface VectorStore { query(q: { text: string; vector: number[] | null; k: number; language: string | null; where: { field: string; value: string }[] }): Promise<Array<{ id: string; score: number; row?: ChunkRow }>>;}interface ChunkSource { get(ids: string[], language: string | null): Promise<ChunkRow[]>;}createStaticVectorStore(rows)ranks rows by cosine in memory, among those that meetwhere. It needs no service.createHttpVectorStore({ url, fetch })POSTs{ text, vector, k, language, where }as JSON and expects{ matches: [{ id, score, row }] }.rowis optional. Without it, the id is read through theChunkSource. An id no row holds is dropped and traced.vectorisnullwhen the host did not embed, so the server can embed instead.createChunkFileSource(textOrUrl, { fetch })reads onechunks.<tag>.jsonl, given as its text or its URL.parseChunkRows(text)returns a JSONL file’s rows, for ingestion into a vector DB.parseChunksManifest(input)checks that a document is a chunk manifest. Both throw on a document missing a field the cores read.
The cores the verbs call, lookup and retrieve, take the context and the
options in one object. A host with seams of its own can call them directly.
| Area | Exports |
|---|---|
| The script surface | createRetriever, with CreateRetrieverOptions, Retriever and RetrieverFetch |
| The cores | lookup, retrieve, with RetrieverContext, EmbeddingModel, LookupOptions and RetrieveOptions |
| The seams | VectorStore, VectorStoreQuery, VectorStoreMatch, ChunkSource, WhereCondition, createStaticVectorStore, createHttpVectorStore, createChunkFileSource |
| The artifact | parseChunkRows, parseChunksManifest, ChunkRow, ChunksManifest, ProvenanceRecord and its parts |
| Results | LookupResult, LookupMatch, LookupReached, LookupEdge, PassageBundle, PassageBlock, RetrievalTrace, WithheldNode, FilteredNode |
| Errors | StaleArtifactError, ModelMismatchError |
RefusalReason gains withheld, lang-required, unknown-node and
unknown-scope.
The embedders
Section titled “The embedders”@hawkeyexl/manni/graph/embed carries createLocalEmbedder,
createMockEmbedder, EmbedderUnavailableError, DEFAULT_MODEL,
MODEL_PROFILES, profileFor and withPrefix. The local embedder needs the
optional @huggingface/transformers peer; the mock one runs offline and is
what regenerates the committed fixtures.
A mock is not coverage of the thing it stands in for. createLocalEmbedder
once shipped a hardcoded device that threw on every real call, and the mocks
certified it for a whole release. Anything that mocks an external library here
ships the real-path test in the same change.
Stability
Section titled “Stability”These are the symbols the package entry point publishes today. The CLI is the supported surface, and the library follows it. A verb’s options and report shape change when the flag surface does, under the same semantic-versioning rules. The browser runtime’s neutrality is a guarantee rather than an accident, and a bundle-purity test enforces it on every build.