LocalMode
Core

Typed Metadata

Type-safe VectorDB metadata with compile-time filter validation.

The VectorDB supports generic metadata types, giving you compile-time type checking on add(), search(), deleteWhere(), and filter expressions. No runtime cost -- all validation happens at the TypeScript level.

See it in action

Try Semantic Search and Product Search for working demos of these APIs.

Before and After

// No type parameter — metadata is Record<string, unknown>
const db = await createVectorDB({ name: 'docs', dimensions: 384 });

await db.add({
  id: 'doc-1',
  vector: embedding,
  metadata: { anything: 'goes', count: 42 },
});

// Filters accept any key — typos compile fine but produce wrong results
const results = await db.search(query, {
  k: 5,
  filter: { categry: { $eq: 'tech' } }, // Typo not caught!
});
interface ArticleMetadata {
  title: string;
  category: 'tech' | 'science' | 'business';
  year: number;
  tags: string[];
}

const db = await createVectorDB<ArticleMetadata>({
  name: 'articles',
  dimensions: 384,
});

await db.add({
  id: 'doc-1',
  vector: embedding,
  metadata: {
    title: 'Intro to ML',
    category: 'tech', // Autocomplete shows 'tech' | 'science' | 'business'
    year: 2025,
    tags: ['ml', 'ai'],
  },
});

// Filter keys are constrained to keyof ArticleMetadata
const results = await db.search(query, {
  k: 5,
  filter: { category: { $eq: 'tech' } }, // Type-safe!
});

// TypeScript error: 'categry' does not exist on type
// filter: { categry: { $eq: 'tech' } }

TypedFilterQuery

When you pass a type parameter to createVectorDB<TMetadata>(), filter expressions use TypedFilterQuery<TMetadata> instead of the default untyped FilterQuery. This constrains filter keys to keyof TMetadata and validates operator value types.

// TypedFilterQuery<ArticleMetadata> only allows these keys:
const results = await db.search(query, {
  k: 10,
  filter: {
    category: { $eq: 'tech' },      // string operators
    year: { $gte: 2023 },           // number operators
    tags: { $in: ['ml', 'ai'] },    // array operators
  },
});

// deleteWhere is also type-safe
await db.deleteWhere({
  year: { $lt: 2020 },
});

Prop

Type

For the full list of filter operators, see Vector Database — Filter Operators.

Schema Validation

For runtime validation in addition to compile-time types, pass a schema to createVectorDB(). The schema uses the same ObjectSchema interface as generateObject().

With jsonSchema()

import { createVectorDB, jsonSchema } from '@localmode/core';

const db = await createVectorDB<ArticleMetadata>({
  name: 'articles',
  dimensions: 384,
  schema: jsonSchema({
    type: 'object',
    properties: {
      title: { type: 'string' },
      category: { type: 'string', enum: ['tech', 'science', 'business'] },
      year: { type: 'number' },
      tags: { type: 'array', items: { type: 'string' } },
    },
    required: ['title', 'category', 'year', 'tags'],
  }),
});

// Throws ValidationError at runtime if metadata doesn't match schema
await db.add({
  id: 'doc-1',
  vector: embedding,
  metadata: { title: 123, category: 'invalid' }, // Runtime error!
});

With Zod

import { createVectorDB, jsonSchema } from '@localmode/core';
import { z } from 'zod';

const ArticleSchema = z.object({
  title: z.string(),
  category: z.enum(['tech', 'science', 'business']),
  year: z.number().int().min(1900),
  tags: z.array(z.string()),
});

type ArticleMetadata = z.infer<typeof ArticleSchema>;

const db = await createVectorDB<ArticleMetadata>({
  name: 'articles',
  dimensions: 384,
  schema: jsonSchema(ArticleSchema),
});

Schema is Optional

Type-safe filters work with or without a schema. The generic type parameter handles compile-time safety. The schema adds optional runtime validation on add() and addMany().

Backward Compatibility

The default generic parameter is Record<string, unknown>, so existing code continues to work without changes:

// These are equivalent — both use untyped metadata
const db1 = await createVectorDB({ name: 'db', dimensions: 384 });
const db2 = await createVectorDB<Record<string, unknown>>({ name: 'db', dimensions: 384 });

The FilterQuery type alias is kept for backward compatibility but is deprecated in favor of TypedFilterQuery<TMetadata>:

import type { TypedFilterQuery, FilterQuery } from '@localmode/core';

// Preferred
type MyFilter = TypedFilterQuery<ArticleMetadata>;

// Deprecated alias (still works)
type UntypedFilter = FilterQuery;

Typed Search Results

When using a typed VectorDB, search results have typed metadata:

const results = await db.search(queryVector, { k: 5 });

results.forEach((r) => {
  // r.metadata is typed as ArticleMetadata
  console.log(r.metadata.title);    // string
  console.log(r.metadata.category); // 'tech' | 'science' | 'business'
  console.log(r.metadata.year);     // number
});

Next Steps

Showcase Apps

AppDescriptionLinks
Semantic SearchType-safe metadata filtering on vector search resultsDemo · Source
Product SearchTyped product metadata for filtered catalog searchDemo · Source

On this page