Skip to content

TypeScript

@littlebigbrain/client is a thin, dependency-free wrapper over the platform fetch. Request and response types are generated from little big brain’s API contract, so every method is fully typed.

It runs anywhere there is a global fetch: Node 18+, browsers, and edge workers. You can also pass your own fetch through the fetch option.

Source and issues: github.com/littlebigbrains/lbb-typescript (Apache-2.0).

Terminal window
npm install @littlebigbrain/client
import { LbbClient, LbbError } from "@littlebigbrain/client";
const lbb = new LbbClient({
baseUrl: "https://0abc1def--production.db.eu.littlebigbrain.com",
apiKey: process.env.LBB_API_KEY, // lbb_sk_live_… / lbb_sk_test_…
});
const graph = lbb.graph("main");

facts.importRdf posts a document in N-Triples, Turtle, N-Quads, or TriG. The graph is created on first write if it does not exist, using a fixed generic schema, so no ontology design is required to start.

const imported = await graph.facts.importRdf(turtleDocument, {
format: "turtle",
baseIri: "https://example.org/data/",
graphUri: "https://example.org/graphs/current",
blankNodeScope: "catalog-2026-07",
});
console.log(imported.triples_read, imported.committed_commit_seq);

Keep each request under the 64 MiB body cap. N-Triples and N-Quads are line-oriented, so a large file can be split on line boundaries; Turtle and TriG are document grammars, so each request must be a complete parseable document.

Omit blankNodeScope to preserve blank-node labels across chunks and retries, or reuse one scope for every chunk of a source document to isolate it from documents that reuse the same labels.

Scope once with client.graph("name"). Its facts, entities, schema, ontology, and query namespaces carry the same graph and branch. Pass an idempotencyKey so retries are safe:

await graph.facts.create(
{
triplets: [
{
source: { type: "SERVICE", name: "auth-service" },
relation: "WRITES_TO",
target: { type: "DATABASE", name: "user-db" },
confidence: 0.93,
evidence: "auth-service writes identity records to user-db",
},
],
},
{ idempotencyKey: "import-2026-06-13" },
);

For a large NDJSON dataset of entity and edge records, submitImport streams an iterable or async iterable without assembling one large string. The explicit key binds retries to the uploaded content:

async function* records() {
for await (const row of sourceRows()) {
yield { type: "DOCUMENT", name: row.title, key: row.id, properties: row };
}
}
const accepted = await lbb.submitImport(records(), {
idempotencyKey: "sharepoint:run-2026-07-29",
});
const status = await lbb.waitForImportJob(accepted.job_id);
if (status.state !== "succeeded") throw new Error(status.failure?.message);
console.log(status.committed_commit_seq, status.publication_job);

The SDK requires durable_import_jobs_v1 from GET /version and never silently falls back to the synchronous import endpoint. getImportJob and cancelImportJob support reconnect and cancellation. Every acknowledged commit is strongly queryable; base reconciliation remains asynchronous and optional. Empty iterables fail locally before a POST.

const published = await lbb.readSnapshot();
console.log({
servedAt: published.snapshot.served_at_seq,
commitLag: published.query_lag_commits,
generationLag: published.generation_lag,
});

Writes make one exact RDF delta visible in the branch head and advance one server-managed reconciliation fence. Strong SPARQL is immediately queryable from the immutable base plus that bounded suffix; clients do not coordinate indexing. waitForPublished(commitSeq) is optional when a workflow wants the base itself to cover a commit, publicationStatus() exposes maintenance progress, and readSnapshot() diagnoses the head-owned RDF lineage.

For several RDF documents, graph.facts.importRdfMany(...) sends intermediate imports with reconciliation deferred and triggers it once after the final write. Its finalSequence may be passed to graph.waitForPublished(...) when base compaction, rather than strong queryability, is the desired gate.

sparqlRows(...) runs a SPARQL 1.1 text query (SELECT or ASK) and returns parsed results, so you avoid a manual JSON.parse of a results string and the zipping of head.vars with binding values:

const { vars, rows } = await lbb.sparqlRows({
query: `SELECT ?service ?db WHERE {
?service <https://littlebigbrain.com/r/writes_to> ?db
} LIMIT 10`,
entailment: "rdfs", // optional: apply the RDFS rules at query time
});
for (const row of rows) console.log(row.service, "->", row.db);
const exists = (await lbb.sparqlRows({ query: "ASK { ?s ?p ?o }" })).boolean;

sparqlRows returns { vars, boolean, bindings, rows }. rows flattens the bindings to { variable: lexicalValue }, bindings keeps the raw typed term objects, and boolean is the ASK answer (null for SELECT). sparqlText(...) returns the unparsed envelope, and the standalone parseSparqlResults(response) helper parses it.

sparql(body) is the JSON form of a SELECT/ASK: a basic graph pattern with typed filters, GROUP BY (entity keys, scalar property keys, or calendar date buckets), aggregates, HAVING, and ORDER BY. Use it when building a query from user input is safer than assembling query text.

await lbb.sparql({
patterns: [{ subject: { var: "service" }, predicate: "WRITES_TO", object: { var: "db" } }],
select: ["service", "db"],
limit: 50,
});

entities.filterByAttributes(...) builds that structured body for you when you already have relation patterns and need typed attribute predicates:

await lbb.entities.filterByAttributes({
patterns: [{ subject: { var: "service" }, predicate: "WRITES_TO", object: { var: "db" } }],
where: [
{ field: "slo", op: "ge", value: 0.99 },
{ var: "db", field: "tier", value: "prod" },
],
select: ["service"],
});
const ontology = await graph.ontology.view({ counts: true });
const report = await graph.ontology.conformance(); // published SHACL report
console.log(report.conforms, report.result_count, report.validated_at_seq);
const bundle = await graph.schema.view(); // active shapes + mode
await graph.schema.publish({
desired_mode: "warn",
shapes: { source: shapesTurtle, format: "turtle" },
});

There is no request-time runShacl() or ShaclQueryRequest. Publishing changes the active schema; conformance reads the immutable report produced for that schema and never accepts inline shapes.

commitDryRun(...) runs the same validation a real commit would and reports the would-be effect without writing anything, which is the cheapest way to catch a bad statement before it is committed.

A branch is a copy-on-write snapshot. Fork one, load into it, validate it, and merge it back as a single commit:

// Scope a client to the new branch, then fork it from `main`.
const review = lbb.withScope({ branch: "review" });
await review.createBranch({ from_branch: "main" });
// …load and validate on `review`…
// Merge back: the client scoped to the fork parent replays the child onto it.
await lbb.mergeBranch({ from_branch: "review" });
await review.deleteBranch({ confirm: "review" });

mergeBranch replays the child’s post-fork commits onto the scoped branch, its fork parent, as one commit. Event ids are preserved, SHACL validation runs first, and conflicts come back as supersedure_race. Graph deletion is whole-graph: lbb.graph("main").delete({ confirm: "main" }).

await lbb.currentState({ entity: { entity_type: "DATABASE", name: "user-db" } });
await lbb.history({ entity: { entity_type: "SERVICE", name: "auth-service" } });
await lbb.why({ /* which observations support this statement */ });

To pin a SPARQL query to a past commit, use the native /sparql endpoint’s ?as_of_commit_seq= and ?as_of_valid_time= parameters. See the HTTP API.

Reads default to eventual: one immutable F3 base supplies the explicit watermark in snapshot.served_at_seq. Pass consistency: "strong" to apply the head’s exact bounded delta suffix, or set a client-level default with new LbbClient({ …, defaultConsistency: "strong" }). No request reads mutable truth or builds an index.

To read back exactly what you wrote, use the minIndexedSeq floor. Take the committed sequence a write returned (surfaced as commitSeq) and read strongly with minIndexedSeq set to it. The accepted commit already carries its F3 delta, so no publication polling loop is needed:

const { commitSeq } = await lbb.commit({ triplets });
const rows = await lbb.sparqlRows(
{ query: "SELECT ?s ?p ?o WHERE { ?s ?p ?o }" },
{ consistency: "strong", minIndexedSeq: commitSeq },
);

minIndexedSeq and consistency are accepted on query.sparql, query.structured, sparqlRows, sparqlText, and summary.

Regular methods return parsed JSON. Use rawRequest(...) when you need requestId, version, or headers. Every method throws LbbError on a non-2xx response:

try {
await lbb.graph("main").facts.create({ triplets: [/* … */] });
} catch (err) {
if (err instanceof LbbError) {
console.error(err.status, err.code, err.message, err.param, err.requestId, err.docUrl);
}
}

Requests have a 120-second per-attempt timeout. Safe reads and idempotency-keyed writes retry 429, 5xx, and network failures with full-jitter exponential backoff, bounded by a retry budget (retryBudgetMs, default 60s) rather than a fixed attempt count. maxRetries (default 6) is a secondary cap. Retries honor Retry-After, and an error the server marks non-retryable is raised immediately. Namespaced methods accept the same final request options: timeoutMs, maxRetries, retryBudgetMs, signal, headers, and idempotencyKey for mutations.

Configure onRequest, onResponse, and onRetry (an absorbed-retry callback) for body-free lifecycle events. rawRequest(...) reports attempts, retryCount, and elapsedMs alongside the request id and API version.

Area Methods
Load RDF graph("main").facts.importRdf
Write graph("main").facts.create, commit, commitDryRun, graph("main").retract
Bulk import submitImport, getImportJob, waitForImportJob, cancelImportJob, graph("main").facts.import
Graph lifecycle createGraph, forkGraph (durable create-only copy), reload (declarative full-state replace, dryRun previews the delta), deleteGraph, deleteBranch
Branches withScope({ branch }), createBranch, mergeBranch, deleteBranch
Query sparqlRows, sparqlText, sparql (structured), query.structured, query.sparql, query.analytics, entities.filterByAttributes
Ontology ontology.view, ontology.conformance, ontology.define, ontology.evolve
Schema & shapes schema.view, schema.publish
Temporal / lineage currentState, history, why
Inspection entities.sample, entities.get, entities.detail, readSnapshot, schemaSummary, status, metadata, summary

Common shapes are exported directly as Entity, GraphSummary, and CommitRequest. Every generated shape remains available as Schemas["TypeName"], and the raw components, paths, and operations are exported too.

A stack also serves the native SPARQL 1.1 Protocol at /sparql (GET ?query=, POST form or application/sparql-query body, Accept-negotiated JSON, XML, CSV, and TSV for SELECT/ASK and Turtle or N-Triples for CONSTRUCT/DESCRIBE) for standard clients like YASGUI and Protégé. sparqlRows is the in-process SELECT/ASK equivalent that returns parsed JSON. To export a snapshot as an RDF document, call GET /v1/graph/export/rdf over HTTP.