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

@deven96/ahnlich-client-node

v0.2.1

Published

Node.js gRPC client for Ahnlich AI and DB servers

Readme

Ahnlich Node.js Client SDK

Ahnlich TestSuite Node Client Tag and Deploy

A Node.js/TypeScript client that interacts with both Ahnlich DB and AI over gRPC.

Table of Contents

Installation

npm install ahnlich-client-node

Package Information

This package provides:

  • gRPC service clients for DB and AI via @connectrpc/connect
  • TypeScript types generated from the Ahnlich .proto definitions
  • Optional auth (TLS + bearer token) and trace ID support

Initialization

DB Client

import { createDbClient } from "ahnlich-client-node";

const client = createDbClient("127.0.0.1:1369");

AI Client

import { createAiClient } from "ahnlich-client-node";

const client = createAiClient("127.0.0.1:1370");

With Authentication

When the server is started with --enable-auth, pass a CA certificate and credentials:

import * as fs from "fs";
import { createDbClient } from "ahnlich-client-node";

const client = createDbClient("127.0.0.1:1369", {
  caCert: fs.readFileSync("ca.crt"),
  auth: { username: "myuser", apiKey: "mykey" },
});

Pass a trace ID to correlate requests across services:

const client = createDbClient("127.0.0.1:1369", {
  traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});

Requests - DB

Ping

import { Ping } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.ping(new Ping());
console.log(response); // Pong

Info Server

import { InfoServer } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.infoServer(new InfoServer());
console.log(response.info?.version);

List Connected Clients

import { ListClients } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.listClients(new ListClients());
console.log(response.clients);

List Stores

import { ListStores } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));

Each StoreInfo object in response.stores includes name, len, sizeInBytes, nonLinearIndices, predicateIndices, and dimension.

Get Store

Returns detailed information about a single store by name.

import { GetStore } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.getStore(new GetStore({ store: "my_store" }));
console.log(response.name);            // store name
console.log(response.dimension);        // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.nonLinearIndices); // non-linear algorithm indices
console.log(response.len);             // number of entries
console.log(response.sizeInBytes);     // size on disk

Create Store

import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb";

await client.createStore(
  new CreateStore({
    store: "my_store",
    dimension: 4,
    predicates: ["label"],
    errorIfExists: true,
  }),
);

Store dimension is fixed at creation — all inserted vectors must match it.

Set

import { Set } from "ahnlich-client-node/grpc/db/query_pb";
import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";

await client.set(
  new Set({
    store: "my_store",
    inputs: [
      new DbStoreEntry({
        key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
        value: new StoreValue({
          value: {
            label: new MetadataValue({ value: { case: "rawString", value: "A" } }),
          },
        }),
      }),
    ],
  }),
);

Get Sim N

Returns the closest N entries to a query vector.

import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";

const response = await client.getSimN(
  new GetSimN({
    store: "my_store",
    searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
    closestN: 3,
    algorithm: Algorithm.COSINE_SIMILARITY,
  }),
);
console.log(response.entries);

Get Key

import { GetKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";

const response = await client.getKey(
  new GetKey({
    store: "my_store",
    keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
  }),
);
console.log(response.entries);

Get By Predicate

import { GetPred } from "ahnlich-client-node/grpc/db/query_pb";
import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";

const response = await client.getPred(
  new GetPred({
    store: "my_store",
    condition: new PredicateCondition({
      kind: {
        case: "value",
        value: new Predicate({
          kind: {
            case: "equals",
            value: new Equals({
              key: "label",
              value: new MetadataValue({ value: { case: "rawString", value: "A" } }),
            }),
          },
        }),
      },
    }),
  }),
);

Create Predicate Index

import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb";

await client.createPredIndex(
  new CreatePredIndex({ store: "my_store", predicates: ["label"] }),
);

Drop Predicate Index

import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb";

await client.dropPredIndex(
  new DropPredIndex({ store: "my_store", predicates: ["label"], errorIfNotExists: true }),
);

Create Non Linear Algorithm Index

import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
  }),
);

// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
  }),
);

Drop Non Linear Algorithm Index

import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

await client.dropNonLinearAlgorithmIndex(
  new DropNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [NonLinearAlgorithm.KDTree],
    errorIfNotExists: true,
  }),
);

Delete Key

import { DelKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";

await client.delKey(
  new DelKey({
    store: "my_store",
    keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
  }),
);

Delete Predicate

import { DelPred } from "ahnlich-client-node/grpc/db/query_pb";

await client.delPred(
  new DelPred({
    store: "my_store",
    condition: /* same PredicateCondition as Get By Predicate */,
  }),
);

Drop Store

import { DropStore } from "ahnlich-client-node/grpc/db/query_pb";

await client.dropStore(new DropStore({ store: "my_store", errorIfNotExists: true }));

Requests - AI

Ping

import { Ping } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.ping(new Ping());

Info Server

import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.infoServer(new InfoServer());

List Stores

import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));

Get Store

Returns detailed information about a single AI store by name.

import { GetStore } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.getStore(new GetStore({ store: "ai_store" }));
console.log(response.name);           // store name
console.log(response.queryModel);     // AI model used for querying
console.log(response.indexModel);     // AI model used for indexing
console.log(response.embeddingSize);  // number of stored embeddings
console.log(response.dimension);      // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.dbInfo);         // optional DB store info (when AI is connected to DB)

Create Store

import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb";
import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb";

await client.createStore(
  new CreateStore({
    store: "ai_store",
    queryModel: AIModel.ALL_MINI_LM_L6_V2,
    indexModel: AIModel.ALL_MINI_LM_L6_V2,
    predicates: ["brand"],
    errorIfExists: true,
    storeOriginal: true,
  }),
);

Set

import { Set } from "ahnlich-client-node/grpc/ai/query_pb";
import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";
import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb";

await client.set(
  new Set({
    store: "ai_store",
    inputs: [
      new AiStoreEntry({
        key: new StoreInput({ value: { case: "rawString", value: "Jordan One" } }),
        value: new StoreValue({
          value: {
            brand: new MetadataValue({ value: { case: "rawString", value: "Nike" } }),
          },
        }),
      }),
    ],
    preprocessAction: PreprocessAction.NO_PREPROCESSING,
  }),
);

Get Sim N

import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";

const response = await client.getSimN(
  new GetSimN({
    store: "ai_store",
    searchInput: new StoreInput({ value: { case: "rawString", value: "Jordan" } }),
    closestN: 3,
    algorithm: Algorithm.COSINE_SIMILARITY,
  }),
);
console.log(response.entries);

Get By Predicate

import { GetPred } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.getPred(
  new GetPred({
    store: "ai_store",
    condition: /* PredicateCondition — same structure as DB */,
  }),
);

Create Predicate Index

import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb";

await client.createPredIndex(
  new CreatePredIndex({ store: "ai_store", predicates: ["brand"] }),
);

Drop Predicate Index

import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb";

await client.dropPredIndex(
  new DropPredIndex({ store: "ai_store", predicates: ["brand"], errorIfNotExists: true }),
);

Create Non Linear Algorithm Index

import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
  }),
);

// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
  }),
);

Drop Non Linear Algorithm Index

import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

await client.dropNonLinearAlgorithmIndex(
  new DropNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [NonLinearAlgorithm.KDTree],
    errorIfNotExists: true,
  }),
);

Delete Key

import { DelKey } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";

await client.delKey(
  new DelKey({
    store: "ai_store",
    keys: [new StoreInput({ value: { case: "rawString", value: "Jordan One" } })],
  }),
);

Drop Store

import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb";

await client.dropStore(new DropStore({ store: "ai_store", errorIfNotExists: true }));

Convert Store Input To Embeddings

Converts raw inputs (text, images, audio) into embeddings without storing them.

Basic Example:

import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";

const transport = createConnectTransport({
  baseUrl: "http://localhost:1370",
  httpVersion: "2",
});

const client = createPromiseClient(AiService, transport);

const inputs = [{ rawString: "Hello world" }];

const response = await client.convertStoreInputToEmbeddings({
  storeInputs: inputs,
  preprocessAction: PreprocessAction.NO_PREPROCESSING,
  model: AiModel.ALL_MINI_LM_L6_V2,
});

// Access embeddings
for (const item of response.values) {
  if (item.variant.case === "single") {
    const embedding = item.variant.value.embedding;
    console.log(`Embedding size: ${embedding.key.length}`);
  }
}

Face Detection with Bounding Box Metadata (v0.2.1+):

Buffalo-L and SFace models return normalized bounding boxes (0-1 range) and confidence scores:

import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";
import * as fs from "fs";

const transport = createConnectTransport({
  baseUrl: "http://localhost:1370",
  httpVersion: "2",
});

const client = createPromiseClient(AiService, transport);

// Load image
const imageBytes = fs.readFileSync("group_photo.jpg");
const inputs = [{ image: imageBytes }];

const response = await client.convertStoreInputToEmbeddings({
  storeInputs: inputs,
  preprocessAction: PreprocessAction.MODEL_PREPROCESSING,
  model: AiModel.BUFFALO_L,
});

// Process detected faces with metadata
for (const item of response.values) {
  if (item.variant.case === "multiple") {
    const faces = item.variant.value.embeddings;
    console.log(`Detected ${faces.length} faces`);
    
    for (const [i, faceData] of faces.entries()) {
      // Access embedding
      const embedding = faceData.embedding!.key; // Float32Array
      console.log(`Face ${i}: ${embedding.length}-dim embedding`);
      
      // Access bounding box metadata
      if (faceData.metadata) {
        const metadata = faceData.metadata.value;
        
        const bboxX1 = parseFloat(metadata.bbox_x1!.value!.value as string);
        const bboxY1 = parseFloat(metadata.bbox_y1!.value!.value as string);
        const bboxX2 = parseFloat(metadata.bbox_x2!.value!.value as string);
        const bboxY2 = parseFloat(metadata.bbox_y2!.value!.value as string);
        const confidence = parseFloat(metadata.confidence!.value!.value as string);
        
        console.log(`  BBox: (${bboxX1.toFixed(3)}, ${bboxY1.toFixed(3)}) ` +
                    `to (${bboxX2.toFixed(3)}, ${bboxY2.toFixed(3)})`);
        console.log(`  Confidence: ${confidence.toFixed(3)}`);
      }
    }
  }
}

Metadata Fields:

  • bbox_x1, bbox_y1, bbox_x2, bbox_y2: Normalized coordinates (0.0-1.0)
  • confidence: Detection confidence score (0.0-1.0)

To convert to pixel coordinates:

import sharp from "sharp";
const img = sharp("photo.jpg");
const { width, height } = await img.metadata();
const pixelX1 = Math.round(bboxX1 * width!);
const pixelY1 = Math.round(bboxY1 * height!);

Tracing

Pass a W3C trace ID to correlate requests across services:

const client = createDbClient("127.0.0.1:1369", {
  traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});

This sets the ahnlich-trace-id header on every request.


Development & Testing

make install-dependencies
make generate    # regenerate TypeScript protobuf code from proto definitions (requires buf)
make format
make lint-check
make test

Deploy to npm

Bump the version field in package.json. When your PR is merged into main, the CI will detect the version change and automatically publish to npm.

Type Meanings

  • StoreKey: A one-dimensional float32 vector of fixed dimension
  • StoreValue: A map of string keys to MetadataValue (text or binary)
  • StoreInput: A raw string or binary blob accepted by the AI proxy
  • Predicates: Filter conditions for stored values (Equals, NotEquals, In, etc.)
  • PredicateCondition: Combines one or more predicates with AND, OR, or Value
  • AIModel: Supported embedding models (ALL_MINI_LM_L6_V2, RESNET50, BUFFALO_L, etc.)

Change Log

| Version | Description | |---------|--------------------------------------------------| | 0.1.0 | Initial Node.js/TypeScript SDK release via gRPC |