🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@inferagraph/sql-datasource

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@inferagraph/sql-datasource - npm Package Compare versions

Comparing version
0.1.1
to
0.1.2
+29
-9
dist/index.cjs

@@ -137,16 +137,36 @@ "use strict";

}
async getNeighbors(nodeId, _depth = 1) {
async getNeighbors(nodeId, depth = 1) {
this.ensureConnected();
const edgeRows = await this.db(this.tables.edges).where("source_id", nodeId).orWhere("target_id", nodeId);
const neighborIds = /* @__PURE__ */ new Set();
neighborIds.add(nodeId);
for (const edge of edgeRows) {
neighborIds.add(edge.source_id);
neighborIds.add(edge.target_id);
const effectiveDepth = Math.max(1, Math.floor(depth));
const visitedNodeIds = /* @__PURE__ */ new Set([nodeId]);
const collectedEdgeRows = /* @__PURE__ */ new Map();
let frontier = [nodeId];
for (let level = 0; level < effectiveDepth && frontier.length > 0; level++) {
const nextFrontier = [];
for (const currentId of frontier) {
const edgeRows = await this.fetchEdgeRowsForNode(currentId);
for (const edge of edgeRows) {
const edgeId = String(edge.id);
if (!collectedEdgeRows.has(edgeId)) {
collectedEdgeRows.set(edgeId, edge);
}
const sourceId = String(edge.source_id);
const targetId = String(edge.target_id);
const otherId = sourceId === currentId ? targetId : sourceId;
if (!visitedNodeIds.has(otherId)) {
visitedNodeIds.add(otherId);
nextFrontier.push(otherId);
}
}
}
frontier = nextFrontier;
}
const nodeRows = await this.db(this.tables.nodes).whereIn("id", [...neighborIds]);
const nodeRows = await this.db(this.tables.nodes).whereIn("id", [...visitedNodeIds]);
const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));
const edges = edgeRows.map((row) => this.rowToEdgeData(row));
const edges = [...collectedEdgeRows.values()].map((row) => this.rowToEdgeData(row));
return { nodes, edges };
}
async fetchEdgeRowsForNode(nodeId) {
return this.db(this.tables.edges).where("source_id", nodeId).orWhere("target_id", nodeId);
}
async findPath(fromId, toId) {

@@ -153,0 +173,0 @@ this.ensureConnected();

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts","../src/SqlDatasource.ts","../src/schema.ts"],"sourcesContent":["export { SqlDatasource } from './SqlDatasource.js';\nexport { createSchema } from './schema.js';\nexport type { SqlDatasourceConfig, TableNames } from './types.js';\n","import { Datasource } from '@inferagraph/core';\nimport type {\n DataAdapterConfig,\n GraphData,\n NodeId,\n NodeData,\n ContentData,\n PaginationOptions,\n PaginatedResult,\n DataFilter,\n} from '@inferagraph/core';\nimport knex, { type Knex } from 'knex';\nimport type { SqlDatasourceConfig, TableNames } from './types.js';\nimport { createSchema } from './schema.js';\n\nexport class SqlDatasource extends Datasource {\n readonly name = 'sql';\n private db: Knex | null = null;\n private config: SqlDatasourceConfig;\n private tables: TableNames;\n\n constructor(config: SqlDatasourceConfig) {\n super();\n this.config = config;\n this.tables = {\n nodes: config.tables?.nodes ?? 'nodes',\n edges: config.tables?.edges ?? 'edges',\n properties: config.tables?.properties ?? 'node_properties',\n content: config.tables?.content ?? 'content',\n };\n }\n\n async connect(): Promise<void> {\n this.db = knex({\n client: this.config.dialect,\n connection: this.config.connection,\n useNullAsDefault: true,\n });\n\n if (this.config.autoMigrate) {\n await createSchema(this.db, this.tables);\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.db) {\n await this.db.destroy();\n this.db = null;\n }\n }\n\n isConnected(): boolean {\n return this.db !== null;\n }\n\n async getInitialView(config?: DataAdapterConfig): Promise<GraphData> {\n this.ensureConnected();\n const limit = (config?.limit as number) ?? 100;\n\n const rows = await this.db!(this.tables.nodes).limit(limit);\n const nodes = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n\n const nodeIds = nodes.map((n) => n.id);\n if (nodeIds.length === 0) return { nodes: [], edges: [] };\n\n const edgeRows = await this.db!(this.tables.edges)\n .whereIn('source_id', nodeIds)\n .whereIn('target_id', nodeIds);\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async getNode(id: NodeId): Promise<NodeData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.nodes).where('id', id).first();\n if (!row) return undefined;\n return this.rowToNodeData(row);\n }\n\n async getNeighbors(nodeId: NodeId, _depth: number = 1): Promise<GraphData> {\n this.ensureConnected();\n\n // Get edges connected to this node\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', nodeId)\n .orWhere('target_id', nodeId);\n\n // Collect neighbor IDs\n const neighborIds = new Set<string>();\n neighborIds.add(nodeId);\n for (const edge of edgeRows) {\n neighborIds.add(edge.source_id);\n neighborIds.add(edge.target_id);\n }\n\n // Fetch all nodes\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', [...neighborIds]);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async findPath(fromId: NodeId, toId: NodeId): Promise<GraphData> {\n this.ensureConnected();\n\n // Application-level BFS\n const visited = new Set<string>([fromId]);\n const parent = new Map<string, { nodeId: string; edge: Record<string, unknown> }>();\n let frontier = [fromId];\n let found = false;\n const maxDepth = 20;\n let depth = 0;\n\n while (frontier.length > 0 && !found && depth < maxDepth) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', currentId)\n .orWhere('target_id', currentId);\n\n for (const edge of edgeRows) {\n const neighborId = edge.source_id === currentId ? edge.target_id : edge.source_id;\n if (!visited.has(neighborId)) {\n visited.add(neighborId);\n parent.set(neighborId, { nodeId: currentId, edge });\n nextFrontier.push(neighborId);\n if (neighborId === toId) {\n found = true;\n break;\n }\n }\n }\n if (found) break;\n }\n\n frontier = nextFrontier;\n depth++;\n }\n\n if (!found) return { nodes: [], edges: [] };\n\n // Reconstruct path\n const pathIds: string[] = [toId];\n const pathEdges: Record<string, unknown>[] = [];\n let current = toId;\n while (parent.has(current)) {\n const p = parent.get(current)!;\n pathIds.push(p.nodeId);\n pathEdges.push(p.edge);\n current = p.nodeId;\n }\n\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', pathIds);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n\n return {\n nodes,\n edges: pathEdges.map((row) => this.rowToEdgeData(row)),\n };\n }\n\n async search(\n query: string,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n const rows = await this.db!(this.tables.nodes).where('name', 'like', `%${query}%`);\n\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async filter(\n filter: DataFilter,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n let queryBuilder = this.db!(this.tables.nodes);\n\n if (filter.types?.length) {\n queryBuilder = queryBuilder.whereIn('type', filter.types);\n }\n if (filter.search) {\n queryBuilder = queryBuilder.where('name', 'like', `%${filter.search}%`);\n }\n if (filter.attributes) {\n for (const [key, value] of Object.entries(filter.attributes)) {\n // Check if it's a base column or an EAV property\n if (['name', 'type'].includes(key)) {\n queryBuilder = queryBuilder.where(key, value as string);\n } else {\n // Sub-query into properties table\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', key)\n .where('value', String(value));\n });\n }\n }\n }\n if (filter.tags?.length) {\n // Tags are stored in properties table as key='tags'\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', 'tags')\n .whereIn('value', filter.tags!);\n });\n }\n\n const rows = await queryBuilder;\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async getContent(nodeId: NodeId): Promise<ContentData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.content).where('node_id', nodeId).first();\n if (!row) return undefined;\n\n return {\n nodeId,\n content: row.content,\n contentType: row.content_type ?? 'markdown',\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // --- Private Helpers ---\n\n private ensureConnected(): void {\n if (!this.db) {\n throw new Error('SqlDatasource is not connected. Call connect() first.');\n }\n }\n\n private async rowToNodeData(row: Record<string, unknown>): Promise<NodeData> {\n const attributes: Record<string, unknown> = {\n name: row.name,\n type: row.type,\n };\n\n // Fetch EAV properties\n if (this.db) {\n const props = await this.db(this.tables.properties).where('node_id', row.id);\n\n for (const prop of props) {\n const value = this.deserializeValue(prop.value, prop.value_type);\n // For repeated keys, collect into arrays\n if (attributes[prop.key] !== undefined) {\n if (Array.isArray(attributes[prop.key])) {\n (attributes[prop.key] as unknown[]).push(value);\n } else {\n attributes[prop.key] = [attributes[prop.key], value];\n }\n } else {\n attributes[prop.key] = value;\n }\n }\n }\n\n return { id: String(row.id), attributes };\n }\n\n private rowToEdgeData(row: Record<string, unknown>) {\n return {\n id: String(row.id),\n sourceId: String(row.source_id),\n targetId: String(row.target_id),\n attributes: {\n type: row.type as string,\n weight: row.weight as number,\n },\n };\n }\n\n private deserializeValue(value: string, valueType: string): unknown {\n switch (valueType) {\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n return JSON.parse(value);\n default:\n return value;\n }\n }\n\n private paginate(\n items: NodeData[],\n pagination?: PaginationOptions,\n ): PaginatedResult<NodeData> {\n const total = items.length;\n if (!pagination) return { items, total, hasMore: false };\n const { offset, limit } = pagination;\n const sliced = items.slice(offset, offset + limit);\n return { items: sliced, total, hasMore: offset + limit < total };\n }\n}\n","import type { Knex } from 'knex';\nimport type { TableNames } from './types.js';\n\nexport async function createSchema(knex: Knex, tables: TableNames): Promise<void> {\n // Create nodes table\n if (!(await knex.schema.hasTable(tables.nodes))) {\n await knex.schema.createTable(tables.nodes, (table) => {\n table.string('id', 255).primary();\n table.string('name', 500).notNullable();\n table.string('type', 100).notNullable();\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n\n // Create edges table\n if (!(await knex.schema.hasTable(tables.edges))) {\n await knex.schema.createTable(tables.edges, (table) => {\n table.string('id', 255).primary();\n table\n .string('source_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table\n .string('target_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('type', 100).notNullable();\n table.float('weight').defaultTo(1.0);\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.index(['source_id']);\n table.index(['target_id']);\n });\n }\n\n // Create node_properties table (EAV pattern)\n if (!(await knex.schema.hasTable(tables.properties))) {\n await knex.schema.createTable(tables.properties, (table) => {\n table\n .string('node_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('key', 255).notNullable();\n table.text('value');\n table.string('value_type', 50).defaultTo('string');\n table.primary(['node_id', 'key']);\n });\n }\n\n // Create content table\n if (!(await knex.schema.hasTable(tables.content))) {\n await knex.schema.createTable(tables.content, (table) => {\n table\n .string('node_id', 255)\n .primary()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.text('content').notNullable();\n table.string('content_type', 50).defaultTo('markdown');\n table.text('metadata'); // JSON string\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n}\n"],"mappings":"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","names":["knex","knex"]}
{"version":3,"sources":["../src/index.ts","../src/SqlDatasource.ts","../src/schema.ts"],"sourcesContent":["export { SqlDatasource } from './SqlDatasource.js';\nexport { createSchema } from './schema.js';\nexport type { SqlDatasourceConfig, TableNames } from './types.js';\n","import { Datasource } from '@inferagraph/core';\nimport type {\n DataAdapterConfig,\n GraphData,\n NodeId,\n NodeData,\n ContentData,\n PaginationOptions,\n PaginatedResult,\n DataFilter,\n} from '@inferagraph/core';\nimport knex, { type Knex } from 'knex';\nimport type { SqlDatasourceConfig, TableNames } from './types.js';\nimport { createSchema } from './schema.js';\n\nexport class SqlDatasource extends Datasource {\n readonly name = 'sql';\n private db: Knex | null = null;\n private config: SqlDatasourceConfig;\n private tables: TableNames;\n\n constructor(config: SqlDatasourceConfig) {\n super();\n this.config = config;\n this.tables = {\n nodes: config.tables?.nodes ?? 'nodes',\n edges: config.tables?.edges ?? 'edges',\n properties: config.tables?.properties ?? 'node_properties',\n content: config.tables?.content ?? 'content',\n };\n }\n\n async connect(): Promise<void> {\n this.db = knex({\n client: this.config.dialect,\n connection: this.config.connection,\n useNullAsDefault: true,\n });\n\n if (this.config.autoMigrate) {\n await createSchema(this.db, this.tables);\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.db) {\n await this.db.destroy();\n this.db = null;\n }\n }\n\n isConnected(): boolean {\n return this.db !== null;\n }\n\n async getInitialView(config?: DataAdapterConfig): Promise<GraphData> {\n this.ensureConnected();\n const limit = (config?.limit as number) ?? 100;\n\n const rows = await this.db!(this.tables.nodes).limit(limit);\n const nodes = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n\n const nodeIds = nodes.map((n) => n.id);\n if (nodeIds.length === 0) return { nodes: [], edges: [] };\n\n const edgeRows = await this.db!(this.tables.edges)\n .whereIn('source_id', nodeIds)\n .whereIn('target_id', nodeIds);\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async getNode(id: NodeId): Promise<NodeData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.nodes).where('id', id).first();\n if (!row) return undefined;\n return this.rowToNodeData(row);\n }\n\n async getNeighbors(nodeId: NodeId, depth: number = 1): Promise<GraphData> {\n this.ensureConnected();\n\n // SQL has no native graph traversal, so depth>1 is implemented as\n // application-level BFS mirroring the existing findPath pattern: iterate\n // 1-hop fan-out from each newly discovered frontier node up to `depth`\n // levels. Dedupe edges by id and nodes by id.\n const effectiveDepth = Math.max(1, Math.floor(depth));\n\n const visitedNodeIds = new Set<string>([nodeId]);\n const collectedEdgeRows = new Map<string, Record<string, unknown>>();\n let frontier: string[] = [nodeId];\n\n for (let level = 0; level < effectiveDepth && frontier.length > 0; level++) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.fetchEdgeRowsForNode(currentId);\n for (const edge of edgeRows) {\n const edgeId = String(edge.id);\n if (!collectedEdgeRows.has(edgeId)) {\n collectedEdgeRows.set(edgeId, edge);\n }\n const sourceId = String(edge.source_id);\n const targetId = String(edge.target_id);\n const otherId = sourceId === currentId ? targetId : sourceId;\n if (!visitedNodeIds.has(otherId)) {\n visitedNodeIds.add(otherId);\n nextFrontier.push(otherId);\n }\n }\n }\n\n frontier = nextFrontier;\n }\n\n // Fetch all visited node rows in a single query\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', [...visitedNodeIds]);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n const edges = [...collectedEdgeRows.values()].map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n private async fetchEdgeRowsForNode(nodeId: NodeId): Promise<Record<string, unknown>[]> {\n return this.db!(this.tables.edges)\n .where('source_id', nodeId)\n .orWhere('target_id', nodeId);\n }\n\n async findPath(fromId: NodeId, toId: NodeId): Promise<GraphData> {\n this.ensureConnected();\n\n // Application-level BFS\n const visited = new Set<string>([fromId]);\n const parent = new Map<string, { nodeId: string; edge: Record<string, unknown> }>();\n let frontier = [fromId];\n let found = false;\n const maxDepth = 20;\n let depth = 0;\n\n while (frontier.length > 0 && !found && depth < maxDepth) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', currentId)\n .orWhere('target_id', currentId);\n\n for (const edge of edgeRows) {\n const neighborId = edge.source_id === currentId ? edge.target_id : edge.source_id;\n if (!visited.has(neighborId)) {\n visited.add(neighborId);\n parent.set(neighborId, { nodeId: currentId, edge });\n nextFrontier.push(neighborId);\n if (neighborId === toId) {\n found = true;\n break;\n }\n }\n }\n if (found) break;\n }\n\n frontier = nextFrontier;\n depth++;\n }\n\n if (!found) return { nodes: [], edges: [] };\n\n // Reconstruct path\n const pathIds: string[] = [toId];\n const pathEdges: Record<string, unknown>[] = [];\n let current = toId;\n while (parent.has(current)) {\n const p = parent.get(current)!;\n pathIds.push(p.nodeId);\n pathEdges.push(p.edge);\n current = p.nodeId;\n }\n\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', pathIds);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n\n return {\n nodes,\n edges: pathEdges.map((row) => this.rowToEdgeData(row)),\n };\n }\n\n async search(\n query: string,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n const rows = await this.db!(this.tables.nodes).where('name', 'like', `%${query}%`);\n\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async filter(\n filter: DataFilter,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n let queryBuilder = this.db!(this.tables.nodes);\n\n if (filter.types?.length) {\n queryBuilder = queryBuilder.whereIn('type', filter.types);\n }\n if (filter.search) {\n queryBuilder = queryBuilder.where('name', 'like', `%${filter.search}%`);\n }\n if (filter.attributes) {\n for (const [key, value] of Object.entries(filter.attributes)) {\n // Check if it's a base column or an EAV property\n if (['name', 'type'].includes(key)) {\n queryBuilder = queryBuilder.where(key, value as string);\n } else {\n // Sub-query into properties table\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', key)\n .where('value', String(value));\n });\n }\n }\n }\n if (filter.tags?.length) {\n // Tags are stored in properties table as key='tags'\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', 'tags')\n .whereIn('value', filter.tags!);\n });\n }\n\n const rows = await queryBuilder;\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async getContent(nodeId: NodeId): Promise<ContentData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.content).where('node_id', nodeId).first();\n if (!row) return undefined;\n\n return {\n nodeId,\n content: row.content,\n contentType: row.content_type ?? 'markdown',\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // --- Private Helpers ---\n\n private ensureConnected(): void {\n if (!this.db) {\n throw new Error('SqlDatasource is not connected. Call connect() first.');\n }\n }\n\n private async rowToNodeData(row: Record<string, unknown>): Promise<NodeData> {\n const attributes: Record<string, unknown> = {\n name: row.name,\n type: row.type,\n };\n\n // Fetch EAV properties\n if (this.db) {\n const props = await this.db(this.tables.properties).where('node_id', row.id);\n\n for (const prop of props) {\n const value = this.deserializeValue(prop.value, prop.value_type);\n // For repeated keys, collect into arrays\n if (attributes[prop.key] !== undefined) {\n if (Array.isArray(attributes[prop.key])) {\n (attributes[prop.key] as unknown[]).push(value);\n } else {\n attributes[prop.key] = [attributes[prop.key], value];\n }\n } else {\n attributes[prop.key] = value;\n }\n }\n }\n\n return { id: String(row.id), attributes };\n }\n\n private rowToEdgeData(row: Record<string, unknown>) {\n return {\n id: String(row.id),\n sourceId: String(row.source_id),\n targetId: String(row.target_id),\n attributes: {\n type: row.type as string,\n weight: row.weight as number,\n },\n };\n }\n\n private deserializeValue(value: string, valueType: string): unknown {\n switch (valueType) {\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n return JSON.parse(value);\n default:\n return value;\n }\n }\n\n private paginate(\n items: NodeData[],\n pagination?: PaginationOptions,\n ): PaginatedResult<NodeData> {\n const total = items.length;\n if (!pagination) return { items, total, hasMore: false };\n const { offset, limit } = pagination;\n const sliced = items.slice(offset, offset + limit);\n return { items: sliced, total, hasMore: offset + limit < total };\n }\n}\n","import type { Knex } from 'knex';\nimport type { TableNames } from './types.js';\n\nexport async function createSchema(knex: Knex, tables: TableNames): Promise<void> {\n // Create nodes table\n if (!(await knex.schema.hasTable(tables.nodes))) {\n await knex.schema.createTable(tables.nodes, (table) => {\n table.string('id', 255).primary();\n table.string('name', 500).notNullable();\n table.string('type', 100).notNullable();\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n\n // Create edges table\n if (!(await knex.schema.hasTable(tables.edges))) {\n await knex.schema.createTable(tables.edges, (table) => {\n table.string('id', 255).primary();\n table\n .string('source_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table\n .string('target_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('type', 100).notNullable();\n table.float('weight').defaultTo(1.0);\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.index(['source_id']);\n table.index(['target_id']);\n });\n }\n\n // Create node_properties table (EAV pattern)\n if (!(await knex.schema.hasTable(tables.properties))) {\n await knex.schema.createTable(tables.properties, (table) => {\n table\n .string('node_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('key', 255).notNullable();\n table.text('value');\n table.string('value_type', 50).defaultTo('string');\n table.primary(['node_id', 'key']);\n });\n }\n\n // Create content table\n if (!(await knex.schema.hasTable(tables.content))) {\n await knex.schema.createTable(tables.content, (table) => {\n table\n .string('node_id', 255)\n .primary()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.text('content').notNullable();\n table.string('content_type', 50).defaultTo('markdown');\n table.text('metadata'); // JSON string\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA2B;AAW3B,kBAAgC;;;ACRhC,eAAsB,aAAaA,OAAY,QAAmC;AAEhF,MAAI,CAAE,MAAMA,MAAK,OAAO,SAAS,OAAO,KAAK,GAAI;AAC/C,UAAMA,MAAK,OAAO,YAAY,OAAO,OAAO,CAAC,UAAU;AACrD,YAAM,OAAO,MAAM,GAAG,EAAE,QAAQ;AAChC,YAAM,OAAO,QAAQ,GAAG,EAAE,YAAY;AACtC,YAAM,OAAO,QAAQ,GAAG,EAAE,YAAY;AACtC,YAAM,UAAU,YAAY,EAAE,UAAUA,MAAK,GAAG,IAAI,CAAC;AACrD,YAAM,UAAU,YAAY,EAAE,UAAUA,MAAK,GAAG,IAAI,CAAC;AAAA,IACvD,CAAC;AAAA,EACH;AAGA,MAAI,CAAE,MAAMA,MAAK,OAAO,SAAS,OAAO,KAAK,GAAI;AAC/C,UAAMA,MAAK,OAAO,YAAY,OAAO,OAAO,CAAC,UAAU;AACrD,YAAM,OAAO,MAAM,GAAG,EAAE,QAAQ;AAChC,YACG,OAAO,aAAa,GAAG,EACvB,YAAY,EACZ,WAAW,IAAI,EACf,QAAQ,OAAO,KAAK,EACpB,SAAS,SAAS;AACrB,YACG,OAAO,aAAa,GAAG,EACvB,YAAY,EACZ,WAAW,IAAI,EACf,QAAQ,OAAO,KAAK,EACpB,SAAS,SAAS;AACrB,YAAM,OAAO,QAAQ,GAAG,EAAE,YAAY;AACtC,YAAM,MAAM,QAAQ,EAAE,UAAU,CAAG;AACnC,YAAM,UAAU,YAAY,EAAE,UAAUA,MAAK,GAAG,IAAI,CAAC;AACrD,YAAM,MAAM,CAAC,WAAW,CAAC;AACzB,YAAM,MAAM,CAAC,WAAW,CAAC;AAAA,IAC3B,CAAC;AAAA,EACH;AAGA,MAAI,CAAE,MAAMA,MAAK,OAAO,SAAS,OAAO,UAAU,GAAI;AACpD,UAAMA,MAAK,OAAO,YAAY,OAAO,YAAY,CAAC,UAAU;AAC1D,YACG,OAAO,WAAW,GAAG,EACrB,YAAY,EACZ,WAAW,IAAI,EACf,QAAQ,OAAO,KAAK,EACpB,SAAS,SAAS;AACrB,YAAM,OAAO,OAAO,GAAG,EAAE,YAAY;AACrC,YAAM,KAAK,OAAO;AAClB,YAAM,OAAO,cAAc,EAAE,EAAE,UAAU,QAAQ;AACjD,YAAM,QAAQ,CAAC,WAAW,KAAK,CAAC;AAAA,IAClC,CAAC;AAAA,EACH;AAGA,MAAI,CAAE,MAAMA,MAAK,OAAO,SAAS,OAAO,OAAO,GAAI;AACjD,UAAMA,MAAK,OAAO,YAAY,OAAO,SAAS,CAAC,UAAU;AACvD,YACG,OAAO,WAAW,GAAG,EACrB,QAAQ,EACR,WAAW,IAAI,EACf,QAAQ,OAAO,KAAK,EACpB,SAAS,SAAS;AACrB,YAAM,KAAK,SAAS,EAAE,YAAY;AAClC,YAAM,OAAO,gBAAgB,EAAE,EAAE,UAAU,UAAU;AACrD,YAAM,KAAK,UAAU;AACrB,YAAM,UAAU,YAAY,EAAE,UAAUA,MAAK,GAAG,IAAI,CAAC;AAAA,IACvD,CAAC;AAAA,EACH;AACF;;;ADvDO,IAAM,gBAAN,cAA4B,uBAAW;AAAA,EACnC,OAAO;AAAA,EACR,KAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EAER,YAAY,QAA6B;AACvC,UAAM;AACN,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,MACZ,OAAO,OAAO,QAAQ,SAAS;AAAA,MAC/B,OAAO,OAAO,QAAQ,SAAS;AAAA,MAC/B,YAAY,OAAO,QAAQ,cAAc;AAAA,MACzC,SAAS,OAAO,QAAQ,WAAW;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,SAAK,YAAAC,SAAK;AAAA,MACb,QAAQ,KAAK,OAAO;AAAA,MACpB,YAAY,KAAK,OAAO;AAAA,MACxB,kBAAkB;AAAA,IACpB,CAAC;AAED,QAAI,KAAK,OAAO,aAAa;AAC3B,YAAM,aAAa,KAAK,IAAI,KAAK,MAAM;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,KAAK,IAAI;AACX,YAAM,KAAK,GAAG,QAAQ;AACtB,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,MAAM,eAAe,QAAgD;AACnE,SAAK,gBAAgB;AACrB,UAAM,QAAS,QAAQ,SAAoB;AAE3C,UAAM,OAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAAE,MAAM,KAAK;AAC1D,UAAM,QAAQ,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,CAAC;AAE1E,UAAM,UAAU,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE;AACrC,QAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;AAExD,UAAM,WAAW,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAC9C,QAAQ,aAAa,OAAO,EAC5B,QAAQ,aAAa,OAAO;AAC/B,UAAM,QAAQ,SAAS,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC;AAE3D,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AAAA,EAEA,MAAM,QAAQ,IAA2C;AACvD,SAAK,gBAAgB;AAErB,UAAM,MAAM,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAAE,MAAM,MAAM,EAAE,EAAE,MAAM;AACpE,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,cAAc,GAAG;AAAA,EAC/B;AAAA,EAEA,MAAM,aAAa,QAAgB,QAAgB,GAAuB;AACxE,SAAK,gBAAgB;AAMrB,UAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AAEpD,UAAM,iBAAiB,oBAAI,IAAY,CAAC,MAAM,CAAC;AAC/C,UAAM,oBAAoB,oBAAI,IAAqC;AACnE,QAAI,WAAqB,CAAC,MAAM;AAEhC,aAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS,SAAS,GAAG,SAAS;AAC1E,YAAM,eAAyB,CAAC;AAEhC,iBAAW,aAAa,UAAU;AAChC,cAAM,WAAW,MAAM,KAAK,qBAAqB,SAAS;AAC1D,mBAAW,QAAQ,UAAU;AAC3B,gBAAM,SAAS,OAAO,KAAK,EAAE;AAC7B,cAAI,CAAC,kBAAkB,IAAI,MAAM,GAAG;AAClC,8BAAkB,IAAI,QAAQ,IAAI;AAAA,UACpC;AACA,gBAAM,WAAW,OAAO,KAAK,SAAS;AACtC,gBAAM,WAAW,OAAO,KAAK,SAAS;AACtC,gBAAM,UAAU,aAAa,YAAY,WAAW;AACpD,cAAI,CAAC,eAAe,IAAI,OAAO,GAAG;AAChC,2BAAe,IAAI,OAAO;AAC1B,yBAAa,KAAK,OAAO;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAEA,iBAAW;AAAA,IACb;AAGA,UAAM,WAAW,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAAE,QAAQ,MAAM,CAAC,GAAG,cAAc,CAAC;AACpF,UAAM,QAAQ,MAAM,QAAQ,IAAI,SAAS,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,CAAC;AAC9E,UAAM,QAAQ,CAAC,GAAG,kBAAkB,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC;AAElF,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AAAA,EAEA,MAAc,qBAAqB,QAAoD;AACrF,WAAO,KAAK,GAAI,KAAK,OAAO,KAAK,EAC9B,MAAM,aAAa,MAAM,EACzB,QAAQ,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,SAAS,QAAgB,MAAkC;AAC/D,SAAK,gBAAgB;AAGrB,UAAM,UAAU,oBAAI,IAAY,CAAC,MAAM,CAAC;AACxC,UAAM,SAAS,oBAAI,IAA+D;AAClF,QAAI,WAAW,CAAC,MAAM;AACtB,QAAI,QAAQ;AACZ,UAAM,WAAW;AACjB,QAAI,QAAQ;AAEZ,WAAO,SAAS,SAAS,KAAK,CAAC,SAAS,QAAQ,UAAU;AACxD,YAAM,eAAyB,CAAC;AAEhC,iBAAW,aAAa,UAAU;AAChC,cAAM,WAAW,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAC9C,MAAM,aAAa,SAAS,EAC5B,QAAQ,aAAa,SAAS;AAEjC,mBAAW,QAAQ,UAAU;AAC3B,gBAAM,aAAa,KAAK,cAAc,YAAY,KAAK,YAAY,KAAK;AACxE,cAAI,CAAC,QAAQ,IAAI,UAAU,GAAG;AAC5B,oBAAQ,IAAI,UAAU;AACtB,mBAAO,IAAI,YAAY,EAAE,QAAQ,WAAW,KAAK,CAAC;AAClD,yBAAa,KAAK,UAAU;AAC5B,gBAAI,eAAe,MAAM;AACvB,sBAAQ;AACR;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA,YAAI,MAAO;AAAA,MACb;AAEA,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,CAAC,MAAO,QAAO,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;AAG1C,UAAM,UAAoB,CAAC,IAAI;AAC/B,UAAM,YAAuC,CAAC;AAC9C,QAAI,UAAU;AACd,WAAO,OAAO,IAAI,OAAO,GAAG;AAC1B,YAAM,IAAI,OAAO,IAAI,OAAO;AAC5B,cAAQ,KAAK,EAAE,MAAM;AACrB,gBAAU,KAAK,EAAE,IAAI;AACrB,gBAAU,EAAE;AAAA,IACd;AAEA,UAAM,WAAW,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAAE,QAAQ,MAAM,OAAO;AACxE,UAAM,QAAQ,MAAM,QAAQ,IAAI,SAAS,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,CAAC;AAE9E,WAAO;AAAA,MACL;AAAA,MACA,OAAO,UAAU,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC;AAAA,IACvD;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,YACoC;AACpC,SAAK,gBAAgB;AAErB,UAAM,OAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAK,EAAE,MAAM,QAAQ,QAAQ,IAAI,KAAK,GAAG;AAEjF,UAAM,WAAW,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,CAAC;AAC7E,WAAO,KAAK,SAAS,UAAU,UAAU;AAAA,EAC3C;AAAA,EAEA,MAAM,OACJ,QACA,YACoC;AACpC,SAAK,gBAAgB;AAErB,QAAI,eAAe,KAAK,GAAI,KAAK,OAAO,KAAK;AAE7C,QAAI,OAAO,OAAO,QAAQ;AACxB,qBAAe,aAAa,QAAQ,QAAQ,OAAO,KAAK;AAAA,IAC1D;AACA,QAAI,OAAO,QAAQ;AACjB,qBAAe,aAAa,MAAM,QAAQ,QAAQ,IAAI,OAAO,MAAM,GAAG;AAAA,IACxE;AACA,QAAI,OAAO,YAAY;AACrB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAE5D,YAAI,CAAC,QAAQ,MAAM,EAAE,SAAS,GAAG,GAAG;AAClC,yBAAe,aAAa,MAAM,KAAK,KAAe;AAAA,QACxD,OAAO;AAEL,yBAAe,aAAa,QAAQ,MAAM,CAAC,QAAQ;AACjD,gBACG,OAAO,SAAS,EAChB,KAAK,KAAK,OAAO,UAAU,EAC3B,MAAM,OAAO,GAAG,EAChB,MAAM,SAAS,OAAO,KAAK,CAAC;AAAA,UACjC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAO,MAAM,QAAQ;AAEvB,qBAAe,aAAa,QAAQ,MAAM,CAAC,QAAQ;AACjD,YACG,OAAO,SAAS,EAChB,KAAK,KAAK,OAAO,UAAU,EAC3B,MAAM,OAAO,MAAM,EACnB,QAAQ,SAAS,OAAO,IAAK;AAAA,MAClC,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,MAAM;AACnB,UAAM,WAAW,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,CAAC;AAC7E,WAAO,KAAK,SAAS,UAAU,UAAU;AAAA,EAC3C;AAAA,EAEA,MAAM,WAAW,QAAkD;AACjE,SAAK,gBAAgB;AAErB,UAAM,MAAM,MAAM,KAAK,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,WAAW,MAAM,EAAE,MAAM;AAC/E,QAAI,CAAC,IAAK,QAAO;AAEjB,WAAO;AAAA,MACL;AAAA,MACA,SAAS,IAAI;AAAA,MACb,aAAa,IAAI,gBAAgB;AAAA,MACjC,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,IACtD;AAAA,EACF;AAAA;AAAA,EAIQ,kBAAwB;AAC9B,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,KAAiD;AAC3E,UAAM,aAAsC;AAAA,MAC1C,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,IACZ;AAGA,QAAI,KAAK,IAAI;AACX,YAAM,QAAQ,MAAM,KAAK,GAAG,KAAK,OAAO,UAAU,EAAE,MAAM,WAAW,IAAI,EAAE;AAE3E,iBAAW,QAAQ,OAAO;AACxB,cAAM,QAAQ,KAAK,iBAAiB,KAAK,OAAO,KAAK,UAAU;AAE/D,YAAI,WAAW,KAAK,GAAG,MAAM,QAAW;AACtC,cAAI,MAAM,QAAQ,WAAW,KAAK,GAAG,CAAC,GAAG;AACvC,YAAC,WAAW,KAAK,GAAG,EAAgB,KAAK,KAAK;AAAA,UAChD,OAAO;AACL,uBAAW,KAAK,GAAG,IAAI,CAAC,WAAW,KAAK,GAAG,GAAG,KAAK;AAAA,UACrD;AAAA,QACF,OAAO;AACL,qBAAW,KAAK,GAAG,IAAI;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,IAAI,OAAO,IAAI,EAAE,GAAG,WAAW;AAAA,EAC1C;AAAA,EAEQ,cAAc,KAA8B;AAClD,WAAO;AAAA,MACL,IAAI,OAAO,IAAI,EAAE;AAAA,MACjB,UAAU,OAAO,IAAI,SAAS;AAAA,MAC9B,UAAU,OAAO,IAAI,SAAS;AAAA,MAC9B,YAAY;AAAA,QACV,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAe,WAA4B;AAClE,YAAQ,WAAW;AAAA,MACjB,KAAK;AACH,eAAO,OAAO,KAAK;AAAA,MACrB,KAAK;AACH,eAAO,UAAU;AAAA,MACnB,KAAK;AACH,eAAO,KAAK,MAAM,KAAK;AAAA,MACzB;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,SACN,OACA,YAC2B;AAC3B,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,WAAY,QAAO,EAAE,OAAO,OAAO,SAAS,MAAM;AACvD,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,UAAM,SAAS,MAAM,MAAM,QAAQ,SAAS,KAAK;AACjD,WAAO,EAAE,OAAO,QAAQ,OAAO,SAAS,SAAS,QAAQ,MAAM;AAAA,EACjE;AACF;","names":["knex","knex"]}

@@ -33,3 +33,4 @@ import { Datasource, DataAdapterConfig, GraphData, NodeId, NodeData, PaginationOptions, PaginatedResult, DataFilter, ContentData } from '@inferagraph/core';

getNode(id: NodeId): Promise<NodeData | undefined>;
getNeighbors(nodeId: NodeId, _depth?: number): Promise<GraphData>;
getNeighbors(nodeId: NodeId, depth?: number): Promise<GraphData>;
private fetchEdgeRowsForNode;
findPath(fromId: NodeId, toId: NodeId): Promise<GraphData>;

@@ -36,0 +37,0 @@ search(query: string, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>;

@@ -33,3 +33,4 @@ import { Datasource, DataAdapterConfig, GraphData, NodeId, NodeData, PaginationOptions, PaginatedResult, DataFilter, ContentData } from '@inferagraph/core';

getNode(id: NodeId): Promise<NodeData | undefined>;
getNeighbors(nodeId: NodeId, _depth?: number): Promise<GraphData>;
getNeighbors(nodeId: NodeId, depth?: number): Promise<GraphData>;
private fetchEdgeRowsForNode;
findPath(fromId: NodeId, toId: NodeId): Promise<GraphData>;

@@ -36,0 +37,0 @@ search(query: string, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>;

@@ -100,16 +100,36 @@ // src/SqlDatasource.ts

}
async getNeighbors(nodeId, _depth = 1) {
async getNeighbors(nodeId, depth = 1) {
this.ensureConnected();
const edgeRows = await this.db(this.tables.edges).where("source_id", nodeId).orWhere("target_id", nodeId);
const neighborIds = /* @__PURE__ */ new Set();
neighborIds.add(nodeId);
for (const edge of edgeRows) {
neighborIds.add(edge.source_id);
neighborIds.add(edge.target_id);
const effectiveDepth = Math.max(1, Math.floor(depth));
const visitedNodeIds = /* @__PURE__ */ new Set([nodeId]);
const collectedEdgeRows = /* @__PURE__ */ new Map();
let frontier = [nodeId];
for (let level = 0; level < effectiveDepth && frontier.length > 0; level++) {
const nextFrontier = [];
for (const currentId of frontier) {
const edgeRows = await this.fetchEdgeRowsForNode(currentId);
for (const edge of edgeRows) {
const edgeId = String(edge.id);
if (!collectedEdgeRows.has(edgeId)) {
collectedEdgeRows.set(edgeId, edge);
}
const sourceId = String(edge.source_id);
const targetId = String(edge.target_id);
const otherId = sourceId === currentId ? targetId : sourceId;
if (!visitedNodeIds.has(otherId)) {
visitedNodeIds.add(otherId);
nextFrontier.push(otherId);
}
}
}
frontier = nextFrontier;
}
const nodeRows = await this.db(this.tables.nodes).whereIn("id", [...neighborIds]);
const nodeRows = await this.db(this.tables.nodes).whereIn("id", [...visitedNodeIds]);
const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));
const edges = edgeRows.map((row) => this.rowToEdgeData(row));
const edges = [...collectedEdgeRows.values()].map((row) => this.rowToEdgeData(row));
return { nodes, edges };
}
async fetchEdgeRowsForNode(nodeId) {
return this.db(this.tables.edges).where("source_id", nodeId).orWhere("target_id", nodeId);
}
async findPath(fromId, toId) {

@@ -116,0 +136,0 @@ this.ensureConnected();

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/SqlDatasource.ts","../src/schema.ts"],"sourcesContent":["import { Datasource } from '@inferagraph/core';\nimport type {\n DataAdapterConfig,\n GraphData,\n NodeId,\n NodeData,\n ContentData,\n PaginationOptions,\n PaginatedResult,\n DataFilter,\n} from '@inferagraph/core';\nimport knex, { type Knex } from 'knex';\nimport type { SqlDatasourceConfig, TableNames } from './types.js';\nimport { createSchema } from './schema.js';\n\nexport class SqlDatasource extends Datasource {\n readonly name = 'sql';\n private db: Knex | null = null;\n private config: SqlDatasourceConfig;\n private tables: TableNames;\n\n constructor(config: SqlDatasourceConfig) {\n super();\n this.config = config;\n this.tables = {\n nodes: config.tables?.nodes ?? 'nodes',\n edges: config.tables?.edges ?? 'edges',\n properties: config.tables?.properties ?? 'node_properties',\n content: config.tables?.content ?? 'content',\n };\n }\n\n async connect(): Promise<void> {\n this.db = knex({\n client: this.config.dialect,\n connection: this.config.connection,\n useNullAsDefault: true,\n });\n\n if (this.config.autoMigrate) {\n await createSchema(this.db, this.tables);\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.db) {\n await this.db.destroy();\n this.db = null;\n }\n }\n\n isConnected(): boolean {\n return this.db !== null;\n }\n\n async getInitialView(config?: DataAdapterConfig): Promise<GraphData> {\n this.ensureConnected();\n const limit = (config?.limit as number) ?? 100;\n\n const rows = await this.db!(this.tables.nodes).limit(limit);\n const nodes = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n\n const nodeIds = nodes.map((n) => n.id);\n if (nodeIds.length === 0) return { nodes: [], edges: [] };\n\n const edgeRows = await this.db!(this.tables.edges)\n .whereIn('source_id', nodeIds)\n .whereIn('target_id', nodeIds);\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async getNode(id: NodeId): Promise<NodeData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.nodes).where('id', id).first();\n if (!row) return undefined;\n return this.rowToNodeData(row);\n }\n\n async getNeighbors(nodeId: NodeId, _depth: number = 1): Promise<GraphData> {\n this.ensureConnected();\n\n // Get edges connected to this node\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', nodeId)\n .orWhere('target_id', nodeId);\n\n // Collect neighbor IDs\n const neighborIds = new Set<string>();\n neighborIds.add(nodeId);\n for (const edge of edgeRows) {\n neighborIds.add(edge.source_id);\n neighborIds.add(edge.target_id);\n }\n\n // Fetch all nodes\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', [...neighborIds]);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async findPath(fromId: NodeId, toId: NodeId): Promise<GraphData> {\n this.ensureConnected();\n\n // Application-level BFS\n const visited = new Set<string>([fromId]);\n const parent = new Map<string, { nodeId: string; edge: Record<string, unknown> }>();\n let frontier = [fromId];\n let found = false;\n const maxDepth = 20;\n let depth = 0;\n\n while (frontier.length > 0 && !found && depth < maxDepth) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', currentId)\n .orWhere('target_id', currentId);\n\n for (const edge of edgeRows) {\n const neighborId = edge.source_id === currentId ? edge.target_id : edge.source_id;\n if (!visited.has(neighborId)) {\n visited.add(neighborId);\n parent.set(neighborId, { nodeId: currentId, edge });\n nextFrontier.push(neighborId);\n if (neighborId === toId) {\n found = true;\n break;\n }\n }\n }\n if (found) break;\n }\n\n frontier = nextFrontier;\n depth++;\n }\n\n if (!found) return { nodes: [], edges: [] };\n\n // Reconstruct path\n const pathIds: string[] = [toId];\n const pathEdges: Record<string, unknown>[] = [];\n let current = toId;\n while (parent.has(current)) {\n const p = parent.get(current)!;\n pathIds.push(p.nodeId);\n pathEdges.push(p.edge);\n current = p.nodeId;\n }\n\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', pathIds);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n\n return {\n nodes,\n edges: pathEdges.map((row) => this.rowToEdgeData(row)),\n };\n }\n\n async search(\n query: string,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n const rows = await this.db!(this.tables.nodes).where('name', 'like', `%${query}%`);\n\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async filter(\n filter: DataFilter,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n let queryBuilder = this.db!(this.tables.nodes);\n\n if (filter.types?.length) {\n queryBuilder = queryBuilder.whereIn('type', filter.types);\n }\n if (filter.search) {\n queryBuilder = queryBuilder.where('name', 'like', `%${filter.search}%`);\n }\n if (filter.attributes) {\n for (const [key, value] of Object.entries(filter.attributes)) {\n // Check if it's a base column or an EAV property\n if (['name', 'type'].includes(key)) {\n queryBuilder = queryBuilder.where(key, value as string);\n } else {\n // Sub-query into properties table\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', key)\n .where('value', String(value));\n });\n }\n }\n }\n if (filter.tags?.length) {\n // Tags are stored in properties table as key='tags'\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', 'tags')\n .whereIn('value', filter.tags!);\n });\n }\n\n const rows = await queryBuilder;\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async getContent(nodeId: NodeId): Promise<ContentData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.content).where('node_id', nodeId).first();\n if (!row) return undefined;\n\n return {\n nodeId,\n content: row.content,\n contentType: row.content_type ?? 'markdown',\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // --- Private Helpers ---\n\n private ensureConnected(): void {\n if (!this.db) {\n throw new Error('SqlDatasource is not connected. Call connect() first.');\n }\n }\n\n private async rowToNodeData(row: Record<string, unknown>): Promise<NodeData> {\n const attributes: Record<string, unknown> = {\n name: row.name,\n type: row.type,\n };\n\n // Fetch EAV properties\n if (this.db) {\n const props = await this.db(this.tables.properties).where('node_id', row.id);\n\n for (const prop of props) {\n const value = this.deserializeValue(prop.value, prop.value_type);\n // For repeated keys, collect into arrays\n if (attributes[prop.key] !== undefined) {\n if (Array.isArray(attributes[prop.key])) {\n (attributes[prop.key] as unknown[]).push(value);\n } else {\n attributes[prop.key] = [attributes[prop.key], value];\n }\n } else {\n attributes[prop.key] = value;\n }\n }\n }\n\n return { id: String(row.id), attributes };\n }\n\n private rowToEdgeData(row: Record<string, unknown>) {\n return {\n id: String(row.id),\n sourceId: String(row.source_id),\n targetId: String(row.target_id),\n attributes: {\n type: row.type as string,\n weight: row.weight as number,\n },\n };\n }\n\n private deserializeValue(value: string, valueType: string): unknown {\n switch (valueType) {\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n return JSON.parse(value);\n default:\n return value;\n }\n }\n\n private paginate(\n items: NodeData[],\n pagination?: PaginationOptions,\n ): PaginatedResult<NodeData> {\n const total = items.length;\n if (!pagination) return { items, total, hasMore: false };\n const { offset, limit } = pagination;\n const sliced = items.slice(offset, offset + limit);\n return { items: sliced, total, hasMore: offset + limit < total };\n }\n}\n","import type { Knex } from 'knex';\nimport type { TableNames } from './types.js';\n\nexport async function createSchema(knex: Knex, tables: TableNames): Promise<void> {\n // Create nodes table\n if (!(await knex.schema.hasTable(tables.nodes))) {\n await knex.schema.createTable(tables.nodes, (table) => {\n table.string('id', 255).primary();\n table.string('name', 500).notNullable();\n table.string('type', 100).notNullable();\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n\n // Create edges table\n if (!(await knex.schema.hasTable(tables.edges))) {\n await knex.schema.createTable(tables.edges, (table) => {\n table.string('id', 255).primary();\n table\n .string('source_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table\n .string('target_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('type', 100).notNullable();\n table.float('weight').defaultTo(1.0);\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.index(['source_id']);\n table.index(['target_id']);\n });\n }\n\n // Create node_properties table (EAV pattern)\n if (!(await knex.schema.hasTable(tables.properties))) {\n await knex.schema.createTable(tables.properties, (table) => {\n table\n .string('node_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('key', 255).notNullable();\n table.text('value');\n table.string('value_type', 50).defaultTo('string');\n table.primary(['node_id', 'key']);\n });\n }\n\n // Create content table\n if (!(await knex.schema.hasTable(tables.content))) {\n await knex.schema.createTable(tables.content, (table) => {\n table\n .string('node_id', 255)\n .primary()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.text('content').notNullable();\n table.string('content_type', 50).defaultTo('markdown');\n table.text('metadata'); // JSON string\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n}\n"],"mappings":"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","names":["knex"]}
{"version":3,"sources":["../src/SqlDatasource.ts","../src/schema.ts"],"sourcesContent":["import { Datasource } from '@inferagraph/core';\nimport type {\n DataAdapterConfig,\n GraphData,\n NodeId,\n NodeData,\n ContentData,\n PaginationOptions,\n PaginatedResult,\n DataFilter,\n} from '@inferagraph/core';\nimport knex, { type Knex } from 'knex';\nimport type { SqlDatasourceConfig, TableNames } from './types.js';\nimport { createSchema } from './schema.js';\n\nexport class SqlDatasource extends Datasource {\n readonly name = 'sql';\n private db: Knex | null = null;\n private config: SqlDatasourceConfig;\n private tables: TableNames;\n\n constructor(config: SqlDatasourceConfig) {\n super();\n this.config = config;\n this.tables = {\n nodes: config.tables?.nodes ?? 'nodes',\n edges: config.tables?.edges ?? 'edges',\n properties: config.tables?.properties ?? 'node_properties',\n content: config.tables?.content ?? 'content',\n };\n }\n\n async connect(): Promise<void> {\n this.db = knex({\n client: this.config.dialect,\n connection: this.config.connection,\n useNullAsDefault: true,\n });\n\n if (this.config.autoMigrate) {\n await createSchema(this.db, this.tables);\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.db) {\n await this.db.destroy();\n this.db = null;\n }\n }\n\n isConnected(): boolean {\n return this.db !== null;\n }\n\n async getInitialView(config?: DataAdapterConfig): Promise<GraphData> {\n this.ensureConnected();\n const limit = (config?.limit as number) ?? 100;\n\n const rows = await this.db!(this.tables.nodes).limit(limit);\n const nodes = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n\n const nodeIds = nodes.map((n) => n.id);\n if (nodeIds.length === 0) return { nodes: [], edges: [] };\n\n const edgeRows = await this.db!(this.tables.edges)\n .whereIn('source_id', nodeIds)\n .whereIn('target_id', nodeIds);\n const edges = edgeRows.map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n async getNode(id: NodeId): Promise<NodeData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.nodes).where('id', id).first();\n if (!row) return undefined;\n return this.rowToNodeData(row);\n }\n\n async getNeighbors(nodeId: NodeId, depth: number = 1): Promise<GraphData> {\n this.ensureConnected();\n\n // SQL has no native graph traversal, so depth>1 is implemented as\n // application-level BFS mirroring the existing findPath pattern: iterate\n // 1-hop fan-out from each newly discovered frontier node up to `depth`\n // levels. Dedupe edges by id and nodes by id.\n const effectiveDepth = Math.max(1, Math.floor(depth));\n\n const visitedNodeIds = new Set<string>([nodeId]);\n const collectedEdgeRows = new Map<string, Record<string, unknown>>();\n let frontier: string[] = [nodeId];\n\n for (let level = 0; level < effectiveDepth && frontier.length > 0; level++) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.fetchEdgeRowsForNode(currentId);\n for (const edge of edgeRows) {\n const edgeId = String(edge.id);\n if (!collectedEdgeRows.has(edgeId)) {\n collectedEdgeRows.set(edgeId, edge);\n }\n const sourceId = String(edge.source_id);\n const targetId = String(edge.target_id);\n const otherId = sourceId === currentId ? targetId : sourceId;\n if (!visitedNodeIds.has(otherId)) {\n visitedNodeIds.add(otherId);\n nextFrontier.push(otherId);\n }\n }\n }\n\n frontier = nextFrontier;\n }\n\n // Fetch all visited node rows in a single query\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', [...visitedNodeIds]);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n const edges = [...collectedEdgeRows.values()].map((row) => this.rowToEdgeData(row));\n\n return { nodes, edges };\n }\n\n private async fetchEdgeRowsForNode(nodeId: NodeId): Promise<Record<string, unknown>[]> {\n return this.db!(this.tables.edges)\n .where('source_id', nodeId)\n .orWhere('target_id', nodeId);\n }\n\n async findPath(fromId: NodeId, toId: NodeId): Promise<GraphData> {\n this.ensureConnected();\n\n // Application-level BFS\n const visited = new Set<string>([fromId]);\n const parent = new Map<string, { nodeId: string; edge: Record<string, unknown> }>();\n let frontier = [fromId];\n let found = false;\n const maxDepth = 20;\n let depth = 0;\n\n while (frontier.length > 0 && !found && depth < maxDepth) {\n const nextFrontier: string[] = [];\n\n for (const currentId of frontier) {\n const edgeRows = await this.db!(this.tables.edges)\n .where('source_id', currentId)\n .orWhere('target_id', currentId);\n\n for (const edge of edgeRows) {\n const neighborId = edge.source_id === currentId ? edge.target_id : edge.source_id;\n if (!visited.has(neighborId)) {\n visited.add(neighborId);\n parent.set(neighborId, { nodeId: currentId, edge });\n nextFrontier.push(neighborId);\n if (neighborId === toId) {\n found = true;\n break;\n }\n }\n }\n if (found) break;\n }\n\n frontier = nextFrontier;\n depth++;\n }\n\n if (!found) return { nodes: [], edges: [] };\n\n // Reconstruct path\n const pathIds: string[] = [toId];\n const pathEdges: Record<string, unknown>[] = [];\n let current = toId;\n while (parent.has(current)) {\n const p = parent.get(current)!;\n pathIds.push(p.nodeId);\n pathEdges.push(p.edge);\n current = p.nodeId;\n }\n\n const nodeRows = await this.db!(this.tables.nodes).whereIn('id', pathIds);\n const nodes = await Promise.all(nodeRows.map((row) => this.rowToNodeData(row)));\n\n return {\n nodes,\n edges: pathEdges.map((row) => this.rowToEdgeData(row)),\n };\n }\n\n async search(\n query: string,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n const rows = await this.db!(this.tables.nodes).where('name', 'like', `%${query}%`);\n\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async filter(\n filter: DataFilter,\n pagination?: PaginationOptions,\n ): Promise<PaginatedResult<NodeData>> {\n this.ensureConnected();\n\n let queryBuilder = this.db!(this.tables.nodes);\n\n if (filter.types?.length) {\n queryBuilder = queryBuilder.whereIn('type', filter.types);\n }\n if (filter.search) {\n queryBuilder = queryBuilder.where('name', 'like', `%${filter.search}%`);\n }\n if (filter.attributes) {\n for (const [key, value] of Object.entries(filter.attributes)) {\n // Check if it's a base column or an EAV property\n if (['name', 'type'].includes(key)) {\n queryBuilder = queryBuilder.where(key, value as string);\n } else {\n // Sub-query into properties table\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', key)\n .where('value', String(value));\n });\n }\n }\n }\n if (filter.tags?.length) {\n // Tags are stored in properties table as key='tags'\n queryBuilder = queryBuilder.whereIn('id', (sub) => {\n sub\n .select('node_id')\n .from(this.tables.properties)\n .where('key', 'tags')\n .whereIn('value', filter.tags!);\n });\n }\n\n const rows = await queryBuilder;\n const allItems = await Promise.all(rows.map((row) => this.rowToNodeData(row)));\n return this.paginate(allItems, pagination);\n }\n\n async getContent(nodeId: NodeId): Promise<ContentData | undefined> {\n this.ensureConnected();\n\n const row = await this.db!(this.tables.content).where('node_id', nodeId).first();\n if (!row) return undefined;\n\n return {\n nodeId,\n content: row.content,\n contentType: row.content_type ?? 'markdown',\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n };\n }\n\n // --- Private Helpers ---\n\n private ensureConnected(): void {\n if (!this.db) {\n throw new Error('SqlDatasource is not connected. Call connect() first.');\n }\n }\n\n private async rowToNodeData(row: Record<string, unknown>): Promise<NodeData> {\n const attributes: Record<string, unknown> = {\n name: row.name,\n type: row.type,\n };\n\n // Fetch EAV properties\n if (this.db) {\n const props = await this.db(this.tables.properties).where('node_id', row.id);\n\n for (const prop of props) {\n const value = this.deserializeValue(prop.value, prop.value_type);\n // For repeated keys, collect into arrays\n if (attributes[prop.key] !== undefined) {\n if (Array.isArray(attributes[prop.key])) {\n (attributes[prop.key] as unknown[]).push(value);\n } else {\n attributes[prop.key] = [attributes[prop.key], value];\n }\n } else {\n attributes[prop.key] = value;\n }\n }\n }\n\n return { id: String(row.id), attributes };\n }\n\n private rowToEdgeData(row: Record<string, unknown>) {\n return {\n id: String(row.id),\n sourceId: String(row.source_id),\n targetId: String(row.target_id),\n attributes: {\n type: row.type as string,\n weight: row.weight as number,\n },\n };\n }\n\n private deserializeValue(value: string, valueType: string): unknown {\n switch (valueType) {\n case 'number':\n return Number(value);\n case 'boolean':\n return value === 'true';\n case 'json':\n return JSON.parse(value);\n default:\n return value;\n }\n }\n\n private paginate(\n items: NodeData[],\n pagination?: PaginationOptions,\n ): PaginatedResult<NodeData> {\n const total = items.length;\n if (!pagination) return { items, total, hasMore: false };\n const { offset, limit } = pagination;\n const sliced = items.slice(offset, offset + limit);\n return { items: sliced, total, hasMore: offset + limit < total };\n }\n}\n","import type { Knex } from 'knex';\nimport type { TableNames } from './types.js';\n\nexport async function createSchema(knex: Knex, tables: TableNames): Promise<void> {\n // Create nodes table\n if (!(await knex.schema.hasTable(tables.nodes))) {\n await knex.schema.createTable(tables.nodes, (table) => {\n table.string('id', 255).primary();\n table.string('name', 500).notNullable();\n table.string('type', 100).notNullable();\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n\n // Create edges table\n if (!(await knex.schema.hasTable(tables.edges))) {\n await knex.schema.createTable(tables.edges, (table) => {\n table.string('id', 255).primary();\n table\n .string('source_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table\n .string('target_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('type', 100).notNullable();\n table.float('weight').defaultTo(1.0);\n table.timestamp('created_at').defaultTo(knex.fn.now());\n table.index(['source_id']);\n table.index(['target_id']);\n });\n }\n\n // Create node_properties table (EAV pattern)\n if (!(await knex.schema.hasTable(tables.properties))) {\n await knex.schema.createTable(tables.properties, (table) => {\n table\n .string('node_id', 255)\n .notNullable()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.string('key', 255).notNullable();\n table.text('value');\n table.string('value_type', 50).defaultTo('string');\n table.primary(['node_id', 'key']);\n });\n }\n\n // Create content table\n if (!(await knex.schema.hasTable(tables.content))) {\n await knex.schema.createTable(tables.content, (table) => {\n table\n .string('node_id', 255)\n .primary()\n .references('id')\n .inTable(tables.nodes)\n .onDelete('CASCADE');\n table.text('content').notNullable();\n table.string('content_type', 50).defaultTo('markdown');\n table.text('metadata'); // JSON string\n table.timestamp('updated_at').defaultTo(knex.fn.now());\n });\n }\n}\n"],"mappings":"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","names":["knex"]}
{
"name": "@inferagraph/sql-datasource",
"version": "0.1.1",
"version": "0.1.2",
"type": "module",

@@ -5,0 +5,0 @@ "description": "SQL datasource for InferaGraph (PostgreSQL, MySQL, SQLite, MSSQL)",

@@ -48,3 +48,3 @@ # @inferagraph/sql-datasource

const node = await datasource.getNode('node-1');
const neighbors = await datasource.getNeighbors('node-1');
const neighbors = await datasource.getNeighbors('node-1', 2); // depth>1 supported
const results = await datasource.search('keyword');

@@ -55,2 +55,6 @@

### Multi-hop neighbors
`getNeighbors(nodeId, depth)` supports `depth > 1`. SQL has no native graph traversal, so the datasource does an application-level BFS — one 1-hop fan-out per level, deduping nodes and edges by id. Single-hop callers see no change.
## Configuration

@@ -57,0 +61,0 @@