Skip to content

Planned — Not Yet Implemented

The API described below is planned but not yet implemented. The documented interface shows the intended design.

Node.js ​

Node.js wrapper for Memory with full TypeScript support. Communicates with a pre-built Go binary over JSON-RPC (stdin/stdout).

bash
npm install @openparallax/memory

The Go binary is downloaded automatically on install for your platform (linux/darwin/windows, amd64/arm64), similar to how esbuild and Prisma work.

Quick Start ​

typescript
import { Memory, SQLiteStore } from '@openparallax/memory'

const store = new SQLiteStore('memory.db')
const mem = new Memory({
  store,
  embedding: {
    provider: 'openai',
    model: 'text-embedding-3-small',
  },
})

// Store a record
await mem.store({
  id: 'note-1',
  text: 'The deployment pipeline uses blue-green deploys with Kubernetes.',
  metadata: { source: 'ops-docs' },
})

// Search by meaning
const results = await mem.search('CI/CD and deployment strategy', { limit: 5 })
for (const r of results) {
  console.log(`[${r.score.toFixed(3)}] (${r.source}) ${r.id}: ${r.text.slice(0, 80)}`)
}

await store.close()

TypeScript Types ​

typescript
interface Record {
  id?: string                          // Auto-generated if omitted
  text: string                         // Text content to store and search
  metadata?: Record<string, string>    // Arbitrary key-value metadata
}

interface SearchResult {
  id: string                           // Record ID
  text: string                         // Matched text content
  score: number                        // Relevance score (0.0 to 1.0)
  source: 'vector' | 'keyword' | 'hybrid'
  metadata: Record<string, string>     // Record metadata
  path?: string                        // Source file path
  startLine?: number                   // Start line in source file
  endLine?: number                     // End line in source file
}

interface SearchOptions {
  limit?: number                       // Max results (default: 10)
  filters?: Record<string, string>     // Metadata filters
}

interface EmbeddingConfig {
  provider: 'openai' | 'google' | 'ollama'
  model?: string
  apiKey?: string                      // Or reads from env var
  apiKeyEnv?: string                   // Env var name for API key
  baseUrl?: string                     // Custom API endpoint
}

interface MemoryOptions {
  store: Store
  embedding?: EmbeddingConfig
  chunkSize?: number                   // Tokens per chunk (default: 400)
  chunkOverlap?: number                // Overlap tokens (default: 80)
  vectorWeight?: number                // Vector score weight (default: 0.7)
  keywordWeight?: number               // Keyword score weight (default: 0.3)
}

Configuration ​

Embedding Providers ​

typescript
// OpenAI
const mem = new Memory({
  store,
  embedding: {
    provider: 'openai',
    model: 'text-embedding-3-small',  // 1536 dimensions
    // Reads OPENAI_API_KEY from env by default
  },
})

// OpenAI with explicit key
const mem = new Memory({
  store,
  embedding: {
    provider: 'openai',
    model: 'text-embedding-3-large',  // 3072 dimensions
    apiKey: process.env.OPENAI_API_KEY,
  },
})

// Google
const mem = new Memory({
  store,
  embedding: {
    provider: 'google',
    model: 'text-embedding-004',  // 768 dimensions
    apiKeyEnv: 'GOOGLE_AI_API_KEY',
  },
})

// Ollama (local, free)
const mem = new Memory({
  store,
  embedding: {
    provider: 'ollama',
    model: 'nomic-embed-text',  // 768 dimensions
    baseUrl: 'http://localhost:11434',
  },
})

Memory Options ​

typescript
const mem = new Memory({
  store,
  embedding: { provider: 'openai', model: 'text-embedding-3-small' },
  chunkSize: 400,       // tokens per chunk
  chunkOverlap: 80,     // overlap between chunks
  vectorWeight: 0.7,    // hybrid search vector weight
  keywordWeight: 0.3,   // hybrid search keyword weight
})

Store Backends ​

SQLite (Default) ​

typescript
import { SQLiteStore } from '@openparallax/memory'

const store = new SQLiteStore('memory.db')

PostgreSQL + pgvector ​

typescript
import { PgvectorStore } from '@openparallax/memory'

const store = new PgvectorStore({
  dsn: 'postgres://user:pass@localhost:5432/memory',
  tableName: 'memories',
  dimensions: 1536,
  indexType: 'hnsw',
})

Qdrant ​

typescript
import { QdrantStore } from '@openparallax/memory'

const store = new QdrantStore({
  url: 'http://localhost:6333',
  collectionName: 'memories',
  dimensions: 1536,
  apiKey: process.env.QDRANT_API_KEY,
})

Pinecone ​

typescript
import { PineconeStore } from '@openparallax/memory'

const store = new PineconeStore({
  apiKey: process.env.PINECONE_API_KEY!,
  indexName: 'memories',
  namespace: 'default',
})

Weaviate ​

typescript
import { WeaviateStore } from '@openparallax/memory'

const store = new WeaviateStore({
  url: 'http://localhost:8080',
  className: 'Memory',
  apiKey: process.env.WEAVIATE_API_KEY,
})

ChromaDB ​

typescript
import { ChromaStore } from '@openparallax/memory'

const store = new ChromaStore({
  url: 'http://localhost:8000',
  collectionName: 'memories',
})

Redis ​

typescript
import { RedisStore } from '@openparallax/memory'

const store = new RedisStore({
  url: 'redis://localhost:6379',
  indexName: 'memory_idx',
  prefix: 'mem:',
  dimensions: 1536,
})

Storing Records ​

Basic Store ​

typescript
await mem.store({
  id: 'note-1',
  text: 'The frontend uses Svelte 4 with TypeScript and Vite for bundling.',
  metadata: { source: 'tech-stack', date: '2026-03-15' },
})

Auto-Generated ID ​

typescript
const id = await mem.store({
  text: 'User prefers GitHub Issues over Jira for project tracking.',
})
console.log(`Stored with ID: ${id}`)

Batch Store ​

typescript
await mem.storeBatch([
  { id: 'doc-1', text: 'Architecture uses CQRS pattern.', metadata: { type: 'adr' } },
  { id: 'doc-2', text: 'Events are stored in append-only log.', metadata: { type: 'adr' } },
  { id: 'doc-3', text: 'Read models are eventually consistent.', metadata: { type: 'adr' } },
])

Upsert ​

Storing with an existing ID replaces the record:

typescript
await mem.store({ id: 'config', text: 'Using port 8080' })
await mem.store({ id: 'config', text: 'Migrated to port 3100' })  // replaces

Searching ​

Hybrid Search (Default) ​

typescript
const results = await mem.search('authentication and authorization', { limit: 10 })

for (const r of results) {
  console.log(`[${r.score.toFixed(3)}] (${r.source}) ${r.id}`)
  console.log(`  ${r.text.slice(0, 100)}`)
  console.log(`  metadata:`, r.metadata)
}
typescript
const results = await mem.searchVector('deployment automation', { limit: 10 })

Keyword-Only Search (FTS5) ​

typescript
const results = await mem.searchKeyword('CORS middleware', { limit: 10 })

Search with Metadata Filtering ​

typescript
const results = await mem.search('database schema', {
  limit: 10,
  filters: { type: 'architecture-decision' },
})

Deleting Records ​

typescript
// Delete by ID
await mem.delete('note-1')

// Delete by metadata filter
const deletedCount = await mem.deleteByFilter({ type: 'temporary' })
console.log(`Deleted ${deletedCount} records`)

Error Handling ​

typescript
import { MemoryError, StoreError, EmbeddingError } from '@openparallax/memory'

try {
  await mem.store({ id: 'doc-1', text: 'Some content' })
} catch (err) {
  if (err instanceof EmbeddingError) {
    console.error(`Embedding failed: ${err.message}`)
    // FTS5 keyword search still works without embeddings
  } else if (err instanceof StoreError) {
    console.error(`Backend error: ${err.message}`)
  } else if (err instanceof MemoryError) {
    console.error(`Memory error: ${err.message}`)
  }
}

Complete Example ​

typescript
import { readdir, readFile } from 'node:fs/promises'
import { join, relative } from 'node:path'
import { Memory, SQLiteStore } from '@openparallax/memory'

async function indexDirectory(docsDir: string, dbPath: string): Promise<Memory> {
  const store = new SQLiteStore(dbPath)
  const mem = new Memory({
    store,
    embedding: {
      provider: 'openai',
      model: 'text-embedding-3-small',
    },
  })

  const files = await readdir(docsDir, { recursive: true })
  const mdFiles = files.filter(f => f.endsWith('.md'))

  for (const file of mdFiles) {
    const fullPath = join(docsDir, file)
    const content = await readFile(fullPath, 'utf-8')
    await mem.store({
      id: file,
      text: content,
      metadata: {
        source: 'docs',
        path: fullPath,
        size: String(content.length),
      },
    })
    console.log(`Indexed: ${file}`)
  }

  return mem
}

async function main() {
  const mem = await indexDirectory('./docs', 'knowledge.db')

  const queries = [
    'how does authentication work?',
    'deployment process and CI/CD',
    'error handling patterns',
  ]

  for (const query of queries) {
    console.log(`\nQuery: "${query}"`)
    const results = await mem.search(query, { limit: 3 })
    for (const r of results) {
      console.log(`  [${r.score.toFixed(3)}] ${r.id} (${r.source})`)
      console.log(`    ${r.text.slice(0, 100).replace(/\n/g, ' ')}...`)
    }
  }
}

main().catch(console.error)

How It Works ​

The Node.js package communicates with a pre-built Go binary over JSON-RPC (stdin/stdout) -- the same protocol used by MCP. The Go binary is compiled for all major platforms and downloaded automatically during npm install.

This architecture means:

  • No native addons -- works with any Node.js version, no node-gyp
  • Same performance as the Go library -- all computation runs in Go
  • Cross-platform -- Linux, macOS, and Windows (amd64 and arm64)
  • Full TypeScript types -- complete type definitions included

Next Steps ​