feat: refactor VFS to use proper Brainy graph relationships

- Replace metadata path queries with brain.getRelations() API
- Use graph traversal for parent-child relationships via VerbType.Contains
- Remove fallback metadata queries - trust graph relationships completely
- Fix getChildren() to properly query relationship graph
- Update getRelated() and getRelationships() to use native Brainy APIs

This enables full graph benefits: indexes, optimizations, and visualizations
now work correctly with VFS. The filesystem structure is now a true graph
using Brainy's native relationship system.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-09-25 14:50:52 -07:00
parent cc6fa00f30
commit 4f76dac7be
3 changed files with 255 additions and 97 deletions

View file

@ -185,47 +185,35 @@ export class PathResolver {
/**
* Resolve a child entity by name within a parent directory
* Uses proper graph relationships instead of metadata queries
*/
private async resolveChild(parentId: string, name: string): Promise<string | null> {
// Check parent cache first
const cachedChildren = this.parentCache.get(parentId)
if (cachedChildren && cachedChildren.has(name)) {
// We know this child exists, find it by path since parent queries don't work
const parentEntity = await this.getEntity(parentId)
const parentPath = parentEntity.metadata.path
const childPath = this.joinPath(parentPath, name)
const pathResults = await this.brain.find({
where: { path: childPath },
limit: 1
})
if (pathResults.length > 0) {
return pathResults[0].entity.id
}
// Use cached knowledge to quickly find the child
// Still need to verify it exists
}
// Since parent field queries don't work reliably, construct the expected path
// and query by path instead
const parentEntity = await this.getEntity(parentId)
const parentPath = parentEntity.metadata.path
const childPath = this.joinPath(parentPath, name)
const results = await this.brain.find({
where: { path: childPath },
limit: 1
// Use proper graph traversal to find children
// Get all relationships where parentId contains other entities
const relations = await this.brain.getRelations({
from: parentId,
type: VerbType.Contains
})
if (results.length > 0) {
const childId = results[0].entity.id
// Find the child with matching name
for (const relation of relations) {
const childEntity = await this.brain.get(relation.to)
if (childEntity && childEntity.metadata?.name === name) {
// Update parent cache
if (!this.parentCache.has(parentId)) {
this.parentCache.set(parentId, new Set())
}
this.parentCache.get(parentId)!.add(name)
// Update parent cache
if (!this.parentCache.has(parentId)) {
this.parentCache.set(parentId, new Set())
return childEntity.id
}
this.parentCache.get(parentId)!.add(name)
return childId
}
return null
@ -233,33 +221,28 @@ export class PathResolver {
/**
* Get all children of a directory
* Uses proper graph relationships to traverse the tree
*/
async getChildren(dirId: string): Promise<VFSEntity[]> {
// Use Brainy's relationship query to find all children
const results = await this.brain.find({
connected: {
from: dirId,
via: VerbType.Contains
},
limit: 10000 // Large limit for directories
// Use proper graph API to get all Contains relationships from this directory
const relations = await this.brain.getRelations({
from: dirId,
type: VerbType.Contains
})
// Filter and process valid VFS entities only
const validChildren: VFSEntity[] = []
const childNames = new Set<string>()
for (const result of results) {
const entity = result.entity
// Only include entities with proper VFS metadata and non-empty names
if (entity.metadata?.vfsType &&
entity.metadata?.name &&
entity.metadata?.path &&
entity.id !== dirId) { // Don't include the directory itself
// Fetch all child entities
for (const relation of relations) {
const entity = await this.brain.get(relation.to)
if (entity && entity.metadata?.vfsType && entity.metadata?.name) {
validChildren.push(entity as VFSEntity)
childNames.add(entity.metadata.name)
}
}
// Update cache
this.parentCache.set(dirId, childNames)
return validChildren
}

View file

@ -1610,28 +1610,19 @@ export class VirtualFileSystem implements IVirtualFileSystem {
const entityId = await this.pathResolver.resolve(path)
const results: Array<{ path: string, relationship: string, direction: 'from' | 'to' }> = []
// Get all relationships involving this entity (both from and to)
// Use proper Brainy relationship API to get all relationships
const [fromRelations, toRelations] = await Promise.all([
this.brain.find({
connected: {
from: entityId
},
limit: 1000
}),
this.brain.find({
connected: {
to: entityId
},
limit: 1000
})
this.brain.getRelations({ from: entityId }),
this.brain.getRelations({ to: entityId })
])
// Add outgoing relationships
for (const rel of fromRelations) {
if (rel.entity.metadata?.path) {
const targetEntity = await this.brain.get(rel.to)
if (targetEntity && targetEntity.metadata?.path) {
results.push({
path: rel.entity.metadata.path,
relationship: 'related',
path: targetEntity.metadata.path,
relationship: rel.type || 'related',
direction: 'from'
})
}
@ -1639,10 +1630,11 @@ export class VirtualFileSystem implements IVirtualFileSystem {
// Add incoming relationships
for (const rel of toRelations) {
if (rel.entity.metadata?.path) {
const sourceEntity = await this.brain.get(rel.from)
if (sourceEntity && sourceEntity.metadata?.path) {
results.push({
path: rel.entity.metadata.path,
relationship: 'related',
path: sourceEntity.metadata.path,
relationship: rel.type || 'related',
direction: 'to'
})
}
@ -1657,51 +1649,39 @@ export class VirtualFileSystem implements IVirtualFileSystem {
const entityId = await this.pathResolver.resolve(path)
const relationships: Relation[] = []
// Get all relationships involving this entity (both from and to)
// Use proper Brainy relationship API
const [fromRelations, toRelations] = await Promise.all([
this.brain.find({
connected: {
from: entityId
},
limit: 1000
}),
this.brain.find({
connected: {
to: entityId
},
limit: 1000
})
this.brain.getRelations({ from: entityId }),
this.brain.getRelations({ to: entityId })
])
// Add outgoing relationships (exclude parent-child relationships)
// Process outgoing relationships (excluding Contains for parent-child)
for (const rel of fromRelations) {
if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
// Skip parent-child relationships to focus on user-defined relationships
const parentPath = this.getParentPath(rel.entity.metadata.path)
if (parentPath !== path) { // Not a direct child
if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
const targetEntity = await this.brain.get(rel.to)
if (targetEntity && targetEntity.metadata?.path) {
relationships.push({
id: crypto.randomUUID(),
from: path,
to: rel.entity.metadata.path,
type: VerbType.References,
createdAt: Date.now()
id: rel.id || crypto.randomUUID(),
from: entityId,
to: rel.to,
type: rel.type,
createdAt: rel.createdAt || Date.now()
})
}
}
}
// Add incoming relationships (exclude parent-child relationships)
// Process incoming relationships (excluding Contains for parent-child)
for (const rel of toRelations) {
if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
// Skip parent-child relationships to focus on user-defined relationships
const parentPath = this.getParentPath(path)
if (rel.entity.metadata.path !== parentPath) { // Not the parent
if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
const sourceEntity = await this.brain.get(rel.from)
if (sourceEntity && sourceEntity.metadata?.path) {
relationships.push({
id: crypto.randomUUID(),
from: rel.entity.metadata.path,
to: path,
type: VerbType.References,
createdAt: Date.now()
id: rel.id || crypto.randomUUID(),
from: rel.from,
to: entityId,
type: rel.type,
createdAt: rel.createdAt || Date.now()
})
}
}