TypeScript SDK

The official TypeScript/JavaScript SDK for brinicle provides a type-safe HTTP client that works in both Node.js and browser environments. It uses the native fetch API with zero runtime dependencies.

Installation

npm install @bicardinal/brinicle
# or
yarn add @bicardinal/brinicle
# or
pnpm add @bicardinal/brinicle

Quick Start

import { BrinicleClient } from "@bicardinal/brinicle";

const client = new BrinicleClient({
  baseUrl: "http://localhost:1984",
  timeout: 30000,
});

// Create an index
await client.createIndex("products", 384, 0.1, {
  M: 48,
  ef_construction: 1024,
  ef_search: 512,
});

// Initialize and ingest
await client.init("products", "build");
await client.ingest("products", "p1", new Array(384).fill(0.1));
await client.ingest("products", "p2", new Array(384).fill(0.2));
await client.finalize("products", true);

// Search
const results = await client.search("products", new Array(384).fill(0.1), 5);
console.log(results); // ['p1', 'p2']

Configuration

import { BrinicleClient } from "@bicardinal/brinicle";

const client = new BrinicleClient({
  baseUrl: "http://localhost:1984", // Server URL
  timeout: 30000, // Request timeout in ms
  headers: {
    // Custom headers
    Authorization: "Bearer token",
  },
});

BrinicleConfig

OptionTypeDefaultDescription
baseUrlstring"http://localhost:1984"Server base URL
timeoutnumber30000Request timeout in milliseconds
headersRecord<string, string>{}Custom HTTP headers

API Reference

healthCheck

const response = await client.healthCheck();
// SuccessResponse { success: true, message: "..." }

createIndex

await client.createIndex("my_index", 384, 0.1, {
  M: 48,
  ef_construction: 1024,
  ef_search: 512,
  rng_seed: 0,
});

listIndexes

const response = await client.listIndexes();
// ListIndexesResponse { indexes: string[], count: number }

deleteIndex

// Close the index (data preserved)
await client.deleteIndex("my_index");

// Permanently destroy
await client.deleteIndex("my_index", true);

getIndexStatus

const status = await client.getIndexStatus("my_index");
// IndexStatusResponse { index_name, dim, has_index, needs_rebuild }

loadIndex

await client.loadIndex("my_index");

init

await client.init("my_index", "build"); // New index
await client.init("my_index", "insert"); // Add to existing
await client.init("my_index", "upsert"); // Add or update

ingest

await client.ingest("my_index", "item_001", [0.1, 0.2, 0.3, 0.4]);

ingestBatch

Batch ingest using the binary protocol for maximum throughput:
const ids = ["item_001", "item_002", "item_003"];
const vectors = [
  [0.1, 0.2, 0.3, 0.4],
  [0.5, 0.6, 0.7, 0.8],
  [0.9, 1.0, 1.1, 1.2],
];
const response = await client.ingestBatch("my_index", ids, vectors, 4);
// BatchIngestResponse { success: true, count: 3 }

finalize

await client.finalize("my_index", true); // optimize = true
await client.finalize("my_index", true, { M: 48 }); // with params

deleteItems

const response = await client.deleteItems("my_index", ["id1", "id2"], true);
// DeleteResponse { deleted_count: 2, not_found: [] }

rebuild

await client.rebuild("my_index", { M: 48, ef_construction: 1024 });
const results = await client.search("my_index", [0.1, 0.2, 0.3, 0.4], 10, 64);
// string[] — array of external IDs

optimize

await client.optimize("my_index");

ItemSearch

The ItemSearch API enables structured search over items with lexical fields, combining vector similarity with metadata filtering. Unlike the basic vector search which operates on raw embeddings, ItemSearch lets you ingest items with human-readable fields such as titles, descriptions, and categories, then perform filtered searches that combine full-text matching with vector similarity. This makes it ideal for product catalogs, document collections, and any dataset where you need to search by both content and structured attributes. Each item index is backed by its own vector index, so you still benefit from brinicle’s high-performance HNSW graph for similarity lookups. The difference is that ItemSearch adds a lexical configuration layer that controls how text fields are tokenized and matched, allowing you to fine-tune relevance for your specific domain. The TypeScript SDK provides full type safety for all ItemSearch operations, including strongly-typed filters, results, and configuration objects.

createItemIndex

Create a new item search index with an optional lexical configuration. The lexicalConfig parameter controls how text fields are analyzed during ingestion and search — you can specify which fields are searchable, how they are tokenized, and whether stemming or stop-word removal should be applied. If omitted, a default configuration is used that treats all text fields as searchable with standard tokenization. The method is fully async and returns a SuccessResponse indicating the index was created.
import { BrinicleClient, LexicalConfig } from "@bicardinal/brinicle";

const client = new BrinicleClient({ baseUrl: "http://localhost:1984" });

const lexicalConfig: LexicalConfig = {
  searchable_fields: ["title", "description", "category"],
  tokenizer: "standard",
  lowercase: true,
  stem: false,
  remove_stop_words: true,
};

await client.createItemIndex(
  "products",
  384,
  0.1,
  {
    M: 48,
    ef_construction: 1024,
    ef_search: 512,
  },
  lexicalConfig,
);

initItemIngest

Initialize an ingest session for an item index. This works identically to the vector engine’s init method, accepting a mode of 'build', 'insert', or 'upsert'. You must call this before ingesting any items, and you must call finalize after all items have been ingested. The build mode creates a fresh index from scratch, while insert and upsert modes allow you to add or update items in an existing index.
await client.initItemIngest("products", "build"); // New index
await client.initItemIngest("products", "insert"); // Add to existing
await client.initItemIngest("products", "upsert"); // Add or update

ingestItem

Ingest a single item with its external ID, vector, and associated metadata fields. The Item type provides the structure for the item parameter, including the external_id, vector, and fields properties. Fields not listed in the searchable_fields of the LexicalConfig are stored but not indexed for text search. The vector must match the dimension specified during index creation.
import { Item } from "@bicardinal/brinicle";

const item1: Item = {
  external_id: "p1",
  vector: new Array(384).fill(0.1),
  fields: {
    title: "Apple iPhone 15 Pro Max",
    description: "Latest iPhone with A17 Pro chip",
    category: "Electronics",
    price: "1199",
  },
};

await client.ingestItem("products", item1);

const item2: Item = {
  external_id: "p2",
  vector: new Array(384).fill(0.2),
  fields: {
    title: "Samsung Galaxy S24 Ultra",
    description: "Flagship Android with AI features",
    category: "Electronics",
    price: "1299",
  },
};

await client.ingestItem("products", item2);

searchItems

Search for items using a combination of vector similarity and structured filters. The query parameter provides the text query which is matched against the searchable fields defined in the lexical configuration. The filters parameter accepts structured conditions that narrow results by exact field values or ranges. The response is a typed ItemSearchResponse containing an array of ItemSearchResult objects with item IDs, scores, and matched fields.
import { ItemSearchFilters } from "@bicardinal/brinicle";

// Simple text search
const response1 = await client.searchItems("products", {
  query: "iphone",
  k: 10,
});
// ItemSearchResponse { results: [...], total: 5 }

// Search with filters
const filters: ItemSearchFilters = {
  must: { category: "Electronics" },
  must_not: { price: ["0", "100"] },
};

const response2 = await client.searchItems("products", {
  query: "smartphone",
  filters,
  k: 10,
  efs: 64,
});

// Access results
for (const result of response2.results) {
  console.log(`${result.external_id} (score: ${result.score})`);
  console.log("Fields:", result.fields);
}

deleteItemIndex

Delete or close an item search index. When called with destroy: false (the default), the index is closed but its data remains on disk and can be reloaded later. When called with destroy: true, the index and all its data are permanently removed. This method is identical in behavior to deleteIndex but operates on item search indexes specifically.
// Close the index (data remains on disk)
await client.deleteItemIndex("products");

// Permanently destroy the index
await client.deleteItemIndex("products", true);

getItemIndexStatus

Check the status of an item search index. Returns the index name, dimension, whether the HNSW graph has been built, and whether a rebuild is needed after recent ingestions. This is useful for monitoring the health of your indexes and determining when a rebuild or optimization might be necessary.
const status = await client.getItemIndexStatus("products");
// ItemIndexStatusResponse { index_name: 'products', dim: 384, has_index: true, needs_rebuild: false }

Autocomplete

The Autocomplete API provides prefix-based search for type-ahead and suggestion use cases. Autocomplete indexes are optimized for fast prefix matching, making them ideal for search boxes, tag suggestion, and any interface where users type partial queries and expect instant results. Unlike ItemSearch which combines vector similarity with text matching, Autocomplete focuses on sub-millisecond prefix lookups over string keys. Each autocomplete index stores a set of string keys and their associated vectors. When a user types a partial query, the engine searches for keys that begin with the typed prefix and returns the closest matches ranked by vector similarity. This allows you to build autocomplete experiences that are both fast and semantically relevant — for example, typing “iph” could suggest “iPhone 15 Pro Max” based on both the prefix match and the semantic proximity to other electronics products.

createAutocompleteIndex

Create a new autocomplete index with an optional autocomplete configuration. The autocompleteConfig parameter controls how suggestions are matched and ranked — for example, you can set the minimum prefix length before suggestions are returned, the maximum number of suggestions, and whether to apply fuzzy matching for typo tolerance. If omitted, sensible defaults are used that work well for most use cases.
import { AutocompleteConfig } from "@bicardinal/brinicle";

const autocompleteConfig: AutocompleteConfig = {
  min_prefix_length: 1,
  max_suggestions: 10,
  fuzzy_match: true,
  fuzzy_distance: 2,
};

await client.createAutocompleteIndex(
  "product_suggestions",
  384,
  0.1,
  {
    M: 48,
    ef_construction: 1024,
    ef_search: 512,
  },
  autocompleteConfig,
);

initAutocompleteIngest

Initialize an ingest session for an autocomplete index. Like the vector engine’s init method, this must be called before ingesting any autocomplete entries. The mode controls whether you are building a new index from scratch or adding entries to an existing one. After ingesting all entries, you must call finalize to commit the data and build the prefix index.
await client.initAutocompleteIngest("product_suggestions", "build");
await client.initAutocompleteIngest("product_suggestions", "insert");
await client.initAutocompleteIngest("product_suggestions", "upsert");

ingestAutocomplete

Ingest a single autocomplete entry with its key string and associated vector. The key is the string that users will type as a prefix — for example, a product name like “iPhone 15 Pro Max”. The vector provides the semantic representation used to rank suggestions when multiple prefix matches exist. Keys should be lowercase if the AutocompleteConfig specifies lowercase: true, as the engine will not automatically normalize keys during ingestion.
await client.ingestAutocomplete(
  "product_suggestions",
  "iphone 15 pro max",
  new Array(384).fill(0.1),
);
await client.ingestAutocomplete(
  "product_suggestions",
  "samsung galaxy s24 ultra",
  new Array(384).fill(0.2),
);
await client.ingestAutocomplete(
  "product_suggestions",
  "google pixel 8 pro",
  new Array(384).fill(0.3),
);

searchAutocomplete

Search for autocomplete suggestions matching a given prefix. The query parameter is the partial string typed by the user, and the k parameter controls how many suggestions to return. Results are ranked by a combination of prefix match quality and vector similarity, ensuring that the most relevant suggestions appear first. This method is designed for sub-millisecond response times, making it suitable for interactive type-ahead interfaces.
const suggestions = await client.searchAutocomplete("product_suggestions", {
  query: "iph",
  k: 5,
});
// ['iphone 15 pro max', 'iphone 15 case', 'iphone charger']

deleteAutocompleteIndex

Delete or close an autocomplete index. When called with destroy: false (the default), the index is closed but its data remains on disk. When called with destroy: true, the index and all associated data are permanently removed from disk. Use this method to clean up indexes that are no longer needed or to force a full rebuild by destroying and recreating an index.
// Close the index (data remains on disk)
await client.deleteAutocompleteIndex("product_suggestions");

// Permanently destroy the index
await client.deleteAutocompleteIndex("product_suggestions", true);

getAutocompleteIndexStatus

Check the status of an autocomplete index. Returns the index name, vector dimension, whether the prefix index has been built, and whether a rebuild is needed after recent ingestions. Monitoring index status is important for autocomplete indexes because the prefix index must be rebuilt after ingestion to reflect new entries.
const status = await client.getAutocompleteIndexStatus("product_suggestions");
// AutocompleteIndexStatusResponse { index_name: 'product_suggestions', dim: 384, has_index: true, needs_rebuild: false }

Error Handling

import {
  BrinicleError,
  ConnectionError,
  ValidationError,
  NotFoundError,
  ConflictError,
} from "@bicardinal/brinicle";

try {
  await client.createIndex("my_index", 128);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error("Index not found:", error.message);
  } else if (error instanceof ConflictError) {
    console.error("Index already exists:", error.message);
  } else if (error instanceof ValidationError) {
    console.error("Invalid parameters:", error.message);
  } else if (error instanceof ConnectionError) {
    console.error("Connection failed:", error.message);
  } else if (error instanceof BrinicleError) {
    console.error(`API error (${error.statusCode}):`, error.message);
  }
}

Type Definitions

interface HNSWParams {
  M?: number;
  ef_construction?: number;
  ef_search?: number;
  rng_seed?: number;
}

interface SuccessResponse {
  success: boolean;
  message?: string;
  index_name?: string;
}

interface ListIndexesResponse {
  indexes: string[];
  count: number;
}

interface IndexStatusResponse {
  index_name: string;
  dim: number;
  has_index: boolean;
  needs_rebuild: boolean;
}

interface DeleteResponse {
  deleted_count: number;
  not_found?: string[];
}

interface BatchIngestResponse {
  success: boolean;
  count: number;
}

type IngestMode = "build" | "insert" | "upsert";

ItemSearch Types

The following types are used by the ItemSearch API. The Item type represents a single item to ingest, with its ID, vector, and metadata fields. The ItemSearchFilters type provides structured filtering with must and must_not conditions. The ItemSearchResult type represents a single search result with score and matched fields, and ItemSearchResponse wraps the full response including pagination metadata.
interface Item {
  external_id: string;
  vector: number[];
  fields: Record<string, string>;
}

interface ItemSearchFilters {
  must?: Record<string, string | string[]>;
  must_not?: Record<string, string | string[]>;
}

interface ItemSearchResult {
  external_id: string;
  score: number;
  fields: Record<string, string>;
}

interface ItemSearchResponse {
  results: ItemSearchResult[];
  total: number;
}

interface LexicalConfig {
  searchable_fields?: string[];
  tokenizer?: "standard" | "whitespace" | "ngram";
  lowercase?: boolean;
  stem?: boolean;
  remove_stop_words?: boolean;
}

Autocomplete Types

The following types are used by the Autocomplete API. The AutocompleteConfig type controls how the autocomplete engine matches prefixes and ranks suggestions, including options for fuzzy matching and minimum prefix length.
interface AutocompleteConfig {
  min_prefix_length?: number;
  max_suggestions?: number;
  fuzzy_match?: boolean;
  fuzzy_distance?: number;
}

Node.js and Browser Compatibility

The SDK uses the native fetch API which is available in:
  • Node.js 18+ (native fetch is built-in)
  • Modern browsers (Chrome, Firefox, Safari, Edge)
For older Node.js versions, you may need to add a fetch polyfill.

Tree Shaking

The SDK supports ES modules and is compatible with tree-shaking bundlers like webpack, Rollup, and esbuild:
// Only import what you need
import { BrinicleClient } from "@bicardinal/brinicle";
import type { HNSWParams, SearchResponse } from "@bicardinal/brinicle";