Troubleshooting
This guide covers common issues and their solutions when working with TypeGraph.
Installation Issues
Section titled “Installation Issues””Cannot find module ‘@nicia-ai/typegraph’”
Section titled “”Cannot find module ‘@nicia-ai/typegraph’””Cause: Package not installed or using wrong package name.
Solution:
npm install @nicia-ai/typegraph zod drizzle-orm“better-sqlite3 compilation failed”
Section titled ““better-sqlite3 compilation failed””Cause: Native module compilation requires build tools.
Solutions:
macOS:
xcode-select --installUbuntu/Debian:
sudo apt-get install build-essential python3Windows:
npm install --global windows-build-toolsAlternative: Use sql.js for pure JavaScript SQLite (no compilation needed).
”Module not found: drizzle-orm/better-sqlite3”
Section titled “”Module not found: drizzle-orm/better-sqlite3””Cause: Drizzle ORM subpath exports require specific import syntax.
Solution: Ensure correct imports:
// Correctimport { drizzle } from "drizzle-orm/better-sqlite3";
// Incorrectimport { drizzle } from "drizzle-orm";Schema Definition Errors
Section titled “Schema Definition Errors””Node schema contains reserved property names”
Section titled “”Node schema contains reserved property names””Cause: Using reserved keys (id, kind, meta) in your Zod schema.
Solution: Rename your properties:
// Bad - 'id' is reservedconst User = defineNode("User", { schema: z.object({ id: z.string(), // Error! name: z.string(), }),});
// Good - use a different nameconst User = defineNode("User", { schema: z.object({ externalId: z.string(), name: z.string(), }),});TypeGraph automatically provides id, kind, and meta on all nodes.
”Edge type already has constraints defined”
Section titled “”Edge type already has constraints defined””Cause: Defining from/to constraints on both the edge type and graph registration.
Solution: Define constraints in one place only:
// Option 1: On the edge type (reusable across graphs)const worksAt = defineEdge("worksAt", { from: [Person], to: [Company],});
const graph = defineGraph({ edges: { worksAt: { type: worksAt }, // No from/to here },});
// Option 2: On the graph (flexible per-graph)const worksAt = defineEdge("worksAt");
const graph = defineGraph({ edges: { worksAt: { type: worksAt, from: [Person], to: [Company] }, },});Runtime Errors
Section titled “Runtime Errors”ValidationError: “Invalid input”
Section titled “ValidationError: “Invalid input””Cause: Data doesn’t match the Zod schema.
Solution: Check the error details for specific issues:
try { await store.nodes.Person.create({ name: "" });} catch (error) { if (error instanceof ValidationError) { console.log(error.details.issues); // Zod issues array }}NodeNotFoundError
Section titled “NodeNotFoundError”Cause: Attempting to read/update/delete a non-existent node.
Solution: Check if the node exists first or handle the error:
const node = await store.nodes.Person.getById(someId);if (!node) { // Handle missing node}
// Or use error handlingtry { await store.nodes.Person.update(someId, { name: "New" });} catch (error) { if (error instanceof NodeNotFoundError) { console.log(`Node ${error.details.id} not found`); }}RestrictedDeleteError
Section titled “RestrictedDeleteError”Cause: Attempting to delete a node that has edges, with onDelete: "restrict" (the default).
Solution: Either delete the edges first or use a different delete behavior:
// Option 1: Delete edges firstconst edges = await store.edges.worksAt.findFrom(person);for (const edge of edges) { await store.edges.worksAt.delete(edge.id);}await store.nodes.Person.delete(person.id);
// Option 2: Use cascade delete in schemaconst graph = defineGraph({ nodes: { Person: { type: Person, onDelete: "cascade" }, },});DisjointError
Section titled “DisjointError”Cause: Creating a node with an ID that’s already used by a disjoint type.
Solution: Ensure IDs are unique across disjoint types or don’t use explicit IDs:
// If Person and Organization are disjoint:// Bad - same ID for different typesawait store.nodes.Person.create({ name: "Alice" }, { id: "entity-1" });await store.nodes.Organization.create({ name: "Acme" }, { id: "entity-1" }); // Error!
// Good - let TypeGraph generate unique IDsawait store.nodes.Person.create({ name: "Alice" });await store.nodes.Organization.create({ name: "Acme" });Query Issues
Section titled “Query Issues””Alias ‘x’ is already in use”
Section titled “”Alias ‘x’ is already in use””Cause: Using the same alias twice in a query.
Solution: Use unique aliases:
// Badstore.query().from("Person", "p").traverse("knows", "e").to("Person", "p"); // Error! 'p' already used
// Goodstore.query().from("Person", "p1").traverse("knows", "e").to("Person", "p2");Empty results when expecting data
Section titled “Empty results when expecting data”Causes and solutions:
-
Type mismatch: Ensure you’re querying the correct node type
// Check the node type name matches exactly.from("Person", "p") // Must match defineNode("Person", ...) -
Missing includeSubClasses: When querying a superclass
.from("Content", "c", { includeSubClasses: true }) -
Strict predicate: Check your filters aren’t too restrictive
// Debug by removing filters temporarilyconst all = await store.query().from("Person", "p").select((c) => c.p).execute();console.log(all.length); // How many total?
Slow queries
Section titled “Slow queries”Solutions:
-
Use the query profiler:
import { QueryProfiler } from "@nicia-ai/typegraph/profiler";const profiler = new QueryProfiler();profiler.attachToStore(store);// Run your queries...const report = profiler.getReport();console.log(report.recommendations); -
Add indexes based on profiler recommendations:
import { defineNodeIndex } from "@nicia-ai/typegraph/indexes";const nameIndex = defineNodeIndex(Person, { fields: ["name"] }); -
Limit results:
.limit(100)// Or use pagination.paginate({ first: 20 })
Database Connection Issues
Section titled “Database Connection Issues””Database is locked” (SQLite)
Section titled “”Database is locked” (SQLite)”Cause: Multiple processes accessing the same SQLite file without WAL mode.
Solution: Enable WAL mode:
const sqlite = new Database("myapp.db");sqlite.pragma("journal_mode = WAL");Connection pool exhausted (PostgreSQL)
Section titled “Connection pool exhausted (PostgreSQL)”Cause: Too many concurrent connections.
Solution: Configure pool limits:
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // Adjust based on your needs idleTimeoutMillis: 30000,});“relation ‘typegraph_nodes’ does not exist”
Section titled ““relation ‘typegraph_nodes’ does not exist””Cause: Migration not run.
Solution: Run the migration SQL:
// PostgreSQLimport { generatePostgresMigrationSQL } from "@nicia-ai/typegraph/postgres";await pool.query(generatePostgresMigrationSQL());
// SQLiteimport { generateSqliteMigrationSQL } from "@nicia-ai/typegraph/sqlite";sqlite.exec(generateSqliteMigrationSQL());“permission denied” / cannot create relation on boot
Section titled ““permission denied” / cannot create relation on boot”Cause: createStoreWithSchema() runs DDL on every cold boot
(bootstrap, safe auto-migrations, and the contribution-marker
CREATE TABLE IF NOT EXISTS). If it runs under a least-privilege,
DML-only database role, that DDL fails with a permission error.
Solution: Run schema/DDL changes as a privileged one-time migration
step, then attach at runtime with the zero-DDL
createVerifiedStore() (or createStore()) under the least-privilege
role. See
Database roles & least privilege.
MigrationError from createVerifiedStore / assertSchemaCurrent
Section titled “MigrationError from createVerifiedStore / assertSchemaCurrent”Cause: The runtime is using a code graph whose schema is ahead of the database. The least-privilege runtime cannot migrate — by design, it fails fast so requests don’t run against a stale schema.
Solution: Run createStoreWithSchema(graph, adminBackend) under
the privileged role before promoting the new runtime build (apply any
generated migration SQL first if you manage DDL externally), then
restart the runtime. The thrown MigrationError.message includes the
diff summary and migration actions to apply.
ConfigurationError: “no schema has been initialized”
Section titled “ConfigurationError: “no schema has been initialized””Cause: A verifying attach (createVerifiedStore /
assertSchemaCurrent) ran before any privileged
createStoreWithSchema() boot — the database has no schema_versions
row (or no typegraph tables at all). The runtime deliberately refuses
to bootstrap under a least-privilege role. Note: running only the
generated migration SQL is not sufficient — it creates the tables but
does not write the schema row or contribution markers.
Solution: Run createStoreWithSchema(graph, adminBackend) once
under the privileged role. If you manage DDL externally with
drizzle-kit / generatePostgresMigrationSQL() /
generateSqliteMigrationSQL(), apply that first, then still run
createStoreWithSchema() to commit the schema row and contribution
markers. See
Database roles & least privilege.
StoreNotInitializedError on the first operation
Section titled “StoreNotInitializedError on the first operation”Cause: The store was created with createStore() (a zero-I/O attach
that never materializes runtime storage) against a database that no
createStoreWithSchema() boot has initialized — commonly the runtime
started before the privileged migration step ran, or the wrong role/
database is configured. createVerifiedStore() catches this case at
boot rather than at first hot-path operation.
Solution: Run createStoreWithSchema(graph, adminBackend) once
under the privileged role before the runtime attaches (it writes the
contribution markers that createStore / createVerifiedStore only
check), and prefer createVerifiedStore() over bare createStore() so
drift fails fast. See
Database roles & least privilege.
Semantic Search Issues
Section titled “Semantic Search Issues””Extension not found” / “vector type not available”
Section titled “”Extension not found” / “vector type not available””Cause: Vector extension not installed.
PostgreSQL:
CREATE EXTENSION IF NOT EXISTS vector;SQLite:
import * as sqliteVec from "sqlite-vec";sqliteVec.load(sqlite); // Must be called before creating backend“Dimension mismatch”
Section titled ““Dimension mismatch””Cause: Query embedding has different dimension than stored embeddings.
Solution: Use consistent embedding dimensions:
// Schema defines 1536 dimensionsconst Document = defineNode("Document", { schema: z.object({ embedding: embedding(1536), }),});
// Query embedding must also be 1536const queryEmbedding = await generateEmbedding(text);console.log(queryEmbedding.length); // Should be 1536“Inner product not supported” (SQLite)
Section titled ““Inner product not supported” (SQLite)”Cause: sqlite-vec doesn’t support inner product metric.
Solution: Use cosine or L2:
// Instead of:d.embedding.similarTo(query, 10, { metric: "inner_product" });
// Use:d.embedding.similarTo(query, 10, { metric: "cosine" });TypeScript Issues
Section titled “TypeScript Issues””Property ‘x’ does not exist on type”
Section titled “”Property ‘x’ does not exist on type””Cause: Accessing a property not defined in your schema.
Solution: Ensure the property is in your Zod schema:
const Person = defineNode("Person", { schema: z.object({ name: z.string(), email: z.string().optional(), }),});
// Now both properties are available with correct typesconst person = await store.nodes.Person.getById(id);person?.name; // stringperson?.email; // string | undefinedType inference not working in select
Section titled “Type inference not working in select”Cause: Complex generic inference limitations.
Solution: Use explicit typing or simplify:
// If inference fails, be explicit.select((ctx) => ({ name: ctx.p.name as string, company: ctx.c.name as string,}))Still Having Issues?
Section titled “Still Having Issues?”- Check the Limitations page for known constraints
- Review Architecture to understand how TypeGraph works
- Search GitHub Issues for similar problems
- Open a new issue with a minimal reproduction case