@samveys/search

Version
1.0.3Nov 5, 2026
Install
pnpm add @samveys/search

From packages/search/README.md in the framework repository.

Full-text search for ORM models with a built-in database driver, so no search server is needed:

EngineIndex
SQLiteFTS5 virtual table, ranked with bm25() and field weights
PostgreSQLtsvector column with setweight(), GIN index, ts_rank
MySQL / MariaDBFULLTEXT index per field; a field's weight counts once when it matches

User input is reduced to plain words before it reaches any engine, so query syntax in a search box is never executed. Meilisearch, Typesense and OpenSearch drivers live in their own packages.

Make a model searchable

ts
import { databaseSearchDriver, searchable } from '@samveys/search'
import { Report } from './models/report.ts'

export const reports = searchable(
  Report,
  { fields: { title: { weight: 3 }, body: {} }, filters: ['region'] },
  { driver: databaseSearchDriver(() => db), database: () => db },
)

const page = await reports.search('october revenue').where('region', 'west-java').paginate(1, 20)

Keep the index in step: the outbox

Index updates go through an outbox table, samveys_search_outbox. Write the update in the same transaction as the model; a worker or scheduled task applies it:

ts
await db.transaction(async (trx) => {
  const report = await Report.create({ title: 'Q3', body: '…', region: 'bali' }, trx)
  await reports.queue(report, 'upsert', trx) // rolled back with the model, never lost after commit
})

await reports.flushOutbox() // idempotent; safe to run from several workers

Soft-deleted and deleted models are removed from the index on the next flush. importAll() rebuilds an index from the table; clear() empties it.

Write a driver

A driver implements SearchDriver (ensureIndex, upsert, remove, flush, search) and proves it with the shared suite:

ts
import { runSearchConformance } from '@samveys/search/conformance'

runSearchConformance({ name: 'my-engine', driver: () => myDriver() })

The suite checks every-word matching, field weights, filters, paging totals, replace/remove/flush, and hostile query syntax.