npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tna5/tantex

v0.1.1

Published

Client for the tantex full-text search server

Readme

tantex

JavaScript/TypeScript client for the tantex full-text search server.

Installation

npm install tantex
# or
bun add tantex

Quick start

import { Tantex } from 'tantex'

const client = new Tantex({ url: 'http://localhost:3000' })

// 1. Create an index
await client.createIndex('articles', {
  fields: [
    { name: 'title', type: 'text' },
    { name: 'body',  type: 'text' },
    { name: 'date',  type: 'date', fast: true },
    { name: 'views', type: 'u64',  fast: true },
  ],
  compression: 'zstd:3',
})

// 2. Ingest documents
await client.ingest('articles', [
  { title: 'Hello world', body: 'My first post.',    date: '2026-01-01', views: 120 },
  { title: 'Tantex is fast', body: 'Benchmarks ...', date: '2026-06-17', views: 4800 },
])

// 3. Commit (make docs visible to searches)
await client.commit('articles')

// 4. Search
const results = await client.search('articles', 'fast search')
console.log(results.hits)

new Tantex(options?)

| Option | Type | Default | Description | |---|---|---|---| | url | string | "http://127.0.0.1:3000" | HTTP URL of the tantex server | | apiKey | string | — | API key sent as X-Api-Key. Only required when the server has keys configured. | | timeout | number | 30000 | Request timeout in milliseconds |

// Local server, no auth
const client = new Tantex()

// Remote server with API key
const client = new Tantex({
  url: 'https://search.example.com',
  apiKey: 'tant2_abc123...',
})

Index management

createIndex(name, schema)

Create a new index. The schema is permanent — changing field types requires deleting and recreating the index.

await client.createIndex('products', {
  fields: [
    { name: 'id',       type: 'u64',  stored: true, indexed: true,  fast: true },
    { name: 'name',     type: 'text', stored: true, indexed: true  },
    { name: 'category', type: 'text', stored: true, indexed: true,  tokenizer: 'raw' },
    { name: 'price',    type: 'f64',  stored: true, indexed: false, fast: true },
    { name: 'in_stock', type: 'bool', stored: true, indexed: true  },
    { name: 'tags',     type: 'json', stored: true, indexed: true  },
  ],
  compression: 'zstd:3',
  block_size: 16384,
})

Field types

| Type | Description | |---|---| | text | Full-text field. Tokenized by default ("default" tokenizer). | | u64 | Unsigned 64-bit integer | | i64 | Signed 64-bit integer | | f64 | 64-bit float | | date | ISO 8601 date/datetime string | | bool | Boolean | | bytes | Raw byte array | | json | Arbitrary JSON object (nested fields searchable) |

Field options

| Option | Default | Description | |---|---|---| | stored | true | Store the raw value so it can be retrieved in search hits | | indexed | true | Make the field searchable / filterable | | fast | false | Enable columnar access for sorting, aggregations, and range filters | | tokenizer | "default" | Tokenizer for text fields: "default", "raw", "en_stem" |

Schema options

| Option | Default | Description | |---|---|---| | compression | "lz4" | Docstore compression: "none", "lz4", "zstd", "zstd:3", "brotli", "snappy" | | block_size | 16384 | Docstore block size in bytes. Larger = better compression, slower point reads. |

listIndexes()

const indexes = await client.listIndexes()
// IndexInfo[]
// { name, doc_count, num_segments, pending_docs, search_count, total_docs_ingested, ... }

getIndex(name)

const info = await client.getIndex('articles')
// IndexDetails — includes schema, doc_count, num_segments, pending_docs

deleteIndex(name)

Deletes the index and all its data from disk. Irreversible.

await client.deleteIndex('articles')

getSegments(name)

const segments = await client.getSegments('articles')
// SegmentInfo[] — { segment_id, num_docs, num_deleted_docs, size_bytes }

Ingesting documents

ingest(index, documents, options?)

Ingest an array of plain objects. Documents are split into batches and sent automatically.

const result = await client.ingest('products', docs, {
  batchSize: 2000,
  onProgress: (indexed, total) => {
    console.log(`${indexed} / ${total} indexed`)
  },
})
// result: { indexed: number, errors: string[] }

Ingest options

| Option | Default | Description | |---|---|---| | batchSize | 1000 | Documents per HTTP request | | onProgress | — | Called after each batch: (indexed: number, total: number) => void |

ingestStream(index, source, options?)

Ingest from an async iterable — suitable for large files or streaming pipelines. Memory usage stays bounded regardless of total document count.

import { createReadStream } from 'node:fs'
import { createInterface } from 'node:readline'

async function* readNdjson(path) {
  const rl = createInterface({ input: createReadStream(path) })
  for await (const line of rl) {
    if (line.trim()) yield JSON.parse(line)
  }
}

const result = await client.ingestStream('articles', readNdjson('./dump.ndjson'), {
  batchSize: 5000,
  onProgress: (n) => process.stdout.write(`\r${n.toLocaleString()} docs`),
})

commit(index)

Flush pending documents and make them visible to searches. The server also auto-commits on a configurable timer and document-count threshold.

await client.commit('articles')

Searching

search(index, query, options?)

const results = await client.search('articles', 'rust programming', {
  limit: 20,
  offset: 0,
})

for (const hit of results.hits) {
  console.log(hit.score, hit.doc)
}
// results.total_hits — total number of matching documents
// results.elapsed_us — server-side query latency in microseconds

Search options

| Option | Default | Description | |---|---|---| | limit | 10 | Maximum number of hits to return | | offset | 0 | Number of hits to skip (for pagination) |

Query syntax

tantex uses tantivy query syntax.

# Full-text (searches all indexed text fields)
rust programming

# Field-scoped
title:tantivy body:search

# Phrase
"full text search"

# Boolean
rust AND NOT javascript
title:tantivy OR title:search

# Range (numeric / date)
views:[100 TO 5000]
date:[2026-01-01 TO 2026-12-31]

# Wildcard (prefix only)
prog*

# Boost
title:rust^3 body:rust

Metrics

getMetrics()

const metrics = await client.getMetrics()
// {
//   totalDocs: number,
//   totalIndexes: number,
//   totalSegments: number,
//   totalPendingDocs: number,
//   indexes: IndexInfo[],
// }

Configuration

getConfig()

const config = await client.getConfig()

setConfig(patch)

Update tunable server parameters at runtime. All fields are optional.

await client.setConfig({
  auto_commit_doc_count: 500_000,
  auto_commit_interval_secs: 10,
  num_indexing_threads: 8,
  merge_target_docs: 20_000_000,
})

| Field | Description | |---|---| | shm_buffer_size | Shared-memory buffer size per session (bytes) | | writer_heap_size | Tantivy writer heap in bytes | | auto_commit_doc_count | Soft commit threshold (docs buffered) | | auto_commit_interval_secs | Idle timeout before auto-commit | | hard_commit_multiplier | Hard commit at N × auto_commit_doc_count docs | | merge_target_docs | Target segment size for the merge policy | | max_merge_factor | Max segments merged in one pass | | min_num_segments | Minimum segments before a merge is scheduled | | num_indexing_threads | Total writer thread budget | | index_threads_pct | % of threads given to tantivy vs the parse pool |


API keys

API keys protect the HTTP API for external clients. The dashboard (localhost) always has full access.

authStatus()

const { auth_required } = await client.authStatus()

listApiKeys()

const keys = await client.listApiKeys()
// ApiKeyInfo[] — { id, name, created_at, last_used_at }

createApiKey(name)

The raw key is returned exactly once — store it immediately.

const { id, name, key } = await client.createApiKey('production-app')
console.log(key) // tant2_abc123...  ← save this now

revokeApiKey(id)

await client.revokeApiKey(id)

Error handling

All methods throw TantexError on HTTP errors.

import { Tantex, TantexError } from 'tantex'

try {
  await client.search('missing-index', 'query')
} catch (err) {
  if (err instanceof TantexError) {
    console.error(err.status, err.message) // 404, "index not found"
  }
}

High-throughput local ingestion

For maximum ingestion speed on a local machine, use TantexSocketClient which speaks the binary Unix socket protocol directly (MessagePack over a Unix domain socket, same transport as the internal benchmark tool).

import { TantexSocketClient } from 'tantex'

const client = new TantexSocketClient('/tmp/tantex.sock')
await client.connect()

await client.createIndex('bench', { fields: [...] })

const result = await client.ingest('bench', docs, {
  batchSize: 5000,
  onProgress: (n) => console.log(`${n} indexed`),
})

await client.commit('bench')
client.close()

TantexSocketClient exposes the same methods as Tantex plus getSegments() and getConfig() / setConfig(). It works in Node.js and Bun. Not available in browsers.

Tip: Open multiple TantexSocketClient connections and run them in parallel to saturate all indexing threads — the server handles concurrent writers per connection.


TypeScript

The package ships full type definitions. Key types:

import type {
  TantexOptions,
  SchemaDefinition,
  FieldDefinition,
  FieldType,
  IndexInfo,
  IndexDetails,
  SegmentInfo,
  SearchResult,
  SearchHit,
  SearchOptions,
  IngestResult,
  IngestOptions,
  ServerConfig,
  ConfigPatch,
  ApiKeyInfo,
  CreatedApiKey,
} from 'tantex'