@nicia-ai/typegraph
v0.73.1
Published
TypeScript-first embedded knowledge graph library with ontological reasoning
Downloads
8,782
Maintainers
Readme
@nicia-ai/typegraph
Type-driven embedded knowledge graph for TypeScript.
- Docs: typegraph.dev
- Repo: github.com/nicia-ai/typegraph
Installation
npm install @nicia-ai/typegraph zod drizzle-orm better-sqlite3Quick Start
import { z } from "zod";
import { defineEdge, defineGraph, defineNode } from "@nicia-ai/typegraph";
import { createLocalSqliteStore } from "@nicia-ai/typegraph/sqlite/local";
const Person = defineNode("Person", { schema: z.object({ name: z.string() }) });
const knows = defineEdge("knows");
const graph = defineGraph({
id: "social",
nodes: { Person: { type: Person } },
edges: { knows: { type: knows, from: [Person], to: [Person] } },
});
const store = await createLocalSqliteStore(graph);
const alice = await store.nodes.Person.create({ name: "Alice" });
const bob = await store.nodes.Person.create({ name: "Bob" });
await store.edges.knows.create(alice, bob);
await store.close();Use store.query().from(["Person", "Company"], "entity") for one result stream
across several node kinds. Queries expose compatible shared fields, full-node
results retain their concrete kind, and cursor pagination includes both kind
and ID so overlapping IDs remain distinct. See Query sources.
For latency-sensitive reads, store.batchOnce() combines independent fluent
queries and set-oriented reads into one statement. Its callback receives a
batch-scoped builder with neighbors(), countNeighbors(), and subgraph()
methods. Direct store.subgraph() and batch-scoped read.subgraph() return the
same result shape and semantics; the direct form retains backend-tuned hydration
while the batch-scoped form guarantees one statement.
Transaction contexts expose the same query(), neighbors(),
countNeighbors(), subgraph(), and batchOnce() reads, all bound to the open
transaction so read-modify-write paths see their uncommitted changes.
store.neighbors() can order by edge metadata or adjacent-node properties,
while subgraph({ edgeWindows }) applies per-kind direction, ordering, and
limits during traversal. See
Schemas and Stores for examples and
constraints.
Use the explicit /adapters/drizzle/... entrypoints when your application owns
the database connection or needs adapter-native transaction handles.
Schema-only packages can import the graph DSL and schema-derived types from the
Drizzle-free @nicia-ai/typegraph/core entrypoint. Custom backend, dialect, and
search-strategy authors can import the complete Drizzle-free contract vocabulary
from @nicia-ai/typegraph/backend.
Optional Peer: drizzle-orm
drizzle-orm is an optional peer dependency. @nicia-ai/typegraph/sqlite/local
and @nicia-ai/typegraph/postgres/pglite load it only when their factory is
called and refuse with a typed ConfigurationError
(MISSING_PEER_DEPENDENCY) naming the package and the install command
(npm install drizzle-orm) when it is absent. The six explicit
/adapters/drizzle/... entrypoints expose Drizzle-native backends, connections,
or schema builders and load drizzle-orm when the module is evaluated. Importing
one without the peer installed therefore surfaces the raw module-resolution
error, which names the same package.
See the repo README for more.
Examples: github.com/nicia-ai/typegraph/tree/main/packages/typegraph/examples
Graph Merge
TypeGraph ships semantic graph merge as a dedicated subpath:
import {
applyDurableMergePlan,
applyMergePlan,
applyMergePlanInTransaction,
branch,
branchDurable,
destroyDurableBranch,
merge,
planMerge,
reopenDurableBranch,
} from "@nicia-ai/typegraph/graph-merge";branch() creates isolated working copies over caller-provided backends, stamped
with the base graph's schema and content version. merge() reconciles those
branches back into a target graph with deterministic entity resolution, conflict
reporting, edge repointing, optional ontology type reconciliation, and provenance
reporting.
Use it when several agents, importers, reviewers, or local workers edit graph state independently and the application needs one canonical result instead of an append-only pile of duplicates. The merge pipeline can:
- resolve duplicate entities by exact unique constraints, blocking keys, fulltext/custom similarity, or vector/hybrid similarity;
- preserve branch-specific context by repointing edges to canonical nodes;
- surface property and delete/modify conflicts (for nodes and edges) in a
MergeReport, three-way merged against base so disjoint edits compose; - explain every entity collapse with deterministic decisive edges, complete candidate-source attribution, and the actual score/threshold for scored matches, with bounded accepted/rejected diagnostics available on request;
- expose report-only provenance, with optional sidecar persistence you can query.
merge() is a snapshot merge (all branches forked from the current base);
mergeIncremental() additively folds a new source into a target that has already
advanced, re-discovering committed entities instead of duplicating them — the
primitive for continuous ingestion.
For approval workflows, planMerge() and planMergeIncremental() produce a
deterministically ordered, JSON-serializable MergePlanArtifact without
mutating the target. Store or review that artifact, then pass it to
applyMergePlan(). Apply validates its digest and checks its durable revision,
graph, schema, and origin fence inside the write transaction; it never re-runs
candidate generation, scoring, embeddings, or policy callbacks. Plans require a
target with revisionTracking: true or history: true. They may contain
sensitive application data, and their digest provides integrity/identity—not a
signature, authentication, or authorization. merge() and mergeIncremental()
remain one-call compatibility APIs over the same resolution and write owners.
When a reviewed merge and application records must commit together,
applyMergePlanInTransaction() applies the plan through a transaction context
created by the same target Store inside withRecordedTransaction(). Call it
before other target-graph writes, then add graph writes or SQL and await the
caller-owned commit. It throws on failure and leaves rollback and whole-
transaction retry to the caller. This path refuses persisted provenance, while
report-only provenance remains available.
Long-running workflows can use branchDurable() to allocate a persistent
working copy, serialize its descriptor, close the current connection, and
reattach from a later process with reopenDurableBranch(). A
DurableWorkingCopyStrategy owns the database's branch lifecycle and declares
the cross-client fencing or exclusive writer lease that keeps planning sound.
After review, applyDurableMergePlan() may use a strategy's native database
merge only when it proves the host diff is exactly the approved TypeGraph plan;
otherwise it applies the complete portable plan. Use destroyDurableBranch()
for explicit teardown. The guide documents the requirements a branch-native
database strategy must satisfy.
It lives in the core package because the primitive is defined over TypeGraph stores, schemas, indexes, backends, and ontology semantics rather than as a separate product surface.
Docs: Graph Merge
Examples: FHIR Graph Merge · Incremental Merge
Bitemporal History
TypeGraph supports valid-time reads and opt-in recorded-time reconstruction:
- Valid time (
validFrom/validTo,store.asOf(T)): when a fact is true in the world. - Recorded time (
history: true,store.asOfRecorded(T)): when the TypeGraph store wrote that fact down.
Together they answer what TypeGraph captured as true at a recorded commit instant for writes that go through TypeGraph's collections. Use this for audit trails, agent decision replay, policy effective dating, and incident forensics.
const store = createStore(graph, backend, { history: true });
await store.nodes.Decision.create({ answer: "approve source A" });
const decisionTime = await store.recordedNow();
if (decisionTime === undefined) throw new Error("expected recorded history");
const replay = store.asOfRecorded(decisionTime);
const answer = await replay.nodes.Decision.getById(decisionId);Docs: Temporal queries
Examples: Bitemporal Time Travel · Agent Decision Replay · Breach Forensics
Provenance and Retraction
TypeGraph ships source-lineage retraction as a dedicated subpath:
import { createRetractionCapability } from "@nicia-ai/typegraph/provenance";Map ordinary graph kinds onto source, justification, fact, premise, and
derivation roles. Retraction flips a source's boolean flag, recomputes
well-founded support, keeps facts with alternate support current, and makes
unsupported facts non-current. Source roles can cover multiple node kinds, and
terminal fact kinds do not have to be valid premises. Because the capability
requires history: true, recorded-time reads can replay what the graph believed
before and after the transition.
Docs: Provenance and Retraction
Example: Provenance Retraction
Performance Smoke Check
The perf harness lives in @nicia-ai/typegraph-benchmarks; these commands delegate to it.
Run a deterministic SQLite perf sanity suite with guardrails:
pnpm --filter @nicia-ai/typegraph test:perfRun the same guardrailed suite against PostgreSQL (requires POSTGRES_URL):
POSTGRES_URL=postgresql://typegraph:[email protected]:5432/typegraph_test \
pnpm --filter @nicia-ai/typegraph test:perf:postgresFor report-only mode (no pass/fail guardrails):
pnpm --filter @nicia-ai/typegraph bench:perf