Skip to content
Merged

Tmp #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
681 changes: 681 additions & 0 deletions internal_documentation/ENTITY_RESOLUTION.md

Large diffs are not rendered by default.

624 changes: 624 additions & 0 deletions internal_documentation/GRAPH_EXTRACTION.md

Large diffs are not rendered by default.

490 changes: 490 additions & 0 deletions internal_documentation/GRAPH_QUERY.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/adapters/pgvector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Postgres + [pgvector](https://github.com/pgvector/pgvector) storage for TypeGraph.

This adapter provides document/chunk storage, vector search, BM25 keyword search, hybrid retrieval, jobs, events, policies, and the memory/graph backing store used by TypeGraph graph and memory features.
This adapter provides source/chunk storage, vector search, BM25 keyword search, hybrid retrieval, jobs, events, policies, and the memory/graph backing store used by TypeGraph graph and memory features.

For complete setup instructions, see [Self-Hosted Initialization](https://typegraph.ai/docs/guides/self-hosted-initialization).

Expand Down Expand Up @@ -115,7 +115,7 @@ new PgVectorAdapter({
schema: 'public',
tablePrefix: 'typegraph_chunks',
hashesTable: 'typegraph_hashes',
documentsTable: 'typegraph_documents',
sourcesTable: 'typegraph_sources',
bucketsTable: 'typegraph_buckets',
jobsTable: 'typegraph_jobs',
})
Expand All @@ -130,7 +130,7 @@ Most projects only need `sql`. Use `schema` or table overrides when sharing a da
| `PgVectorAdapter` | Main Postgres + pgvector adapter |
| `PgMemoryStoreAdapter` | Persistent memory/entity/fact/passage backing store |
| `PgHashStore` | Content-hash deduplication store |
| `PgDocumentStore` | Document CRUD store |
| `PgSourceStore` | Source CRUD store |
| `PgJobStore` | Job tracking store |
| `PgEventSink` | Event sink for query/index telemetry |
| `PgPolicyStore` | Policy storage |
Expand Down
102 changes: 101 additions & 1 deletion packages/adapters/pgvector/__tests__/memory-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import type { SemanticFactRecord } from '@typegraph-ai/sdk'
import type { ExternalId, SemanticFactRecord, SemanticGraphEdge } from '@typegraph-ai/sdk'
import { PgMemoryStoreAdapter } from '../src/memory-store.js'

function makeFact(): SemanticFactRecord {
Expand Down Expand Up @@ -41,6 +41,78 @@ function rowFromParams(params: unknown[] = []): Record<string, unknown> {
}

describe('PgMemoryStoreAdapter', () => {
it('initializes the canonical graph-edge pattern without creating legacy passage tables', async () => {
const queries: string[] = []
const sql = vi.fn(async (query: string) => {
queries.push(query)
if (query.includes('FROM pg_constraint')) return []
return []
})
const store = new PgMemoryStoreAdapter({ sql, embeddingDimensions: 4 })

await store.initialize()

const ddl = queries.join('\n')
expect(ddl).toContain('typegraph_graph_edges')
expect(ddl).toContain('source_type')
expect(ddl).toContain('target_type')
expect(ddl).toContain("CHECK (source_type IN ('entity', 'chunk', 'memory'))")
expect(ddl).toContain('typegraph_entity_chunk_mentions')
expect(ddl).not.toContain('typegraph_passage_nodes')
expect(ddl).not.toContain('typegraph_passage_entity_edges')
})

it('upserts entity-to-chunk associations as typed graph edges with chunk refs', async () => {
let capturedQuery = ''
let capturedParams: unknown[] = []
const sql = vi.fn(async (query: string, params?: unknown[]) => {
capturedQuery = query
capturedParams = params ?? []
return []
})
const store = new PgMemoryStoreAdapter({ sql, embeddingDimensions: 4 })
const edge: SemanticGraphEdge = {
id: 'edge_chunk_1',
sourceType: 'entity',
sourceId: 'ent_pat',
targetType: 'chunk',
targetId: 'chunk_pat',
relation: 'MENTIONED_IN',
weight: 1.5,
properties: { mentionCount: 1 },
scope: { tenantId: 'tenant-1' },
targetChunkRef: {
bucketId: 'bucket-1',
sourceId: 'doc-1',
chunkIndex: 2,
embeddingModel: 'mock-embed',
chunkId: 'chunk_pat',
},
visibility: 'tenant',
evidence: ['chunk_pat'],
temporal: {
validAt: new Date('2026-04-16T00:00:00Z'),
createdAt: new Date('2026-04-16T00:00:00Z'),
},
}

await store.upsertGraphEdges([edge])

expect(capturedQuery).toContain('INSERT INTO typegraph_graph_edges')
expect(capturedQuery).toContain('ON CONFLICT (source_type, source_id, target_type, target_id, relation)')
expect(capturedParams[1]).toBe('entity')
expect(capturedParams[2]).toBe('ent_pat')
expect(capturedParams[3]).toBe('chunk')
expect(capturedParams[4]).toBe('chunk_pat')
expect(capturedParams[14]).toBe('bucket-1')
expect(capturedParams[15]).toBe('doc-1')
expect(capturedParams[16]).toBe(2)
expect(capturedParams[17]).toBe('mock-embed')
expect(capturedParams[18]).toBe('chunk_pat')
expect(capturedParams[19]).toBe('tenant-1')
expect(capturedParams[24]).toBe('tenant')
})

it('retries fact record upsert on duplicate deterministic fact id', async () => {
const queries: string[] = []
const sql = vi.fn(async (query: string, params?: unknown[]) => {
Expand All @@ -63,4 +135,32 @@ describe('PgMemoryStoreAdapter', () => {
expect(result.id).toBe('fact-stable')
expect(result.edgeId).toBe('edge-new')
})

it('stores scoped deterministic entity external IDs with normalized lookup values', async () => {
let capturedQuery = ''
let capturedParams: unknown[] = []
const sql = vi.fn(async (query: string, params?: unknown[]) => {
capturedQuery = query
capturedParams = params ?? []
return [{ id: 'xid_1' }]
})
const store = new PgMemoryStoreAdapter({ sql, embeddingDimensions: 4 })
const externalId: ExternalId = {
id: 'Alice@Example.com',
type: 'EMAIL',
identityType: 'user',
}

await store.upsertEntityExternalIds('ent_alice', [externalId], { tenantId: 'tenant-1' })

expect(capturedQuery).toContain('ON CONFLICT')
expect(capturedQuery).toContain('WHERE typegraph_entity_external_ids.entity_id = EXCLUDED.entity_id')
expect(capturedParams[1]).toBe('ent_alice')
expect(capturedParams[2]).toBe('user')
expect(capturedParams[3]).toBe('email')
expect(capturedParams[4]).toBe('Alice@Example.com')
expect(capturedParams[5]).toBe('alice@example.com')
expect(capturedParams[6]).toBe('none')
expect(capturedParams[9]).toBe('tenant-1')
})
})
Loading
Loading